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.element;
015
016import java.util.ArrayList;
017import java.util.Collection;
018import java.util.Collections;
019import java.util.List;
020import java.util.Optional;
021import java.util.Set;
022
023import java.util.function.Supplier;
024
025import javax.lang.model.element.AnnotationMirror;
026import javax.lang.model.element.Element;
027import javax.lang.model.element.ElementKind;
028import javax.lang.model.element.ElementVisitor;
029import javax.lang.model.element.ExecutableElement;
030import javax.lang.model.element.Modifier;
031import javax.lang.model.element.ModuleElement;
032import javax.lang.model.element.NestingKind;
033import javax.lang.model.element.PackageElement;
034import javax.lang.model.element.Parameterizable;
035import javax.lang.model.element.QualifiedNameable;
036import javax.lang.model.element.RecordComponentElement;
037import javax.lang.model.element.TypeElement;
038import javax.lang.model.element.TypeParameterElement;
039import javax.lang.model.element.VariableElement;
040
041import javax.lang.model.type.TypeKind;
042import javax.lang.model.type.TypeMirror;
043
044import org.microbean.construct.UniversalConstruct;
045import org.microbean.construct.PrimordialDomain;
046
047import org.microbean.construct.type.UniversalType;
048
049/**
050 * An {@link Element} and {@link UniversalConstruct} implementation.
051 *
052 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a>
053 *
054 * @see Element#getKind()
055 *
056 * @see UniversalConstruct
057 */
058@SuppressWarnings("preview")
059public final class UniversalElement
060  extends UniversalConstruct<Element, UniversalElement>
061  implements ExecutableElement,
062             ModuleElement,
063             PackageElement,
064             RecordComponentElement,
065             TypeElement,
066             TypeParameterElement,
067             VariableElement {
068
069  // volatile not needed
070  private Supplier<? extends List<? extends UniversalElement>> enclosedElementsSupplier;
071
072  /**
073   * Creates a new {@link UniversalElement}.
074   *
075   * @param delegate an {@link Element} to which operations will be delegated; must not be {@code null}
076   *
077   * @param domain a {@link PrimordialDomain} from which the supplied {@code delegate} is presumed to have originated;
078   * must not be {@code null}
079   *
080   * @exception NullPointerException if either argument is {@code null}
081   */
082  public UniversalElement(final Element delegate, final PrimordialDomain domain) {
083    this(delegate, null, domain);
084  }
085
086  /**
087   * Creates a new {@link UniversalElement}.
088   *
089   * @param delegate an {@link Element} to which operations will be delegated; must not be {@code null}
090   *
091   * @param annotations a {@link List} of {@link AnnotationMirror} instances representing annotations, often synthetic,
092   * that this {@link UniversalElement} should bear; may be {@code null} in which case only the annotations from the
093   * supplied {@code delegate} will be used
094   *
095   * @param domain a {@link PrimordialDomain} from which the supplied {@code delegate} is presumed to have originated;
096   * must not be {@code null}
097   *
098   * @exception NullPointerException if any argument is {@code null}
099   *
100   * @see #delegate()
101   */
102  @SuppressWarnings("try")
103  private UniversalElement(final Element delegate,
104                           final List<? extends AnnotationMirror> annotations,
105                           final PrimordialDomain domain) {
106    super(delegate, annotations, domain);
107    this.enclosedElementsSupplier = () -> {
108      final List<? extends UniversalElement> ees;
109      try (var lock = domain.lock()) {
110        ees = this.wrap(this.delegate().getEnclosedElements());
111        this.enclosedElementsSupplier = () -> ees;
112      }
113      return ees;
114    };
115  }
116
117  @Override // Element
118  public final <R, P> R accept(final ElementVisitor<R, P> v, final P p) {
119    return switch (this.getKind()) {
120    case
121      ANNOTATION_TYPE,
122      CLASS,
123      ENUM,
124      INTERFACE,
125      RECORD                 -> v.visitType(this, p);
126    case TYPE_PARAMETER      -> v.visitTypeParameter(this, p);
127    case
128      BINDING_VARIABLE,
129      ENUM_CONSTANT,
130      EXCEPTION_PARAMETER,
131      FIELD,
132      LOCAL_VARIABLE,
133      PARAMETER,
134      RESOURCE_VARIABLE      -> v.visitVariable(this, p);
135    case RECORD_COMPONENT    -> v.visitRecordComponent(this, p);
136    case
137      CONSTRUCTOR,
138      INSTANCE_INIT,
139      METHOD,
140      STATIC_INIT            -> v.visitExecutable(this, p);
141    case PACKAGE             -> v.visitPackage(this, p);
142    case MODULE              -> v.visitModule(this, p);
143    case OTHER               -> v.visitUnknown(this, p);
144    };
145  }
146
147  @Override // UniversalConstruct<Element, UniversalElement<Element>>
148  protected final UniversalElement annotate(final List<? extends AnnotationMirror> replacementAnnotations) {
149    return new UniversalElement(this.delegate(), replacementAnnotations, this.domain());
150  }
151
152  @Override // Element
153  public final UniversalType asType() {
154    return UniversalType.of(this.delegate().asType(), this.domain());
155  }
156
157  /**
158   * Returns {@code true} if and only if this {@link UniversalElement} is a <dfn>generic class declaration</dfn>.
159   *
160   * @return {@code true} if and only if this {@link UniversalElement} is a <dfn>generic class declaration</dfn>
161   *
162   * @spec https://docs.oracle.com/javase/specs/jls/se23/html/jls-8.html#jls-8.1.2 Java Language Specification, section
163   * 8.1.2
164   */
165  public final boolean generic() {
166    return switch (this.getKind()) {
167    case CLASS, CONSTRUCTOR, ENUM, INTERFACE, METHOD, RECORD -> !this.getTypeParameters().isEmpty();
168    default -> false;
169    };
170  }
171
172  @Override // RecordComponentElement
173  public final UniversalElement getAccessor() {
174    return switch (this.getKind()) {
175    case RECORD_COMPONENT -> this.wrap(((RecordComponentElement)this.delegate()).getAccessor());
176    default -> null;
177    };
178  }
179
180  @Override // TypeParameterElement
181  public final List<? extends UniversalType> getBounds() {
182    return switch (this.getKind()) {
183    case TYPE_PARAMETER -> UniversalType.of(((TypeParameterElement)this.delegate()).getBounds(), this.domain());
184    default -> List.of();
185    };
186  }
187
188  @Override // VariableElement
189  @SuppressWarnings("try")
190  public final Object getConstantValue() {
191    return switch (this.getKind()) {
192    case BINDING_VARIABLE, ENUM_CONSTANT, EXCEPTION_PARAMETER, FIELD, LOCAL_VARIABLE, PARAMETER, RESOURCE_VARIABLE -> {
193      try (var lock = this.domain().lock()) {
194        // There is a LOT going on here; take the domain lock for safety. See
195        // https://github.com/openjdk/jdk/blob/jdk-25%2B3/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symbol.java#L1813-L1829
196        yield
197          ((VariableElement)this.delegate()).getConstantValue(); // will be a boxed type or String
198      }
199    }
200    default -> null;
201    };
202  }
203
204  @Override // ExecutableElement
205  public final UniversalAnnotationValue getDefaultValue() {
206    return switch (this.getKind()) {
207      // See
208      // https://github.com/openjdk/jdk/blob/jdk-25%2B3/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symbol.java#L2287-L2290;
209      // no concurrent Name access so no lock needed
210    case METHOD -> UniversalAnnotationValue.of(((ExecutableElement)this.delegate()).getDefaultValue(), this.domain());
211    default -> null;
212    };
213  }
214
215  @Override // ModuleElement
216  public final List<? extends UniversalDirective> getDirectives() {
217    return switch (this.getKind()) {
218    case MODULE -> UniversalDirective.of(((ModuleElement)this.delegate()).getDirectives(), this.domain());
219    default -> List.of();
220    };
221  }
222
223  /*
224    java.lang.AssertionError: Filling jrt:/java.base/java/lang/String$CaseInsensitiveComparator.class during DirectoryFileObject[/modules/java.base:java/lang/Integer$IntegerCache.class]
225    at jdk.compiler/com.sun.tools.javac.util.Assert.error(Assert.java:162)
226    at jdk.compiler/com.sun.tools.javac.code.ClassFinder.fillIn(ClassFinder.java:366)
227    at jdk.compiler/com.sun.tools.javac.code.ClassFinder.complete(ClassFinder.java:302)
228    at jdk.compiler/com.sun.tools.javac.code.Symbol.complete(Symbol.java:687)
229    at jdk.compiler/com.sun.tools.javac.code.Symbol$ClassSymbol.complete(Symbol.java:1455)
230    at jdk.compiler/com.sun.tools.javac.code.Symbol.apiComplete(Symbol.java:693)
231    at jdk.compiler/com.sun.tools.javac.code.Symbol$TypeSymbol.getEnclosedElements(Symbol.java:864)
232    at jdk.compiler/com.sun.tools.javac.code.Symbol$ClassSymbol.getEnclosedElements(Symbol.java:1420)
233    at jdk.compiler/com.sun.tools.javac.code.Symbol$ClassSymbol.getEnclosedElements(Symbol.java:1264)
234    at org.microbean.construct@0.0.10-SNAPSHOT/org.microbean.construct.element.UniversalElement.getEnclosedElements(UniversalElement.java:195)
235  */
236  // See https://github.com/microbean/microbean-construct/issues/18
237  @Override // Element
238  public final List<? extends UniversalElement> getEnclosedElements() {
239    return this.enclosedElementsSupplier.get();
240  }
241
242  @Override // Element
243  public final UniversalElement getEnclosingElement() {
244    return this.wrap(this.delegate().getEnclosingElement());
245  }
246
247  @Override // TypeParameterElement
248  public final UniversalElement getGenericElement() {
249    return switch (this.getKind()) {
250    case TYPE_PARAMETER -> this.wrap(((TypeParameterElement)this.delegate()).getGenericElement());
251    default -> this.getEnclosingElement(); // illegal state
252    };
253  }
254
255  @Override // TypeElement
256  public final List<? extends UniversalType> getInterfaces() {
257    return switch (this.getKind()) {
258    case ANNOTATION_TYPE, CLASS, ENUM, INTERFACE, RECORD ->
259      UniversalType.of(((TypeElement)this.delegate()).getInterfaces(), this.domain());
260    default -> List.of();
261    };
262  }
263
264  @Override // Element
265  public final ElementKind getKind() {
266    return this.delegate().getKind();
267  }
268
269  @Override // Element
270  public final Set<Modifier> getModifiers() {
271    return this.delegate().getModifiers();
272  }
273
274  @Override // TypeElement
275  public final NestingKind getNestingKind() {
276    return switch (this.getKind()) {
277    case ANNOTATION_TYPE, CLASS, ENUM, INTERFACE, RECORD -> ((TypeElement)this.delegate()).getNestingKind();
278    default -> NestingKind.TOP_LEVEL; // illegal state
279    };
280  }
281
282  @Override // ExecutableElement
283  public final List<? extends UniversalElement> getParameters() {
284    return switch (this.getKind()) {
285    case CONSTRUCTOR, METHOD -> this.wrap(((ExecutableElement)this.delegate()).getParameters());
286    default -> List.of();
287    };
288  }
289
290  @Override // ModuleElement, PackageElement, TypeElement
291  @SuppressWarnings("try")
292  public final StringName getQualifiedName() {
293    return switch (this.getKind()) {
294    case ANNOTATION_TYPE, CLASS, ENUM, INTERFACE, MODULE, PACKAGE, RECORD -> {
295      try (var lock = this.domain().lock()) {
296        yield StringName.of(((QualifiedNameable)this.delegate()).getQualifiedName().toString(), this.domain());
297      }
298    }
299    default -> StringName.of("", this.domain());
300    };
301  }
302
303  @Override // ExecutableElement
304  public final UniversalType getReceiverType() {
305    return
306      this.getKind().isExecutable() ?
307      UniversalType.of(this.asType(), this.domain()).getReceiverType() :
308      UniversalType.of(this.domain().noType(TypeKind.NONE), this.domain());
309  }
310
311  @Override // TypeElement
312  public final List<? extends UniversalElement> getRecordComponents() {
313    return switch (this.getKind()) {
314    case RECORD -> this.wrap(((TypeElement)this.delegate()).getRecordComponents());
315    default -> List.of();
316    };
317  }
318
319  @Override // ExecutableElement
320  public final UniversalType getReturnType() {
321    return UniversalType.of(this.asType(), this.domain()).getReturnType();
322  }
323
324  @Override // Element
325  @SuppressWarnings("try")
326  public final StringName getSimpleName() {
327    try (var lock = this.domain().lock()) {
328      return new StringName(this.delegate().getSimpleName().toString(), this.domain());
329    }
330  }
331
332  @Override // TypeElement
333  public final UniversalType getSuperclass() {
334    return switch (this.getKind()) {
335    case ANNOTATION_TYPE, CLASS, ENUM, INTERFACE, RECORD ->
336      UniversalType.of(((TypeElement)this.delegate()).getSuperclass(), this.domain());
337    default -> UniversalType.of(this.domain().noType(TypeKind.NONE), this.domain());
338    };
339  }
340
341  @Override // ExecutableElement
342  public final List<? extends UniversalType> getThrownTypes() {
343    return
344      this.getKind().isExecutable() ?
345      UniversalType.of(((ExecutableElement)this.delegate()).getThrownTypes(), this.domain()) :
346      List.of();
347  }
348
349  @Override // ExecutableElement
350  public final List<? extends UniversalElement> getTypeParameters() {
351    return switch (this.getKind()) {
352    case CLASS, CONSTRUCTOR, ENUM, INTERFACE, RECORD, METHOD ->
353      this.wrap(((Parameterizable)this.delegate()).getTypeParameters());
354    default -> List.of();
355    };
356  }
357
358  @Override // ExecutableElement
359  public final boolean isDefault() {
360    return switch (this.getKind()) {
361    case METHOD -> ((ExecutableElement)this.delegate()).isDefault();
362    default -> false;
363    };
364  }
365
366  @Override // ModuleElement
367  public final boolean isOpen() {
368    return switch (this.getKind()) {
369    case MODULE -> ((ModuleElement)this.delegate()).isOpen();
370    default -> false;
371    };
372  }
373
374  @Override // ModuleElement, PackageElement
375  public final boolean isUnnamed() {
376    return switch (this.getKind()) {
377    case MODULE  -> ((ModuleElement)this.delegate()).isUnnamed();
378    case PACKAGE -> ((PackageElement)this.delegate()).isUnnamed();
379    default -> false;
380    };
381  }
382
383  @Override // ExecutableElement
384  public final boolean isVarArgs() {
385    return switch (this.getKind()) {
386    case CONSTRUCTOR, METHOD -> ((ExecutableElement)this.delegate()).isVarArgs();
387    default -> false;
388    };
389  }
390
391  /**
392   * A convenience method that returns {@code true} if this {@link UniversalElement} represents the class declaration
393   * for {@code java.lang.Object}.
394   *
395   * @return {@code true} if this {@link UniversalElement} is the class declaration for {@code java.lang.Object}
396   */
397  public final boolean javaLangObject() {
398    return switch (this.getKind()) {
399    case CLASS -> this.getQualifiedName().contentEquals("java.lang.Object");
400    default -> false;
401    };
402  }
403
404  private final UniversalElement wrap(final Element e) {
405    return of(e, this.domain());
406  }
407
408  private final List<? extends UniversalElement> wrap(final Collection<? extends Element> es) {
409    return of(es, this.domain());
410  }
411
412
413  /*
414   * Static methods.
415   */
416
417
418  /**
419   * Returns a non-{@code null}, immutable {@link List} of {@link UniversalElement}s whose elements wrap the supplied
420   * {@link List}'s elements.
421   *
422   * @param es a {@link Collection} of {@link Element}s; must not be {@code null}
423   *
424   * @param domain a {@link PrimordialDomain}; must not be {@code null}
425   *
426   * @return a non-{@code null}, immutable {@link List} of {@link UniversalElement}s
427   *
428   * @exception NullPointerException if either argument is {@code null}
429   */
430  public static final List<? extends UniversalElement> of(final Collection<? extends Element> es, final PrimordialDomain domain) {
431    if (es.isEmpty()) {
432      return List.of();
433    }
434    final List<UniversalElement> newEs = new ArrayList<>(es.size());
435    for (final Element e : es) {
436      newEs.add(of(e, domain));
437    }
438    return Collections.unmodifiableList(newEs);
439  }
440
441  /**
442   * Returns a {@link UniversalElement} that is either the supplied {@link Element} (if it itself is {@code null} or is
443   * a {@link UniversalElement}) or one that wraps it.
444   *
445   * @param e an {@link Element}; may be {@code null} in which case {@code null} will be returned
446   *
447   * @param domain a {@link PrimordialDomain}; must not be {@code null}
448   *
449   * @return a {@link UniversalElement}, or {@code null} (if {@code e} is {@code null})
450   *
451   * @exception NullPointerException if {@code domain} is {@code null}
452   *
453   * @see #UniversalElement(Element, PrimordialDomain)
454   */
455  public static final UniversalElement of(final Element e, final PrimordialDomain domain) {
456    return switch (e) {
457    case null -> null;
458    case UniversalElement ue -> ue;
459    default -> new UniversalElement(e, domain);
460    };
461  }
462
463}