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