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