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