001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
002 *
003 * Copyright © 2024–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;
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;
022
023import java.util.List;
024import java.util.Optional;
025
026import java.util.concurrent.CopyOnWriteArrayList;
027
028import java.util.function.Supplier;
029
030import javax.lang.model.AnnotatedConstruct;
031
032import javax.lang.model.element.AnnotationMirror;
033import javax.lang.model.element.Element;
034import javax.lang.model.element.QualifiedNameable;
035
036import javax.lang.model.type.TypeMirror;
037
038import org.microbean.construct.constant.Constables;
039
040import org.microbean.construct.element.UniversalAnnotation;
041import org.microbean.construct.element.UniversalElement;
042
043import org.microbean.construct.type.UniversalType;
044
045import static java.lang.constant.ConstantDescs.BSM_INVOKE;
046import static java.lang.constant.ConstantDescs.CD_List;
047
048import static java.lang.constant.MethodHandleDesc.ofConstructor;
049
050import static java.util.Objects.requireNonNull;
051
052/**
053 * An abstract implementation of {@link AnnotatedConstruct} from which only {@link UniversalElement} and {@link
054 * UniversalType} descend.
055 *
056 * @param <T> a type of {@link AnnotatedConstruct}, which may be only either {@link Element} or {@link TypeMirror}
057 *
058 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a>
059 *
060 * @see AnnotatedConstruct
061 *
062 * @see UniversalElement
063 *
064 * @see UniversalType
065 */
066public abstract sealed class UniversalConstruct<T extends AnnotatedConstruct>
067  implements AnnotatedConstruct, Constable
068  permits UniversalElement, UniversalType {
069
070
071  /*
072   * Static fields.
073   */
074
075
076  private static final Annotation[] EMPTY_ANNOTATION_ARRAY = new Annotation[0];
077
078
079  /*
080   * Instance fields.
081   */
082
083
084  private final PrimordialDomain domain;
085
086  // Eventually this should become a lazy constant/stable value
087  // volatile not needed
088  private Supplier<? extends T> delegateSupplier;
089
090  // Eventually this should become a lazy constant/stable value
091  private volatile String s;
092
093  // Eventually this should become a lazy constant/stable value
094  private volatile CopyOnWriteArrayList<AnnotationMirror> annotations;
095
096
097  /*
098   * Constructors.
099   */
100
101
102  /**
103   * Creates a new {@link AnnotatedConstruct}.
104   *
105   * @param delegate a delegate to which operations are delegated; must not be {@code null}
106   *
107   * @param domain a {@link PrimordialDomain} representing the construct domain from which the supplied {@code
108   * delegate} is presumed to have originated; must not be {@code null}
109   *
110   * @exception NullPointerException if either argument is {@code null}
111   *
112   * @see #UniversalConstruct(List, AnnotatedConstruct, PrimordialDomain)
113   */
114  protected UniversalConstruct(final T delegate, final PrimordialDomain domain) {
115    this(null, delegate, domain);
116  }
117
118  /**
119   * Creates a new {@link AnnotatedConstruct}.
120   *
121   * @param annotations a {@link List} of {@link AnnotationMirror} instances representing annotations, often
122   * synthetic, that this {@link UniversalConstruct} should bear; may be {@code null} in which case only the annotations
123   * from the supplied {@code delegate} will be used
124   * @param delegate a delegate to which operations are delegated; must not be {@code null}
125   *
126   * @param domain a {@link PrimordialDomain} representing the construct domain from which the supplied {@code
127   * delegate} is presumed to have originated; must not be {@code null}
128   *
129   * @exception NullPointerException if any argument is {@code null}
130   */
131  @SuppressWarnings("try")
132  protected UniversalConstruct(final List<? extends AnnotationMirror> annotations,
133                               final T delegate,
134                               final PrimordialDomain domain) {
135    super();
136    this.domain = requireNonNull(domain, "domain");
137    if (annotations != null) {
138      this.annotations = new CopyOnWriteArrayList<>(annotations);
139    }
140    final T unwrappedDelegate = unwrap(requireNonNull(delegate, "delegate"));
141    if (unwrappedDelegate == delegate) {
142      this.delegateSupplier = () -> {
143        try (var lock = domain.lock()) {
144          // No unwrapping happened so do symbol completion early; most common case.
145          if (unwrappedDelegate instanceof Element) {
146            ((Element)unwrappedDelegate).getModifiers();
147          } else {
148            ((TypeMirror)unwrappedDelegate).getKind();
149          }
150          this.delegateSupplier = () -> unwrappedDelegate;
151        }
152        return unwrappedDelegate;
153      };
154    } else {
155      assert delegate instanceof UniversalConstruct;
156      // Symbol completion already happened because unwrapping actually happened
157      this.delegateSupplier = () -> unwrappedDelegate;
158    }
159  }
160
161
162  /*
163   * Instance methods.
164   */
165
166
167  /**
168   * Returns the delegate to which operations are delegated.
169   *
170   * <p>The delegate is guaranteed not to be an instance of {@link UniversalConstruct}.</p>
171   *
172   * @return a non-{@code null} delegate
173   *
174   * @see Element
175   *
176   * @see TypeMirror
177   */
178  public final T delegate() {
179    final T delegate = this.delegateSupplier.get();
180    assert !(delegate instanceof UniversalConstruct);
181    return delegate;
182  }
183
184  @Override // Constable
185  public final Optional<? extends ConstantDesc> describeConstable() {
186    final PrimordialDomain primordialDomain = this.domain();
187    if (domain instanceof Domain d && d instanceof Constable dc) {
188      final T delegate = this.delegate();
189      final List<AnnotationMirror> annotations = this.annotations; // volatile read; may be null and that's OK
190      return Constables.describe(delegate, d)
191        .flatMap(delegateDesc -> Constables.describe(annotations)
192                 .map(annosDesc -> DynamicConstantDesc.of(BSM_INVOKE,
193                                                          ofConstructor(ClassDesc.of(this.getClass().getName()),
194                                                                        CD_List,
195                                                                        ClassDesc.of(delegate instanceof TypeMirror ? TypeMirror.class.getName() : Element.class.getName()),
196                                                                        ClassDesc.of(PrimordialDomain.class.getName())),
197                                                          annosDesc,
198                                                          delegateDesc,
199                                                          dc.describeConstable().orElseThrow())));
200    }
201    return Optional.empty();
202  }
203
204  /**
205   * Returns the {@link PrimordialDomain} supplied at construction time.
206   *
207   * @return the non-{@code null} {@link PrimordialDomain} supplied at construction time
208   */
209  public final PrimordialDomain domain() {
210    return this.domain;
211  }
212
213  @Override // Object
214  public final boolean equals(final Object other) {
215    // Interesting; equality does not cause symbol completion. See:
216    //
217    // https://github.com/openjdk/jdk/blob/jdk-26%2B25/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Type.java#L553-L559
218    // (the only type that overrides this is ArrayType; see
219    // https://github.com/openjdk/jdk/blob/jdk-26%2B25/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Type.java#L1402-L1406)
220    //
221    // https://github.com/openjdk/jdk/blob/jdk-26%2B25/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symbol.java
222    // (Symbol (Element) doesn't override it at all.)
223    return this == other || switch (other) {
224    case null -> false;
225    case UniversalConstruct<?> uc when this.getClass() == uc.getClass() -> this.delegate().equals(uc.delegate());
226    default -> false;
227    };
228  }
229
230  /**
231   * Returns a non-{@code null}, determinate, <strong>mutable</strong>, thread-safe {@link List} of {@link
232   * AnnotationMirror} instances representing the annotations to be considered <dfn>directly present</dfn> on this
233   * {@link UniversalConstruct} implementation.
234   *
235   * @return a non-{@code null}, determinate, <strong>mutable</strong> thread-safe {@link List} of {@link
236   * AnnotationMirror}s
237   *
238   * @see AnnotatedConstruct#getAnnotationMirrors()
239   */
240  @Override // AnnotatedConstruct
241  @SuppressWarnings("try")
242  public final List<AnnotationMirror> getAnnotationMirrors() {
243    CopyOnWriteArrayList<AnnotationMirror> annotations = this.annotations; // volatile read
244    if (annotations == null) {
245      try (var lock = this.domain().lock()) {
246        this.annotations = annotations = // volatile write
247          new CopyOnWriteArrayList<>(UniversalAnnotation.of(this.delegate().getAnnotationMirrors(), this.domain()));
248      }
249    }
250    return annotations;
251  }
252
253  /**
254   * Makes a <strong>best effort</strong> to return an {@link Annotation} of the appropriate type <dfn>present</dfn> on
255   * this {@link UniversalConstruct} implementation.
256   *
257   * <p>See the specification for the {@link AnnotatedConstruct#getAnnotation(Class)} method for important details.</p>
258   *
259   * <p>{@link UniversalConstruct} implementations deliberately permit modification of their {@linkplain
260   * #getAnnotationMirrors() annotations}. Consequently, this override first checks to see if there is at least one
261   * {@link AnnotationMirror} whose {@linkplain AnnotationMirror#getAnnotationType() annotation type} is declared by a
262   * {@link javax.lang.model.element.TypeElement} whose {@linkplain
263   * javax.lang.model.element.TypeElement#getQualifiedName() qualified name} is {@linkplain
264   * javax.lang.model.element.Name#contentEquals(CharSequence) equal to} the {@linkplain Class#getCanonicalName()
265   * canonical name} of the supplied {@link Class}. If there is, then the {@link AnnotatedConstruct#getAnnotation(Class)
266   * getAnnotation(Class)} method is invoked on the {@linkplain #delegate() delegate} and its result is
267   * returned. Otherwise, {@code null} is returned.</p>
268   *
269   * <p>There are circumstances where the {@link Annotation} returned by this method may not accurately reflect a
270   * synthetic annotation added to this {@link AnnotatedConstruct} implementation's {@linkplain #getAnnotationMirrors()
271   * annotations}.</p>
272   *
273   * <p>In general, the use of this method is discouraged.</p>
274   *
275   * @param annotationType a {@link Class} that is an annotation interface; must not be {@code null}
276   *
277   * @return an appropriate {@link Annotation}, or {@code null}
278   *
279   * @exception NullPointerException if {@code annotationType} is {@code null}
280   *
281   * @see AnnotatedConstruct#getAnnotation(Class)
282   *
283   * @deprecated The use of this method is discouraged.
284   */
285  @Deprecated
286  @Override // AnnotatedConstruct
287  @SuppressWarnings("try")
288  public final <A extends Annotation> A getAnnotation(final Class<A> annotationType) {
289    if (!annotationType.isAnnotation()) {
290      return null;
291    }
292    final String canonicalName = annotationType.getCanonicalName();
293    for (final AnnotationMirror a : this.getAnnotationMirrors()) {
294      if (((QualifiedNameable)a.getAnnotationType().asElement()).getQualifiedName().contentEquals(canonicalName)) {
295        // TODO: is this lock actually needed, given how delegateSupplier works?
296        try (var lock = this.domain().lock()) {
297          return this.delegate().getAnnotation(annotationType);
298        }
299      }
300    }
301    return null;
302
303  }
304
305  /**
306   * Makes a <strong>best effort</strong> to return an array of {@link Annotation}s of the appropriate type
307   * <dfn>associated</dfn> with this {@link UniversalConstruct} implementation.
308   *
309   * <p>See the specification for the {@link AnnotatedConstruct#getAnnotationsByType(Class)} method for important
310   * details.</p>
311   *
312   * <p>{@link UniversalConstruct} implementations deliberately permit modification of their {@linkplain
313   * #getAnnotationMirrors() annotations}. Consequently, this override first checks to see if there is at least one
314   * {@link AnnotationMirror} whose {@linkplain AnnotationMirror#getAnnotationType() annotation type} is declared by a
315   * {@link javax.lang.model.element.TypeElement} whose {@linkplain
316   * javax.lang.model.element.TypeElement#getQualifiedName() qualified name} is {@linkplain
317   * javax.lang.model.element.Name#contentEquals(CharSequence) equal to} the {@linkplain Class#getCanonicalName()
318   * canonical name} of the supplied {@link Class}. If there is, then the {@link
319   * AnnotatedConstruct#getAnnotationsByType(Class) getAnnotationsByType(Class)} method is invoked on the {@linkplain
320   * #delegate() delegate} and its result is returned. Otherwise, an empty array is returned.</p>
321   *
322   * <p>There are circumstances where the {@link Annotation} array returned by this method may not accurately reflect
323   * synthetic annotations added to this {@link AnnotatedConstruct} implementation's {@linkplain #getAnnotationMirrors()
324   * annotations}.</p>
325   *
326   * <p>In general, the use of this method is discouraged.</p>
327   *
328   * @param annotationType a {@link Class} that is an annotation interface; must not be {@code null}
329   *
330   * @return an appropriate {@link Annotation}, or {@code null}
331   *
332   * @exception NullPointerException if {@code annotationType} is {@code null}
333   *
334   * @see AnnotatedConstruct#getAnnotation(Class)
335   *
336   * @deprecated The use of this method is discouraged.
337   */
338  @Deprecated
339  @Override // AnnotatedConstruct
340  @SuppressWarnings({"try", "unchecked"})
341  public final <A extends Annotation> A[] getAnnotationsByType(final Class<A> annotationType) {
342    if (!annotationType.isAnnotation()) {
343      return (A[])EMPTY_ANNOTATION_ARRAY;
344    }
345    final String canonicalName = annotationType.getCanonicalName();
346    for (final AnnotationMirror a : this.getAnnotationMirrors()) {
347      if (((QualifiedNameable)a.getAnnotationType().asElement()).getQualifiedName().contentEquals(canonicalName)) {
348        // TODO: is this lock actually needed, given how delegateSupplier works?
349        try (var lock = this.domain().lock()) {
350          return this.delegate().getAnnotationsByType(annotationType);
351        }
352      }
353    }
354    return (A[])EMPTY_ANNOTATION_ARRAY;
355  }
356
357  @Override // Object
358  public final int hashCode() {
359    // Interesting; hashCode doesn't cause symbol completion. See:
360    //
361    // https://github.com/openjdk/jdk/blob/jdk-26%2B25/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Type.java#L565-L568
362    // (AnnoConstruct doesn't define it so super.hashCode() is Object.hashCode())
363    //
364    // https://github.com/openjdk/jdk/blob/jdk-26%2B25/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symbol.java
365    // (Symbol (Element) doesn't override it at all.)
366    return this.delegate().hashCode();
367  }
368
369  @Override // Object
370  @SuppressWarnings("try")
371  public final String toString() {
372    String s = this.s; // volatile read
373    if (s == null) {
374      try (var lock = this.domain().lock()) {
375        s = this.s = this.delegate().toString(); // volatile write, read
376      }
377    }
378    return s;
379  }
380
381
382  /*
383   * Static methods.
384   */
385
386
387  /**
388   * <dfn>Unwraps</dfn> the supplied {@link AnnotatedConstruct} implementation such that the returned value is not an
389   * instance of {@link UniversalConstruct}.
390   *
391   * @param <T> an {@link AnnotatedConstruct} subtype (possibly {@link UniversalElement} or {@link UniversalType})
392   *
393   * @param t an {@link AnnotatedConstruct}; may be {@code null}
394   *
395   * @return an object of the appropriate type that is guaranteed not to be an instance of {@link UniversalConstruct}
396   *
397   * @see #delegate()
398   */
399  @SuppressWarnings("unchecked")
400  public static final <T extends AnnotatedConstruct> T unwrap(T t) {
401    while (t instanceof UniversalConstruct<?> uc) {
402      t = (T)uc.delegate();
403    }
404    return t;
405  }
406
407}