001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
002 *
003 * Copyright © 2024–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;
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.MethodHandleDesc;
023
024import java.util.List;
025import java.util.Objects;
026import java.util.Optional;
027
028import java.util.function.Supplier;
029
030import javax.lang.model.AnnotatedConstruct;
031
032import javax.lang.model.element.Element;
033
034import javax.lang.model.type.TypeMirror;
035
036import org.microbean.construct.constant.Constables;
037
038import org.microbean.construct.element.UniversalAnnotation;
039import org.microbean.construct.element.UniversalElement;
040
041import org.microbean.construct.type.UniversalType;
042
043import static java.lang.constant.ConstantDescs.BSM_INVOKE;
044
045/**
046 * An abstract implementation of {@link AnnotatedConstruct} from which only {@link UniversalElement} and {@link
047 * UniversalType} descend.
048 *
049 * @param <T> a type of {@link AnnotatedConstruct}, which may be only either {@link Element} or {@link TypeMirror}
050 *
051 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a>
052 *
053 * @see AnnotatedConstruct
054 *
055 * @see UniversalElement
056 *
057 * @see UniversalType
058 */
059public abstract sealed class UniversalConstruct<T extends AnnotatedConstruct> implements AnnotatedConstruct, Constable
060  permits UniversalElement, UniversalType {
061
062
063  /*
064   * Instance fields.
065   */
066
067
068  private final Domain domain;
069
070  // Eventually this should become a lazy constant/stable value
071  // volatile not needed
072  private Supplier<? extends T> delegateSupplier;
073
074  // Eventually this should become a lazy constant/stable value
075  private volatile String s;
076
077
078  /*
079   * Constructors.
080   */
081
082
083  /**
084   * Creates a new {@link AnnotatedConstruct}.
085   *
086   * @param delegate a delegate to which operations are delegated; must not be {@code null}
087   *
088   * @param domain; a {@link Domain} from which the supplied {@code delegate} is presumed to have originated; must not
089   * be {@code null}
090   *
091   * @exception NullPointerException if either argument is {@code null}
092   */
093  @SuppressWarnings("try")
094  protected UniversalConstruct(final T delegate, final Domain domain) {
095    super();
096    this.domain = Objects.requireNonNull(domain, "domain");
097    final T unwrappedDelegate = unwrap(Objects.requireNonNull(delegate, "delegate"));
098    if (unwrappedDelegate == delegate) {
099      // No unwrapping happened so do symbol completion early; most common case.
100      final Runnable symbolCompleter = switch (unwrappedDelegate) {
101      case Element e    -> e::getModifiers;
102      case TypeMirror t -> t::getKind;
103      default           -> UniversalConstruct::doNothing;
104      };
105      this.delegateSupplier = () -> {
106        try (var lock = domain.lock()) {
107          symbolCompleter.run();
108          this.delegateSupplier = () -> unwrappedDelegate;
109        }
110        return unwrappedDelegate;
111      };
112    } else {
113      assert delegate instanceof UniversalConstruct<?>;
114      // Symbol completion already happened
115      this.delegateSupplier = () -> unwrappedDelegate;
116    }
117  }
118
119
120  /*
121   * Instance methods.
122   */
123
124
125  /**
126   * Returns the delegate to which operations are delegated.
127   *
128   * <p>The delegate is guaranteed not to be an instance of {@link UniversalConstruct}.</p>
129   *
130   * @return a non-{@code null} delegate
131   *
132   * @see Element
133   *
134   * @see TypeMirror
135   */
136  public final T delegate() {
137    final T delegate = this.delegateSupplier.get();
138    assert !(delegate instanceof UniversalConstruct);
139    return delegate;
140  }
141
142  @Override // Constable
143  public final Optional<? extends ConstantDesc> describeConstable() {
144    final T delegate = this.delegate();
145    final Domain domain = this.domain();
146    return Constables.describe(delegate, domain)
147      .map(delegateDesc -> DynamicConstantDesc.of(BSM_INVOKE,
148                                                  MethodHandleDesc.ofConstructor(ClassDesc.of(this.getClass().getName()),
149                                                                                 ClassDesc.of(delegate instanceof TypeMirror ? TypeMirror.class.getName() : Element.class.getName()),
150                                                                                 ClassDesc.of(Domain.class.getName())),
151                                                   delegateDesc,
152                                                   ((Constable)domain).describeConstable().orElseThrow()));
153  }
154
155  /**
156   * Returns the {@link Domain} supplied at construction time.
157   *
158   * @return the non-{@code null} {@link Domain} supplied at construction time
159   */
160  public final Domain domain() {
161    return this.domain;
162  }
163
164  @Override // Object
165  public final boolean equals(final Object other) {
166    // Interesting; equality does not cause symbol completion. See:
167    //
168    // https://github.com/openjdk/jdk/blob/jdk-26%2B25/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Type.java#L553-L559
169    // (the only type that overrides this is ArrayType; see
170    // https://github.com/openjdk/jdk/blob/jdk-26%2B25/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Type.java#L1402-L1406)
171    //
172    // https://github.com/openjdk/jdk/blob/jdk-26%2B25/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symbol.java
173    // (Symbol (Element) doesn't override it at all.)
174    return this == other || switch (other) {
175    case null -> false;
176    case UniversalConstruct<?> uc when this.getClass() == uc.getClass() -> this.delegate().equals(uc.delegate());
177    default -> false;
178    };
179  }
180
181  @Override // AnnotatedConstruct
182  public final List<? extends UniversalAnnotation> getAnnotationMirrors() {
183    return UniversalAnnotation.of(this.delegate().getAnnotationMirrors(), this.domain());
184  }
185
186  @Override // AnnotatedConstruct
187  @SuppressWarnings("try")
188  public final <A extends Annotation> A getAnnotation(final Class<A> annotationType) {
189    // TODO: is this lock actually needed, given how delegateSupplier works?
190    try (var lock = this.domain().lock()) {
191      return this.delegate().getAnnotation(annotationType);
192    }
193  }
194
195  @Override // AnnotatedConstruct
196  @SuppressWarnings("try")
197  public final <A extends Annotation> A[] getAnnotationsByType(final Class<A> annotationType) {
198    // TODO: is this lock actually needed, given how delegateSupplier works?
199    try (var lock = this.domain().lock()) {
200      return this.delegate().getAnnotationsByType(annotationType);
201    }
202  }
203
204  @Override // Object
205  public final int hashCode() {
206    // Interesting; hashCode doesn't cause symbol completion. See:
207    //
208    // https://github.com/openjdk/jdk/blob/jdk-26%2B25/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Type.java#L565-L568
209    // (AnnoConstruct doesn't define it so super.hashCode() is Object.hashCode())
210    //
211    // https://github.com/openjdk/jdk/blob/jdk-26%2B25/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symbol.java
212    // (Symbol (Element) doesn't override it at all.)
213    return this.delegate().hashCode();
214  }
215
216  @Override // Object
217  @SuppressWarnings("try")
218  public final String toString() {
219    String s = this.s; // volatile read
220    if (s == null) {
221      try (var lock = this.domain().lock()) {
222        s = this.s = this.delegate().toString(); // volatile write, read
223      }
224      assert s != null;
225    }
226    return s;
227  }
228
229
230  /*
231   * Static methods.
232   */
233
234
235  /**
236   * <dfn>Unwraps</dfn> the supplied {@link AnnotatedConstruct} implementation such that the returned value is not an
237   * instance of {@link UniversalConstruct}.
238   *
239   * @param <T> an {@link AnnotatedConstruct} subtype (possibly {@link UniversalElement} or {@link UniversalType})
240   *
241   * @param t an {@link AnnotatedConstruct}; may be {@code null}
242   *
243   * @return an object of the appropriate type that is guaranteed not to be an instance of {@link UniversalConstruct}
244   *
245   * @see #delegate()
246   */
247  @SuppressWarnings("unchecked")
248  public static final <T extends AnnotatedConstruct> T unwrap(T t) {
249    while (t instanceof UniversalConstruct<?> uc) {
250      t = (T)uc.delegate();
251    }
252    return t;
253  }
254
255  private static final void doNothing() {}
256
257}