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