001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
002 *
003 * Copyright © 2025–2026 microBean™.
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
006 * the License. You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
011 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
012 * specific language governing permissions and limitations under the License.
013 */
014package org.microbean.construct.element;
015
016import java.lang.constant.ClassDesc;
017import java.lang.constant.Constable;
018import java.lang.constant.ConstantDesc;
019import java.lang.constant.DynamicConstantDesc;
020
021import java.util.LinkedHashMap;
022import java.util.List;
023import java.util.Map;
024import java.util.Optional;
025
026import javax.lang.model.element.AnnotationMirror;
027import javax.lang.model.element.AnnotationValue;
028import javax.lang.model.element.Element;
029import javax.lang.model.element.ExecutableElement;
030import javax.lang.model.element.TypeElement;
031
032import javax.lang.model.type.DeclaredType;
033
034import org.microbean.construct.constant.Constables;
035
036import static java.lang.constant.ConstantDescs.BSM_INVOKE;
037import static java.lang.constant.ConstantDescs.CD_Map;
038
039import static java.lang.constant.MethodHandleDesc.ofConstructor;
040
041import static java.util.Collections.unmodifiableMap;
042
043import static java.util.LinkedHashMap.newLinkedHashMap;
044
045import static javax.lang.model.element.ElementKind.ANNOTATION_TYPE;
046
047import static javax.lang.model.util.ElementFilter.methodsIn;
048
049/**
050 * An <strong>experimental</strong> {@link AnnotationMirror} implementation that is partially or wholly synthetic.
051 *
052 * <p>It is possible to create {@link SyntheticAnnotationMirror} instances representing annotations that a Java compiler
053 * will not produce. For example, <a
054 * href="https://docs.oracle.com/javase/specs/jls/se25/html/jls-9.html#jls-9.6.1">annotations cannot refer to each
055 * other, directly or indirectly</a>, but two {@link SyntheticAnnotationMirror}s may do so.</p>
056 *
057 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a>
058 *
059 * @spec https://docs.oracle.com/javase/specs/jls/se25/html/jls-9.html#jls-9.6.1 Java Language Specification, section
060 * 9.6.1
061 */
062public final class SyntheticAnnotationMirror implements AnnotationMirror, Constable {
063
064
065  /*
066   * Instance fields.
067   */
068
069
070  private final TypeElement annotationTypeElement;
071
072  private final Map<ExecutableElement, AnnotationValue> elementValues;
073
074
075  /*
076   * Constructors.
077   */
078
079
080  /**
081   * Creates a new {@link SyntheticAnnotationMirror}.
082   *
083   * @param annotationTypeElement a {@link TypeElement} representing an annotation type; must not be {@code null}; must
084   * return {@link javax.lang.model.element.ElementKind#ANNOTATION_TYPE ANNOTATION_TYPE} from its {@link
085   * Element#getKind() getKind()} method; {@link SyntheticAnnotationTypeElement} implementations are strongly preferred
086   *
087   * @exception NullPointerException if {@code annotationTypeElement} is {@code null}
088   *
089   * @exception IllegalArgumentException if {@code annotationTypeElement} does not return {@link
090   * javax.lang.model.element.ElementKind#ANNOTATION_TYPE ANNOTATION_TYPE} from an invocation of its {@link
091   * Element#getKind() getKind()} method, or if {@code values} has more entries in it than {@code annotationTypeElement}
092   * has {@linkplain Element#getEnclosedElements() anotation elements}
093   *
094   * @see #SyntheticAnnotationMirror(TypeElement, Map)
095   */
096  public SyntheticAnnotationMirror(final TypeElement annotationTypeElement) {
097    this(annotationTypeElement, Map.of());
098  }
099
100  /**
101   * Creates a new {@link SyntheticAnnotationMirror}.
102   *
103   * @param annotationTypeElement a {@link TypeElement} representing an annotation type; must not be {@code null}; must
104   * return {@link javax.lang.model.element.ElementKind#ANNOTATION_TYPE ANNOTATION_TYPE} from its {@link
105   * Element#getKind() getKind()} method; {@link SyntheticAnnotationTypeElement} implementations are strongly preferred
106   *
107   * @param values a {@link Map} of annotation values indexed by annotation element name; must not be {@code null}; must
108   * contain only values that are permissible for annotation elements
109   *
110   * @exception NullPointerException if any argument is {@code null}
111   *
112   * @exception IllegalArgumentException if {@code annotationTypeElement} does not return {@link
113   * javax.lang.model.element.ElementKind#ANNOTATION_TYPE ANNOTATION_TYPE} from an invocation of its {@link
114   * Element#getKind() getKind()} method, or if {@code values} has more entries in it than {@code annotationTypeElement}
115   * has {@linkplain Element#getEnclosedElements() anotation elements}
116   */
117  public SyntheticAnnotationMirror(final TypeElement annotationTypeElement,
118                                   final Map<? extends String, ?> values) {
119    super();
120    if (annotationTypeElement.getKind() != ANNOTATION_TYPE) {
121      throw new IllegalArgumentException("annotationTypeElement: " + annotationTypeElement);
122    }
123    this.annotationTypeElement = annotationTypeElement;
124    final LinkedHashMap<ExecutableElement, AnnotationValue> m = newLinkedHashMap(values.size());
125    final List<? extends ExecutableElement> methods = methodsIn(annotationTypeElement.getEnclosedElements());
126    for (final ExecutableElement e : methods) {
127      final Object value = values.get(e.getSimpleName().toString()); // default value deliberately not included
128      if (value == null) {
129        if (e.getDefaultValue() == null) {
130          // There has to be a value somewhere, or annotationTypeElement or values is illegal
131          throw new IllegalArgumentException("annotationTypeElement: " + annotationTypeElement + "; values: " + values);
132        }
133        // Default values are excluded from the map on purpose, following the contract of
134        // AnnotationValue#getElementValues().
135      } else {
136        m.put(e, value instanceof AnnotationValue av ? av : new SyntheticAnnotationValue(value));
137      }
138    }
139    if (values.size() > m.size()) {
140      throw new IllegalArgumentException("values: " + values);
141    }
142    this.elementValues = m.isEmpty() ? Map.of() : unmodifiableMap(m);
143  }
144
145
146  /*
147   * Instance methods.
148   */
149
150
151  @Override // Constable
152  public final Optional<? extends ConstantDesc> describeConstable() {
153    return this.annotationTypeElement instanceof Constable c ? c.describeConstable() : Optional.<ConstantDesc>empty()
154      .flatMap(elementDesc -> Constables.describe(this.elementValues,
155                                                  SyntheticAnnotationMirror::describeExecutableElement,
156                                                  SyntheticAnnotationMirror::describeAnnotationValue)
157               .map(valuesDesc -> DynamicConstantDesc.of(BSM_INVOKE,
158                                                         ofConstructor(ClassDesc.of(this.getClass().getName()),
159                                                                       ClassDesc.of(TypeElement.class.getName()),
160                                                                       CD_Map),
161                                                         elementDesc,
162                                                         valuesDesc)));
163  }
164
165  @Override // AnnotationMirror
166  public final DeclaredType getAnnotationType() {
167    return (DeclaredType)this.annotationTypeElement.asType();
168  }
169
170  @Override // AnnotationMirror
171  public final Map<? extends ExecutableElement, ? extends AnnotationValue> getElementValues() {
172    return this.elementValues;
173  }
174
175
176  /*
177   * Static methods.
178   */
179
180
181  private static final Optional<? extends ConstantDesc> describeAnnotationValue(final Object v) {
182    return switch (v) {
183    case null -> Optional.empty(); // deliberately not Optional.of(NULL); annotation values cannot be null
184    case Constable c -> c.describeConstable();
185    case ConstantDesc cd -> Optional.of(cd);
186    case List<?> l -> Constables.describe(l, e -> e instanceof Constable c ? c.describeConstable() : Optional.empty());
187    default -> Optional.empty();
188    };
189  }
190
191  private static final Optional<? extends ConstantDesc> describeExecutableElement(final ExecutableElement e) {
192    return switch (e) {
193    case null -> throw new IllegalStateException();
194    case Constable c -> c.describeConstable();
195    case ConstantDesc cd -> Optional.of(cd);
196    default -> Optional.empty();
197    };
198  }
199
200}