001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
002 *
003 * Copyright © 2025 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.annotation.Annotation;
017
018import java.lang.constant.ClassDesc;
019import java.lang.constant.Constable;
020import java.lang.constant.ConstantDesc;
021import java.lang.constant.DynamicConstantDesc;
022import java.lang.constant.MethodTypeDesc;
023
024import java.util.LinkedHashMap;
025import java.util.List;
026import java.util.Map;
027import java.util.Map.Entry;
028import java.util.Optional;
029
030import java.util.function.Function;
031
032import javax.lang.model.element.AnnotationMirror;
033import javax.lang.model.element.AnnotationValue;
034import javax.lang.model.element.AnnotationValueVisitor;
035import javax.lang.model.element.Element;
036import javax.lang.model.element.ExecutableElement;
037import javax.lang.model.element.TypeElement;
038import javax.lang.model.element.VariableElement;
039
040import javax.lang.model.type.DeclaredType;
041import javax.lang.model.type.TypeMirror;
042
043import org.microbean.construct.constant.Constables;
044
045import static java.lang.constant.ConstantDescs.BSM_INVOKE;
046import static java.lang.constant.ConstantDescs.CD_Map;
047import static java.lang.constant.ConstantDescs.CD_Object;
048import static java.lang.constant.ConstantDescs.NULL;
049
050import static java.lang.constant.DirectMethodHandleDesc.Kind.INTERFACE_STATIC;
051
052import static java.lang.constant.MethodHandleDesc.ofConstructor;
053
054import static java.util.Arrays.fill;
055
056import static java.util.Collections.unmodifiableMap;
057
058import static java.util.LinkedHashMap.newLinkedHashMap;
059
060import static java.util.Objects.requireNonNull;
061
062import static javax.lang.model.element.ElementKind.ANNOTATION_TYPE;
063import static javax.lang.model.element.ElementKind.METHOD;
064
065/**
066 * An <strong>experimental</strong> {@link AnnotationMirror} implementation that is partially or wholly synthetic.
067 *
068 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a>
069 */
070public final class SyntheticAnnotationMirror implements AnnotationMirror, Constable {
071
072  private final TypeElement annotationTypeElement;
073
074  private final Map<ExecutableElement, AnnotationValue> elementValues;
075
076  /**
077   * Creates a new {@link SyntheticAnnotationMirror}.
078   *
079   * @param annotationTypeElement a {@link TypeElement} representing an annotation type; must not be {@code null}; must
080   * return {@link javax.lang.model.element.ElementKind#ANNOTATION_TYPE ANNOTATION_TYPE} from its {@link
081   * Element#getKind() getKind()} method; {@link SyntheticAnnotationTypeElement} implementations are strongly preferred
082   *
083   * @param values a {@link Map} of annotation values indexed by annotation element name; must not be {@code null}; must
084   * contain only values that are permissible for annotation elements
085   *
086   * @exception NullPointerException if any argument is {@code null}
087   *
088   * @exception IllegalArgumentException if {@code annotationTypeElement} does not return {@link
089   * javax.lang.model.element.ElementKind#ANNOTATION_TYPE ANNOTATION_TYPE} from an invocation of its {@link
090   * Element#getKind() getKind()} method, or if {@code values} has more entries in it than {@code annotationTypeElement}
091   * has {@linkplain Element#getEnclosedElements() anotation elements}
092   */
093  public SyntheticAnnotationMirror(final TypeElement annotationTypeElement,
094                                   final Map<? extends String, ?> values) {
095    super();
096    if (annotationTypeElement.getKind() != ANNOTATION_TYPE) {
097      throw new IllegalArgumentException("annotationTypeElement: " + annotationTypeElement);
098    }
099    this.annotationTypeElement = annotationTypeElement;
100    final LinkedHashMap<ExecutableElement, AnnotationValue> m = newLinkedHashMap(values.size());
101    for (final Element e : annotationTypeElement.getEnclosedElements()) {
102      if (e.getKind() == METHOD) {
103        final Object value = values.get(e.getSimpleName().toString());
104        if (value != null) {
105          m.put((ExecutableElement)e, value instanceof AnnotationValue av ? av : new SyntheticAnnotationValue(value));
106        }
107      }
108    }
109    if (values.size() > m.size()) {
110      throw new IllegalArgumentException("values: " + values);
111    }
112    this.elementValues = unmodifiableMap(m);
113  }
114
115
116  /*
117   * Instance methods.
118   */
119
120  @Override // Constable
121  public final Optional<? extends ConstantDesc> describeConstable() {
122    return this.annotationTypeElement instanceof Constable c ? c.describeConstable() : Optional.<ConstantDesc>empty()
123      .flatMap(elementDesc -> Constables.describe(this.elementValues,
124                                                  SyntheticAnnotationMirror::describeExecutableElement,
125                                                  SyntheticAnnotationMirror::describeAnnotationValue)
126               .map(valuesDesc -> DynamicConstantDesc.of(BSM_INVOKE,
127                                                         ofConstructor(ClassDesc.of(this.getClass().getName()),
128                                                                       ClassDesc.of(TypeElement.class.getName()),
129                                                                       CD_Map),
130                                                         elementDesc,
131                                                         valuesDesc)));
132  }
133  
134
135  @Override // AnnotationMirror
136  public final DeclaredType getAnnotationType() {
137    return (DeclaredType)this.annotationTypeElement.asType();
138  }
139
140  @Override // AnnotationMirror
141  public final Map<? extends ExecutableElement, ? extends AnnotationValue> getElementValues() {
142    return this.elementValues;
143  }
144
145  /**
146   * An <strong>experimental</strong> {@link AnnotationValue} implementation that is partially or wholly synthetic.
147   *
148   * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a>
149   */
150  public static final class SyntheticAnnotationValue implements AnnotationValue, Constable {
151
152    // Will be one of:
153    //
154    // * AnnotationMirror
155    // * List<SyntheticAnnotationValue>
156    // * TypeMirror
157    // * VariableElement (ENUM_CONSTANT)
158    // * Boolean
159    // * Byte
160    // * Character
161    // * Double
162    // * Float
163    // * Integer
164    // * Long
165    // * Short
166    // * String
167    private final Object value;
168
169    /**
170     * Creates a new {@link SyntheticAnnotationValue}.
171     *
172     * @param value the value; must not be {@code null}; must be a legal {@link AnnotationValue} type
173     *
174     * @exception NullPointerException if {@code value} is {@code null}
175     *
176     * @exception IllegalArgumentException if {@code value} is not a legal {@link AnnotationValue} type
177     *
178     * @see AnnotationValue
179     */
180    public SyntheticAnnotationValue(final Object value) {
181      super();
182      this.value = value(value);
183    }
184
185    @Override // AnnotationValue
186    @SuppressWarnings("unchecked")
187    public final <R, P> R accept(final AnnotationValueVisitor<R, P> v, final P p) {
188      return switch (this.getValue()) {
189      case null               -> v.visitUnknown(this, p); // ...or AssertionError?
190      case AnnotationMirror a -> v.visitAnnotation(a, p);
191      case List<?> l          -> v.visitArray((List<? extends AnnotationValue>)l, p);
192      case TypeMirror t       -> v.visitType(t, p);
193      case VariableElement e  -> v.visitEnumConstant(e, p);
194      case Boolean b          -> v.visitBoolean(b, p);
195      case Byte b             -> v.visitByte(b, p);
196      case Character c        -> v.visitChar(c, p);
197      case Double d           -> v.visitDouble(d, p);
198      case Float f            -> v.visitFloat(f, p);
199      case Integer i          -> v.visitInt(i, p);
200      case Long l             -> v.visitLong(l, p);
201      case Short s            -> v.visitShort(s, p);
202      case String s           -> v.visitString(s, p);
203      default                 -> v.visitUnknown(this, p);
204      };
205    }
206
207    @Override // Constable
208    public final Optional<? extends ConstantDesc> describeConstable() {
209      return this.value instanceof Constable c ? c.describeConstable() : Optional.<ConstantDesc>empty()
210        .map(valueDesc -> DynamicConstantDesc.of(BSM_INVOKE,
211                                                 ofConstructor(ClassDesc.of(this.getClass().getName()),
212                                                               CD_Object),
213                                                 valueDesc));
214    }
215
216    @Override // Object
217    public final boolean equals(final Object other) {
218      return this == other || switch (other) {
219      case null -> false;
220      case SyntheticAnnotationValue sav when this.getClass() == sav.getClass() -> this.value.equals(sav.value);
221      default -> false;
222      };
223    }
224
225    @Override // AnnotationValue
226    public final Object getValue() {
227      return this.value;
228    }
229
230    @Override // Object
231    public final int hashCode() {
232      return this.value.hashCode();
233    }
234
235    @Override // Object
236    public final String toString() {
237      return this.value.toString();
238    }
239
240    private static final Object value(final Object value) {
241      return switch (value) {
242      case null               -> throw new NullPointerException("value");
243
244      case AnnotationValue av -> av.getValue(); // not part of the spec; just good hygiene
245      case List<?> l          -> l.stream().map(SyntheticAnnotationValue::new).toList();
246
247      case TypeMirror t       -> switch (t.getKind()) {
248      case BOOLEAN, BYTE, CHAR, DECLARED, DOUBLE, FLOAT, INT, LONG, SHORT, VOID /* I think? */ -> t;
249      default -> throw new IllegalArgumentException("value: " + value);
250      };
251
252      case VariableElement e  -> switch (e.getKind()) {
253      case ENUM_CONSTANT -> e;
254      default -> throw new IllegalArgumentException("value: " + value);
255      };
256
257      case AnnotationMirror a -> a;
258      case Boolean b          -> b;
259      case Byte b             -> b;
260      case Character c        -> c;
261      case Double d           -> d;
262      case Float f            -> f;
263      case Integer i          -> i;
264      case Long l             -> l;
265      case Short s            -> s;
266      case String s           -> s;
267      default                 -> throw new IllegalArgumentException("value: " + value);
268      };
269    }
270
271  }
272
273  private static final Optional<? extends ConstantDesc> describeAnnotationValue(final Object v) {
274    return switch (v) {
275    case null -> Optional.empty(); // deliberately not Optional.of(NULL); annotation values cannot be null
276    case Constable c -> c.describeConstable();
277    case ConstantDesc cd -> Optional.of(cd);
278    case List<?> l -> Constables.describe(l, e -> e instanceof Constable c ? c.describeConstable() : Optional.empty());
279    default -> Optional.empty();
280    };
281  }
282
283  private static final Optional<? extends ConstantDesc> describeExecutableElement(final ExecutableElement e) {
284    return switch (e) {
285    case null -> throw new IllegalStateException();
286    case Constable c -> c.describeConstable();
287    case ConstantDesc cd -> Optional.of(cd);
288    default -> Optional.empty();
289    };
290  }
291
292}