diff --git a/README.md b/README.md index 14a6fda..21d9d20 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ as a Maven dependency: Always check https://search.maven.org/artifact/org.microbean/microbean-construct for up-to-date available versions. --> - 0.0.17 + 0.0.18 ``` diff --git a/pom.xml b/pom.xml index dead882..2163839 100644 --- a/pom.xml +++ b/pom.xml @@ -143,7 +143,7 @@ maven-assembly-plugin - 3.7.1 + 3.8.0 maven-checkstyle-plugin @@ -215,11 +215,11 @@ maven-release-plugin - 3.2.0 + 3.3.1 maven-resources-plugin - 3.3.1 + 3.4.0 maven-scm-plugin @@ -235,7 +235,7 @@ maven-source-plugin - 3.3.1 + 3.4.0 attach-sources @@ -256,17 +256,17 @@ com.github.spotbugs spotbugs-maven-plugin - 4.9.8.1 + 4.9.8.2 org.codehaus.mojo versions-maven-plugin - 2.19.1 + 2.20.1 io.smallrye jandex-maven-plugin - 3.5.2 + 3.5.3 org.sonatype.central diff --git a/src/main/java/org/microbean/construct/BlockingCompilationTask.java b/src/main/java/org/microbean/construct/BlockingCompilationTask.java index 8e43111..0b6bfdd 100644 --- a/src/main/java/org/microbean/construct/BlockingCompilationTask.java +++ b/src/main/java/org/microbean/construct/BlockingCompilationTask.java @@ -25,7 +25,6 @@ import java.util.Collections; import java.util.List; import java.util.Locale; -import java.util.Objects; import java.util.Set; import java.util.concurrent.CompletableFuture; diff --git a/src/main/java/org/microbean/construct/DefaultDomain.java b/src/main/java/org/microbean/construct/DefaultDomain.java index fecbcc0..c8fdb84 100644 --- a/src/main/java/org/microbean/construct/DefaultDomain.java +++ b/src/main/java/org/microbean/construct/DefaultDomain.java @@ -35,8 +35,6 @@ import javax.lang.model.element.ModuleElement; import javax.lang.model.element.Name; import javax.lang.model.element.Parameterizable; -import javax.lang.model.element.QualifiedNameable; -import javax.lang.model.element.RecordComponentElement; import javax.lang.model.element.TypeElement; import javax.lang.model.util.Elements; @@ -82,7 +80,8 @@ public DefaultDomain() { } /** - * Creates a new {@link DefaultDomain} whose usage type is determined by the argument supplied to this constructor. + * Creates a new {@link DefaultDomain} normally for use at annotation processing time, whose usage + * type is actually determined by the argument supplied to this constructor. * * @param pe a {@link ProcessingEnvironment}; may be {@code null} in which case the return value of an invocation of * {@link Supplier#get()} on the return value of an invocation of {@link RuntimeProcessingEnvironmentSupplier#of()} @@ -113,7 +112,8 @@ public DefaultDomain(final Lock lock) { } /** - * Creates a new {@link DefaultDomain} whose usage type is determined by the arguments supplied to this constructor. + * Creates a new {@link DefaultDomain} normally for use at annotation processing time, whose usage + * type is actually determined by the arguments supplied to this constructor. * * @param pe a {@link ProcessingEnvironment}; may be {@code null} in which case the return value of an invocation of * {@link Supplier#get()} on the return value of an invocation of {@link RuntimeProcessingEnvironmentSupplier#of()} @@ -121,8 +121,8 @@ public DefaultDomain(final Lock lock) { * * @param lock a {@link Lock} to use to serialize symbol completion; if {@code null} and {@code pe} is {@code null}, * then a global {@link ReentrantLock} will be used instead; if {@code null} and {@code pe} is non-{@code null}, then - * no serialization of symbol completion will occur and this {@link DefaultDomain} will not be safe for concurrent use - * by multiple threads + * no serialization of symbol completion will occur and this {@link DefaultDomain} therefore will not be safe + * for concurrent use by multiple threads * * @see RuntimeProcessingEnvironmentSupplier * @@ -285,7 +285,8 @@ public UniversalType elementType(final TypeMirror t) { return UniversalType.of(Domain.super.elementType(t), this); } - private final Elements elements() { + // Non-private for testing only. + final Elements elements() { return this.pe().getElementUtils(); } @@ -331,6 +332,12 @@ public UniversalElement javaLangObject() { return UniversalElement.of(Domain.super.javaLangObject(), this); } + // (Convenience.) + @Override // Domain + public UniversalType javaLangObjectType() { + return UniversalType.of(Domain.super.javaLangObjectType(), this); + } + /** * Returns a non-{@code null} {@link Unlockable} that should be used in a {@code try}-with-resources block guarding * operations that might cause symbol completion. @@ -559,7 +566,8 @@ public UniversalElement typeParameterElement(final Parameterizable p, final Char return UniversalElement.of(Domain.super.typeParameterElement(p, name), this); } - private final Types types() { + // Non-private for testing only. + final Types types() { return this.pe().getTypeUtils(); } diff --git a/src/main/java/org/microbean/construct/Domain.java b/src/main/java/org/microbean/construct/Domain.java index 299e5d0..833df78 100644 --- a/src/main/java/org/microbean/construct/Domain.java +++ b/src/main/java/org/microbean/construct/Domain.java @@ -21,9 +21,11 @@ import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; +import java.util.ArrayList; import java.util.List; import java.util.Objects; +import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; @@ -51,13 +53,11 @@ import javax.lang.model.util.Elements; import javax.lang.model.util.Elements.Origin; -import org.microbean.construct.element.StringName; import org.microbean.construct.element.UniversalElement; import org.microbean.construct.type.UniversalType; -import static javax.lang.model.element.ElementKind.CONSTRUCTOR; -import static javax.lang.model.element.ElementKind.METHOD; +import static java.util.Collections.unmodifiableList; import static javax.lang.model.type.TypeKind.DECLARED; @@ -85,10 +85,56 @@ * * @author Laird Nelson * + * @see PrimordialDomain + * * @see JDK-8055219 */ @SuppressWarnings("try") -public interface Domain { +public interface Domain extends PrimordialDomain { + + /** + * Returns an immutable, determinate, non-{@code null} {@link List} of {@link AnnotationMirror} instances representing + * all annotations present on an element, whether directly present or present via inheritance. + * + * @param element the {@link Element} whose present annotations should be returned; must not be {@code null} + * + * @return an immutable, determinate, non-{@code null} {@link List} of {@link AnnotationMirror} instances representing + * all annotations present on an element, whether directly present or present via inheritance + * + * @exception NullPointerException if {@code element} is {@code null} + * + * @see javax.lang.model.util.Elements#getAllAnnotationMirrors(Element) + */ + public default List allAnnotationMirrors(Element element) { + element = UniversalElement.of(element, this); // handles locking, symbol completion, etc. + final List annotations = new ArrayList<>(8); + annotations.addAll(element.getAnnotationMirrors().reversed()); + TypeMirror sc = ((TypeElement)element).getSuperclass(); + if (sc.getKind() == DECLARED) { + element = ((DeclaredType)sc).asElement(); + WHILE_LOOP: + while (element != null && element.getKind().isDeclaredType()) { + for (final AnnotationMirror a : element.getAnnotationMirrors().reversed()) { + // See if it's inherited + final TypeElement annotationTypeElement = (TypeElement)a.getAnnotationType().asElement(); + for (final AnnotationMirror metaAnnotation : annotationTypeElement.getAnnotationMirrors()) { + if (((TypeElement)metaAnnotation.getAnnotationType().asElement()).getQualifiedName().contentEquals("java.lang.annotation.Inherited")) { + for (final AnnotationMirror existingAnnotation : annotations) { + if (existingAnnotation.getAnnotationType().asElement().equals(annotationTypeElement)) { + continue WHILE_LOOP; + } + } + annotations.add(a); + break; + } + } + } + sc = ((TypeElement)element).getSuperclass(); + element = sc.getKind() == DECLARED ? ((DeclaredType)sc).asElement() : null; + } + } + return annotations.isEmpty() ? List.of() : unmodifiableList(annotations.reversed()); + } /** * Returns an {@link ArrayType} whose {@linkplain ArrayType#getComponentType() component type} is {@linkplain @@ -105,6 +151,7 @@ public interface Domain { * * @see javax.lang.model.util.Types#getArrayType(TypeMirror) */ + // Type factory method public ArrayType arrayTypeOf(final TypeMirror componentType); /** @@ -136,13 +183,20 @@ public interface Domain { * Returns {@code true} if and only if the supplied {@code payload} (the first argument) is considered assignable to * the supplied {@code receiver} (the second argument) according to the rules of the Java Language - * Specification. + * Specification; note the counterintuitive order of the parameters. + * + *

Perhaps surprisingly, the "left hand side" of the putative assignment is represented by the second parameter + * ({@code receiver}). The "right hand side" of the putative assignment is represented by the first parameter + * ({@code payload}). This follows the contract of the {@link javax.lang.model.util.Types#isAssignable(TypeMirror, + * TypeMirror)} method, on which this method is modeled.

* * @param payload the {@link TypeMirror} being assigned; must not be {@code null} * * @param receiver the {@link TypeMirror} receiving the assignment; must not be {@code null} * - * @return {@code true} if and only if {@code payload} is assignable to {@code receiver} + * @return {@code true} if and only if {@code payload} is assignable to {@code receiver} according to the rules of the Java Language + * Specification * * @exception NullPointerException if either {@code payload} or {@code receiver} is {@code null} * @@ -254,6 +308,7 @@ public interface Domain { * @spec https://docs.oracle.com/javase/specs/jls/se23/html/jls-6.html#jls-6.7 Java Language Specification, section * 6.7 */ + // (Convenience.) public default DeclaredType declaredType(final CharSequence canonicalName) { final TypeElement e = this.typeElement(canonicalName); return e == null ? null : this.declaredType(e); @@ -270,8 +325,8 @@ public default DeclaredType declaredType(final CharSequence canonicalName) { * *

The number of supplied type arguments must either equal the number of the supplied {@link TypeElement}'s * {@linkplain TypeElement#getTypeParameters() formal type parameters}, or must be zero. If it is zero, and if the - * supplied {@link TypeElement} {@link #generic(Element) is generic}, then the supplied {@link TypeElement}'s raw type - * is returned.

+ * supplied {@link TypeElement} {@linkplain #generic(Element) is generic}, then the supplied {@link TypeElement}'s raw + * type is returned.

* *

If a parameterized type is returned, {@linkplain DeclaredType#asElement() its TypeElement} must not * be contained within a {@linkplain #generic(Element) generic} outer class. The parameterized type {@code @@ -290,6 +345,7 @@ public default DeclaredType declaredType(final CharSequence canonicalName) { * * @spec https://docs.oracle.com/javase/specs/jls/se23/html/jls-4.html#jls-4.5 Java Language Specification, section 4.5 */ + // Type factory method. public DeclaredType declaredType(final TypeElement typeElement, final TypeMirror... typeArguments); @@ -326,6 +382,7 @@ public DeclaredType declaredType(final TypeElement typeElement, * * @spec https://docs.oracle.com/javase/specs/jls/se23/html/jls-4.html#jls-4.5 Java Language Specification, section 4.5 */ + // Type factory method. public DeclaredType declaredType(final DeclaredType enclosingType, final TypeElement typeElement, final TypeMirror... typeArguments); @@ -334,6 +391,13 @@ public DeclaredType declaredType(final DeclaredType enclosingType, * Returns a non-{@code null} {@link List} of the direct supertypes of the supplied {@link TypeMirror}, * which is normally a {@linkplain TypeKind#DECLARED declared type}. * + *

The direct supertypes returned by this method are actually a subset of the direct supertypes of a type as + * defined in the Java Language + * Specification, section 4.10. Specifically, the subset contains only those types that can be expressed in the + * {@code extends} or {@code implements} clauses of the Java language. For example, a type {@code Baz} can declare + * only that it {@code extends Foo}, not {@code Foo}, even though {@code Foo} is a specification-described + * direct supertype of {@code Baz}.

+ * * @param t a {@link TypeMirror}; must not be {@code null}; must not be an {@linkplain TypeKind#EXECUTABLE executable * type}, a {@linkplain TypeKind#MODULE module type}, or a {@linkplain TypeKind#PACKAGE package type} * @@ -346,7 +410,9 @@ public DeclaredType declaredType(final DeclaredType enclosingType, * * @see javax.lang.model.util.Types#directSupertypes(TypeMirror) * - * @spec https://docs.oracle.com/javase/specs/jls/se23/html/jls-4.html#jls-4.10 Java Language Specification, section + * @see JDK-8055219 + * + * @spec https://docs.oracle.com/javase/specs/jls/se25/html/jls-4.html#jls-4.10 Java Language Specification, section * 4.10 */ public List directSupertypes(final TypeMirror t); @@ -386,6 +452,7 @@ public DeclaredType declaredType(final DeclaredType enclosingType, * @spec https://docs.oracle.com/javase/specs/jls/se23/html/jls-10.html#jls-10.1 Java Language Specification, section * 10.1 */ + // (Convenience.) public default TypeMirror elementType(final TypeMirror t) { return switch (t) { case null -> throw new NullPointerException("t"); @@ -430,6 +497,7 @@ public default TypeMirror elementType(final TypeMirror t) { * * @exception IllegalArgumentException if somehow {@code e} is neither a {@link Constructor} nor a {@link Method} */ + // (Convenience.) public default ExecutableElement executableElement(final Executable e) { return switch (e) { case null -> throw new NullPointerException("e"); @@ -469,6 +537,7 @@ public default ExecutableElement executableElement(final Executable e) { * * @exception NullPointerException if any argument is {@code null} */ + // (Convenience.) public default ExecutableElement executableElement(final TypeElement declaringElement, final TypeMirror returnType, final CharSequence name, @@ -551,6 +620,7 @@ public default ExecutableElement executableElement(final TypeElement declaringEl * @spec https://docs.oracle.com/javase/specs/jls/se23/html/jls-9.html#jls-9.1.2 Java Language Specification, section * 9.1.2 */ + // (Convenience.) public default boolean generic(final Element e) { return switch (e) { case null -> throw new NullPointerException("e"); @@ -592,6 +662,7 @@ yield switch (e.getKind()) { * @spec https://docs.oracle.com/javase/specs/jls/se23/html/jls-9.html#jls-9.1.2 Java Language Specification, section * 9.1.2 */ + // (Convenience.) public default boolean generic(final TypeMirror t) { return switch (t) { case null -> throw new NullPointerException("t"); @@ -616,6 +687,7 @@ public default boolean generic(final TypeMirror t) { * * @exception NullPointerException if {@code e} is {@code null} */ + // (Convenience.) public default boolean javaLangObject(final Element e) { return switch (e) { case null -> throw new NullPointerException("e"); @@ -643,6 +715,7 @@ public default boolean javaLangObject(final Element e) { * * @see #javaLangObject(Element) */ + // (Convenience.) public default boolean javaLangObject(final TypeMirror t) { return switch (t) { case null -> throw new NullPointerException("t"); @@ -666,21 +739,16 @@ public default boolean javaLangObject(final TypeMirror t) { * * @see #typeElement(CharSequence) */ + // (Convenience.) public default TypeElement javaLangObject() { return this.typeElement("java.lang.Object"); } - /** - * Semantically locks an opaque lock used to serialize symbol completion, and returns it in the form of an {@link - * Unlockable}. - * - *

Implementations of this method must not return {@code null}.

- * - * @return an {@link Unlockable} in a semantically locked state; never {@code null} - * - * @see Unlockable#close() - */ - public Unlockable lock(); + // (Convenience.) + @Override // PrimordialDomain + public default DeclaredType javaLangObjectType() { + return (DeclaredType)this.javaLangObject().asType(); + } /** * Returns a {@link ModuleElement} representing the module {@linkplain ModuleElement#getQualifiedName() named} by the @@ -699,6 +767,7 @@ public default TypeElement javaLangObject() { * @spec https://docs.oracle.com/javase/specs/jls/se23/html/jls-7.html#jls-7.7 Java Language Specification, section * 7.7 */ + // Element factory method. public ModuleElement moduleElement(final CharSequence qualifiedName); /** @@ -714,6 +783,7 @@ public default TypeElement javaLangObject() { * * @see javax.lang.model.util.Elements#getName(CharSequence) */ + // Element factory method. public Name name(final CharSequence name); /** @@ -735,9 +805,13 @@ public default TypeElement javaLangObject() { * * @see javax.lang.model.util.Types#getNoType(TypeKind) * + * @see NoType + * * @spec https://docs.oracle.com/javase/specs/jls/se23/html/jls-8.html#jls-8.4.5 Java Language Specification, section * 8.4.5 */ + // Type factory method. + @Override // PrimordialDomain public NoType noType(final TypeKind kind); /** @@ -748,8 +822,12 @@ public default TypeElement javaLangObject() { * * @see javax.lang.model.util.Types#getNullType() * + * @see NullType + * * @spec https://docs.oracle.com/javase/specs/jls/se23/html/jls-4.html#jls-4.1 Java Language Specification, section 4.1 */ + // Type factory method. + @Override // PrimordialDomain public NullType nullType(); /** @@ -760,6 +838,8 @@ public default TypeElement javaLangObject() { * @return a non-{@code null} {@link Origin} * * @see Elements#getOrigin(Element) + * + * @see Origin */ public Origin origin(final Element e); @@ -777,9 +857,12 @@ public default TypeElement javaLangObject() { * * @see javax.lang.model.util.Elements#getPackageElement(CharSequence) * + * @see PackageElement + * * @spec https://docs.oracle.com/javase/specs/jls/se23/html/jls-6.html#jls-6.7 Java Language Specification, section * 6.7 */ + // Element factory method. public PackageElement packageElement(final CharSequence canonicalName); /** @@ -799,9 +882,12 @@ public default TypeElement javaLangObject() { * * @see javax.lang.model.util.Elements#getPackageElement(ModuleElement, CharSequence) * + * @see PackageElement + * * @spec https://docs.oracle.com/javase/specs/jls/se23/html/jls-6.html#jls-6.7 Java Language Specification, section * 6.7 */ + // Element factory method. public PackageElement packageElement(final ModuleElement asSeenFrom, final CharSequence canonicalName); /** @@ -814,7 +900,10 @@ public default TypeElement javaLangObject() { * @exception NullPointerException if {@code gd} is {@code null} * * @exception IllegalArgumentException if {@code gd} is neither a {@link Class} nor an {@link Executable} + * + * @see Parameterizable */ + // (Convenience.) public default Parameterizable parameterizable(final GenericDeclaration gd) { return switch (gd) { case null -> throw new NullPointerException("gd"); @@ -827,16 +916,17 @@ public default Parameterizable parameterizable(final GenericDeclaration gd) { /** * A convenience method that returns {@code true} if and only if {@code t} is a {@link DeclaredType}, {@linkplain * TypeMirror#getKind() has a TypeKind} of {@link TypeKind#DECLARED DECLARED}, and {@linkplain - * DeclaredType#getTypeArguments() has an empty type arguments list}. + * DeclaredType#getTypeArguments() has a non-empty type arguments list}. * * @param t a {@link TypeMirror}; must not be {@code null} * * @return {@code true} if and only if {@code t} is a {@link DeclaredType}, {@linkplain * TypeMirror#getKind() has a TypeKind} of {@link TypeKind#DECLARED DECLARED}, and {@linkplain - * DeclaredType#getTypeArguments() has an empty type arguments list}; {@code false} otherwise + * DeclaredType#getTypeArguments() has a non-empty type arguments list}; {@code false} otherwise * * @exception NullPointerException if {@code t} is {@code null} */ + // (Convenience.) public default boolean parameterized(final TypeMirror t) { return switch (t) { case null -> throw new NullPointerException("t"); @@ -869,6 +959,8 @@ public default boolean parameterized(final TypeMirror t) { * * @see #primitiveType(TypeKind) * + * @see PrimitiveType + * * @spec https://docs.oracle.com/javase/specs/jls/se23/html/jls-5.html#jls-5.1.8 Java Language Specification, section * 5.1.8 * @@ -910,6 +1002,8 @@ public default PrimitiveType primitiveType(final CharSequence canonicalName) { * * @see #primitiveType(TypeMirror) * + * @see PrimitiveType + * * @spec https://docs.oracle.com/javase/specs/jls/se23/html/jls-5.html#jls-5.1.8 Java Language Specification, section * 5.1.8 */ @@ -934,10 +1028,13 @@ public default PrimitiveType primitiveType(final TypeElement e) { * * @see javax.lang.model.util.Types#getPrimitiveType(TypeKind) * + * @see PrimitiveType + * * @spec https://docs.oracle.com/javase/specs/jls/se23/html/jls-4.html#jls-4.2 Java Language Specification, section * 4.2 */ // (Canonical.) + // Type factory method. public PrimitiveType primitiveType(final TypeKind kind); /** @@ -959,6 +1056,7 @@ public default PrimitiveType primitiveType(final TypeElement e) { */ // (Canonical.) // (Unboxing.) + // Type factory method. public PrimitiveType primitiveType(final TypeMirror t); /** @@ -971,7 +1069,7 @@ public default PrimitiveType primitiveType(final TypeElement e) { * * @param t a {@link TypeMirror}; must not be {@code null} * - * @return {@code true} if and only if this {@link UniversalType} represents a prototypical type + * @return {@code true} if and only if {@code t} represents a prototypical type * * @exception NullPointerException if {@code t} is {@code null} * @@ -989,7 +1087,7 @@ public default boolean prototypical(final TypeMirror t) { } }; } - + /** * A convenience method that returns {@code true} if and only if the supplied {@link TypeMirror} is a raw * type according to the rules @@ -1005,6 +1103,7 @@ public default boolean prototypical(final TypeMirror t) { * * @spec https://docs.oracle.com/javase/specs/jls/se23/html/jls-4.html#jls-4.8 Java Language Specification, section 4.8 */ + // (Convenience.) public default boolean raw(final TypeMirror t) { return switch (t) { case null -> throw new NullPointerException("t"); @@ -1039,21 +1138,21 @@ yield switch (t.getKind()) { * * @exception NullPointerException if {@code t} is {@code null} * + * @see #parameterized(TypeMirror) + * + * @see #elementType(TypeMirror) + * + * @see #erasure(TypeMirror) + * * @spec https://docs.oracle.com/javase/specs/jls/se23/html/jls-4.html#jls-4.8 Java Language Specification, section * 4.8 */ - public default TypeMirror rawType(final TypeMirror t) { + // (Convenience.) + public default TypeMirror rawType(TypeMirror t) { return switch (t) { case null -> throw new NullPointerException("t"); - case UniversalType ut -> ut.rawType(); - default -> { - try (var lock = this.lock()) { - yield switch (t.getKind()) { - case ARRAY -> this.rawType(this.elementType(t)); // recursive - default -> this.parameterized(t) ? this.erasure(t) : null; - }; - } - } + case UniversalType ut -> ut.elementType().parameterized() ? this.erasure(ut) : null; + default -> this.parameterized(this.elementType(t)) ? this.erasure(t) : null; }; } @@ -1145,35 +1244,11 @@ yield switch (t.getKind()) { * * @see javax.lang.model.util.Types#isSubtype(TypeMirror, TypeMirror) * - * @spec https://docs.oracle.com/javase/specs/jls/se23/html/jls-4.html#jls-4.10 Java Language Specification, section + * @spec https://docs.oracle.com/javase/specs/jls/se25/html/jls-4.html#jls-4.10 Java Language Specification, section * 4.10 */ public boolean subtype(TypeMirror candidateSubtype, TypeMirror supertype); - /** - * Converts the supplied {@link CharSequence}, which is often a {@link Name}, into a {@link String}, and returns the - * conversion, {@linkplain #lock() locking} when appropriate to serialize symbol completion. - * - * @param name the {@link CharSequence} to convert; may be {@code null} in which case {@code null} will be returned - * - * @return a {@link String}, or {@code null} if {@code name} was {@code null} - * - * @see #lock() - */ - public default String toString(final CharSequence name) { - return switch (name) { - case null -> null; - case String s -> s; - case StringName sn -> sn.value(); - case Name n -> { - try (var lock = this.lock()) { - yield n.toString(); - } - } - default -> name.toString(); - }; - } - /** * A convenience method that returns the {@link TypeMirror} corresponding to the supplied (reflective) {@link Type}. * @@ -1186,6 +1261,7 @@ public default String toString(final CharSequence name) { * @exception IllegalArgumentException if {@code t} is not a {@link Class}, {@link GenericArrayType}, {@link * ParameterizedType}, {@link java.lang.reflect.TypeVariable} or {@link java.lang.reflect.WildcardType} */ + // (Convenience.) public default TypeMirror type(final Type t) { // TODO: anywhere there is domain.declaredType(), consider passing // domain.moduleElement(this.getClass().getModule().getName()) as the first argument. Not sure how this works @@ -1235,6 +1311,7 @@ public default TypeMirror type(final Type t) { * * @see #type(Type) */ + // (Convenience.) public default TypeMirror[] types(final Type[] ts) { return switch (ts.length) { case 0 -> new TypeMirror[0]; @@ -1264,6 +1341,7 @@ public default TypeMirror[] types(final Type[] ts) { * @spec https://docs.oracle.com/javase/specs/jls/se23/html/jls-6.html#jls-6.7 Java Language Specification, section * 6.7 */ + // Element factory method. public TypeElement typeElement(final CharSequence canonicalName); /** @@ -1284,6 +1362,7 @@ public default TypeMirror[] types(final Type[] ts) { * @spec https://docs.oracle.com/javase/specs/jls/se23/html/jls-6.html#jls-6.7 Java Language Specification, section * 6.7 */ + // Element factory method. public TypeElement typeElement(final ModuleElement asSeenFrom, final CharSequence canonicalName); /** @@ -1318,6 +1397,7 @@ public default TypeMirror[] types(final Type[] ts) { * @spec https://docs.oracle.com/javase/specs/jls/se23/html/jls-5.html#jls-5.1.7 Java Language Specification, section * 5.1.7 */ + // Element factory method. // (Canonical.) // (Boxing.) public default TypeElement typeElement(final PrimitiveType t) { @@ -1380,6 +1460,7 @@ public default TypeElement typeElement(final TypeKind primitiveTypeKind) { * * @return a {@link TypeParameterElement}, or {@code null} */ + // (Convenience.) public default TypeParameterElement typeParameterElement(Parameterizable p, final CharSequence name) { Objects.requireNonNull(p, "p"); Objects.requireNonNull(name, "name"); @@ -1423,6 +1504,7 @@ public default TypeParameterElement typeParameterElement(Parameterizable p, fina * * @see #typeParameterElement(Parameterizable, CharSequence) */ + // (Convenience.) public default TypeVariable typeVariable(Parameterizable p, final CharSequence name) { final TypeParameterElement e = this.typeParameterElement(p, name); return e == null ? null : (TypeVariable)e.asType(); @@ -1450,6 +1532,7 @@ public default TypeVariable typeVariable(Parameterizable p, final CharSequence n * * @see VariableElement */ + // (Convenience.) public default VariableElement variableElement(final Element enclosingElement, final CharSequence simpleName) { Objects.requireNonNull(simpleName, "simpleName"); return switch (enclosingElement) { @@ -1489,6 +1572,7 @@ public default VariableElement variableElement(final Element enclosingElement, f * @spec https://docs.oracle.com/javase/specs/jls/se23/html/jls-4.html#jls-4.5.1 Java Language Specification, section * 4.5.1 */ + // (Convenience.) public default WildcardType wildcardType() { return this.wildcardType(null, null); } @@ -1514,6 +1598,7 @@ public default WildcardType wildcardType() { * @spec https://docs.oracle.com/javase/specs/jls/se23/html/jls-4.html#jls-4.5.1 Java Language Specification, section * 4.5.1 */ + // Type factory method. public WildcardType wildcardType(TypeMirror extendsBound, TypeMirror superBound); } diff --git a/src/main/java/org/microbean/construct/PrimordialDomain.java b/src/main/java/org/microbean/construct/PrimordialDomain.java new file mode 100644 index 0000000..eb11a26 --- /dev/null +++ b/src/main/java/org/microbean/construct/PrimordialDomain.java @@ -0,0 +1,143 @@ +/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*- + * + * Copyright © 2025 microBean™. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package org.microbean.construct; + +import javax.lang.model.element.Name; + +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.NoType; +import javax.lang.model.type.NullType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; + +import org.microbean.construct.element.StringName; +import org.microbean.construct.element.SyntheticName; + +/** + * A view of an underlying domain of valid Java constructs that exposes {@linkplain #nullType() the null type}, various + * kinds of {@linkplain #noType(TypeKind) pseudo-types}, the {@linkplain #javaLangObjectType() prototypical + * java.lang.Object type}, and the ability to {@linkplain #lock() globally lock for symbol completion if + * needed}. + * + *

{@link PrimordialDomain}s are primordial because they expose the "first principles" constructs needed to compose + * other constructs.

+ * + * @author
Laird Nelson + * + * @see #javaLangObjectType() + * + * @see #lock() + * + * @see #noType(TypeKind) + * + * @see #nullType() + */ +public interface PrimordialDomain { + + /** + * Returns the (non-{@code null}, determinate) {@link DeclaredType} representing the prototypical + * type of {@link Object java.lang.Object}. + * + *

Implementations of this method must not return {@code null}.

+ * + *

{@link DeclaredType} instances returned by implementations of this method must return {@link TypeKind#DECLARED} + * from their {@link TypeMirror#getKind()} method.

+ * + * @return the {@link DeclaredType} representing the prototypical type of {@link Object java.lang.Object}; + * never {@code null} + */ + public DeclaredType javaLangObjectType(); + + /** + * Semantically locks an opaque lock used to serialize symbol completion, and returns it in the form of an {@link + * Unlockable}. + * + *

Implementations of this method must not return {@code null}.

+ * + * @return an {@link Unlockable} in a semantically locked state; never {@code null} + * + * @see SymbolCompletionLock + * + * @see Unlockable#close() + */ + public Unlockable lock(); + + /** + * Returns a (non-{@code null}, determinate) {@link NoType} representing the supplied {@link TypeKind}, provided it is + * either {@link TypeKind#NONE} or {@link TypeKind#VOID}. + * + *

Implementations of this method must not return {@code null}.

+ * + * @param kind a {@link TypeKind}; must be either {@link TypeKind#NONE} or {@link TypeKind#VOID} + * + * @return a {@link NoType} representing the supplied {@link TypeKind}; never {@code null} + * + * @exception NullPointerException if {@code kind} is {@code null} + * + * @exception IllegalArgumentException if {@code kind} is neither {@link TypeKind#NONE} nor {@link TypeKind#VOID} + * + * @see javax.lang.model.util.Types#getNoType(TypeKind) + */ + public NoType noType(final TypeKind kind); + + /** + * Returns a (non-{@code null}, determinate) {@link NullType} representing the null type. + * + *

Implementations of thsi method must not return {@code null}.

+ * + * @return a {@link NullType} representing the null type; never {@code null} + * + * @see javax.lang.model.util.Types#getNullType() + */ + public NullType nullType(); + + /** + * A convenience method that converts the supplied {@link CharSequence}, which is often a {@link Name}, into a {@link + * String}, and returns the conversion, {@linkplain #lock() locking} when appropriate to serialize symbol completion. + * + *

The default implementation of this method may return {@code null} if the supplied {@code name} is {@code + * null}.

+ * + *

In many implementations of domains, converting a {@link Name} to a {@link String} can cause problems if symbol + * completion is taking place concurrently and the symbol completion lock is not held. This method helps avoid those + * problems.

+ * + *

Overriding this method is not normally needed.

+ * + * @param name the {@link CharSequence} to convert; may be {@code null} in which case {@code null} will be returned + * + * @return a {@link String}, or {@code null} if {@code name} was {@code null} + * + * @see #lock() + * + * @see Name + */ + @SuppressWarnings("try") + public default String toString(final CharSequence name) { + return switch (name) { + case null -> null; + case String s -> s; + case StringName sn -> sn.value(); + case SyntheticName sn -> sn.toString(); + case Name n -> { + try (var lock = this.lock()) { + yield n.toString(); + } + } + default -> name.toString(); + }; + } + +} diff --git a/src/main/java/org/microbean/construct/Processor.java b/src/main/java/org/microbean/construct/Processor.java index a9f7a9b..a6dea9f 100644 --- a/src/main/java/org/microbean/construct/Processor.java +++ b/src/main/java/org/microbean/construct/Processor.java @@ -38,8 +38,6 @@ import static java.lang.System.getLogger; -import static java.lang.System.Logger.Level.DEBUG; - final class Processor implements AutoCloseable, javax.annotation.processing.Processor { private static final Logger LOGGER = getLogger(Processor.class.getName()); diff --git a/src/main/java/org/microbean/construct/ReadOnlyModularJavaFileManager.java b/src/main/java/org/microbean/construct/ReadOnlyModularJavaFileManager.java index fa6f2a8..d679c5e 100644 --- a/src/main/java/org/microbean/construct/ReadOnlyModularJavaFileManager.java +++ b/src/main/java/org/microbean/construct/ReadOnlyModularJavaFileManager.java @@ -14,19 +14,13 @@ package org.microbean.construct; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.Reader; import java.io.UncheckedIOException; -import java.io.Writer; import java.lang.module.ModuleReader; import java.lang.module.ModuleReference; import java.lang.System.Logger; -import java.net.URI; - import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -34,7 +28,6 @@ import java.util.List; import java.util.HashMap; import java.util.Map; -import java.util.Objects; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -42,9 +35,6 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import javax.lang.model.element.NestingKind; -import javax.lang.model.element.Modifier; - import javax.tools.FileObject; import javax.tools.ForwardingJavaFileManager; import javax.tools.JavaFileManager.Location; diff --git a/src/main/java/org/microbean/construct/RuntimeProcessingEnvironmentSupplier.java b/src/main/java/org/microbean/construct/RuntimeProcessingEnvironmentSupplier.java index c7e60ea..3b15ee2 100644 --- a/src/main/java/org/microbean/construct/RuntimeProcessingEnvironmentSupplier.java +++ b/src/main/java/org/microbean/construct/RuntimeProcessingEnvironmentSupplier.java @@ -15,9 +15,6 @@ import java.lang.System.Logger; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.RunnableFuture; - import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; @@ -83,8 +80,8 @@ private RuntimeProcessingEnvironmentSupplier() { /** - * Closes this {@link RuntimeProcessingEnvironmentSupplier}, which invalidates all {@link ProcessingEnvironment}s - * {@linkplain #get() supplied} by it. + * Closes this {@link RuntimeProcessingEnvironmentSupplier}, which invalidates all {@link + * ProcessingEnvironment}s {@linkplain #get() supplied} by it. * *

A subsequent call to {@link #get()} will reset this {@link RuntimeProcessingEnvironmentSupplier}.

* @@ -103,7 +100,8 @@ public final void close() { /** * Returns a non-{@code null}, {@link ProcessingEnvironment} suitable for runtime use. * - *

{@link ProcessingEnvironment} instances are not guaranteed to be thread-safe.

+ *

{@link ProcessingEnvironment} instances are not guaranteed to be safe for concurrent use by multiple + * threads.

* * @return a non-{@code null} {@link ProcessingEnvironment} * diff --git a/src/main/java/org/microbean/construct/UniversalConstruct.java b/src/main/java/org/microbean/construct/UniversalConstruct.java index 8bb95ff..49d6f46 100644 --- a/src/main/java/org/microbean/construct/UniversalConstruct.java +++ b/src/main/java/org/microbean/construct/UniversalConstruct.java @@ -19,28 +19,36 @@ import java.lang.constant.Constable; import java.lang.constant.ConstantDesc; import java.lang.constant.DynamicConstantDesc; -import java.lang.constant.MethodHandleDesc; +import java.util.ArrayList; import java.util.List; -import java.util.Objects; import java.util.Optional; import java.util.function.Supplier; import javax.lang.model.AnnotatedConstruct; +import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; import javax.lang.model.type.TypeMirror; import org.microbean.construct.constant.Constables; +import org.microbean.construct.element.SyntheticAnnotationMirror; import org.microbean.construct.element.UniversalAnnotation; import org.microbean.construct.element.UniversalElement; import org.microbean.construct.type.UniversalType; import static java.lang.constant.ConstantDescs.BSM_INVOKE; +import static java.lang.constant.ConstantDescs.CD_List; + +import static java.lang.constant.MethodHandleDesc.ofConstructor; + +import static java.util.Collections.unmodifiableList; + +import static java.util.Objects.requireNonNull; /** * An abstract implementation of {@link AnnotatedConstruct} from which only {@link UniversalElement} and {@link @@ -48,6 +56,8 @@ * * @param a type of {@link AnnotatedConstruct}, which may be only either {@link Element} or {@link TypeMirror} * + * @param a type representing one of the permitted subclasses + * * @author Laird Nelson * * @see AnnotatedConstruct @@ -56,7 +66,8 @@ * * @see UniversalType */ -public abstract sealed class UniversalConstruct implements AnnotatedConstruct, Constable +public abstract sealed class UniversalConstruct> + implements AnnotatedConstruct, Constable permits UniversalElement, UniversalType { @@ -65,7 +76,8 @@ public abstract sealed class UniversalConstruct im */ - private final Domain domain; + // Retained only for Constable implementation + private final PrimordialDomain domain; // Eventually this should become a lazy constant/stable value // volatile not needed @@ -74,6 +86,11 @@ public abstract sealed class UniversalConstruct im // Eventually this should become a lazy constant/stable value private volatile String s; + // Eventually this should become a lazy constant/stable value + private volatile List annotations; + + private final List syntheticAnnotations; + /* * Constructors. @@ -85,33 +102,81 @@ public abstract sealed class UniversalConstruct im * * @param delegate a delegate to which operations are delegated; must not be {@code null} * - * @param domain; a {@link Domain} from which the supplied {@code delegate} is presumed to have originated; must not - * be {@code null} + * @param domain a {@link PrimordialDomain} representing the construct domain from which the supplied {@code + * delegate} is presumed to have originated; must not be {@code null} * * @exception NullPointerException if either argument is {@code null} + * + * @see #UniversalConstruct(AnnotatedConstruct, List, PrimordialDomain) + */ + protected UniversalConstruct(final T delegate, final PrimordialDomain domain) { + this(delegate, null, domain); + } + + /** + * Creates a new {@link AnnotatedConstruct}. + * + * @param delegate a delegate to which operations are delegated; must not be {@code null} + * + * @param annotations a {@link List} of {@link AnnotationMirror} instances representing annotations, often + * synthetic, that this {@link UniversalConstruct} should bear; may be {@code null} in which case only the annotations + * from the supplied {@code delegate} will be used + * + * @param domain a {@link PrimordialDomain} representing the construct domain from which the supplied {@code + * delegate} is presumed to have originated; must not be {@code null} + * + * @exception NullPointerException if any argument is {@code null} + * + * @see #annotate(List) */ @SuppressWarnings("try") - protected UniversalConstruct(final T delegate, final Domain domain) { + protected UniversalConstruct(final T delegate, + final List annotations, + final PrimordialDomain domain) { super(); - this.domain = Objects.requireNonNull(domain, "domain"); - final T unwrappedDelegate = unwrap(Objects.requireNonNull(delegate, "delegate")); + this.domain = requireNonNull(domain, "domain"); + if (annotations == null) { + this.syntheticAnnotations = List.of(); + } else if (annotations.isEmpty()) { + this.syntheticAnnotations = List.of(); + this.annotations = List.of(); // volatile write + } else { + final List delegateAnnotations = new ArrayList<>(annotations.size()); + final List syntheticAnnotations = new ArrayList<>(annotations.size()); + for (final AnnotationMirror annotation : annotations) { + switch (annotation) { + case null -> throw new IllegalArgumentException("annotations: " + annotations); + case SyntheticAnnotationMirror sam -> syntheticAnnotations.add(UniversalAnnotation.of(sam, domain)); + case UniversalAnnotation ua -> { + switch (ua.delegate()) { + case SyntheticAnnotationMirror sam -> syntheticAnnotations.add(ua); + case UniversalAnnotation x -> throw new AssertionError(); + default -> delegateAnnotations.add(UniversalAnnotation.of(annotation, domain)); + } + } + default -> delegateAnnotations.add(UniversalAnnotation.of(annotation, domain)); + } + } + this.annotations = delegateAnnotations.isEmpty() ? List.of() : unmodifiableList(delegateAnnotations); // volatile write + this.syntheticAnnotations = syntheticAnnotations.isEmpty() ? List.of() : unmodifiableList(syntheticAnnotations); + } + final T unwrappedDelegate = unwrap(requireNonNull(delegate, "delegate")); if (unwrappedDelegate == delegate) { - // No unwrapping happened so do symbol completion early; most common case. - final Runnable symbolCompleter = switch (unwrappedDelegate) { - case Element e -> e::getModifiers; - case TypeMirror t -> t::getKind; - default -> UniversalConstruct::doNothing; - }; this.delegateSupplier = () -> { try (var lock = domain.lock()) { - symbolCompleter.run(); + // No unwrapping happened so do symbol completion early; most common case. + if (unwrappedDelegate instanceof Element) { + ((Element)unwrappedDelegate).getModifiers(); + } else { + ((TypeMirror)unwrappedDelegate).getKind(); + } this.delegateSupplier = () -> unwrappedDelegate; } return unwrappedDelegate; }; } else { - assert delegate instanceof UniversalConstruct; - // Symbol completion already happened + assert delegate instanceof UniversalConstruct; + // Symbol completion already happened because unwrapping actually happened this.delegateSupplier = () -> unwrappedDelegate; } } @@ -122,6 +187,19 @@ protected UniversalConstruct(final T delegate, final Domain domain) { */ + /** + * Experimental; returns a new {@link UniversalConstruct} instance annotated with only the supplied + * annotations, which are often synthetic. + * + * @param replacementAnnotations a {@link List} of {@link AnnotationMirror}s; must not be {@code null} + * + * @return a new {@link UniversalConstruct} instance; never {@code null} + * + * @exception NullPointerException if {@code replacementAnnotations} is {@code null} + */ + // Experimental. Unsupported. + protected abstract U annotate(final List replacementAnnotations); + /** * Returns the delegate to which operations are delegated. * @@ -142,22 +220,26 @@ public final T delegate() { @Override // Constable public final Optional describeConstable() { final T delegate = this.delegate(); - final Domain domain = this.domain(); - return Constables.describe(delegate, domain) - .map(delegateDesc -> DynamicConstantDesc.of(BSM_INVOKE, - MethodHandleDesc.ofConstructor(ClassDesc.of(this.getClass().getName()), - ClassDesc.of(delegate instanceof TypeMirror ? TypeMirror.class.getName() : Element.class.getName()), - ClassDesc.of(Domain.class.getName())), - delegateDesc, - ((Constable)domain).describeConstable().orElseThrow())); + final List annotations = this.annotations; // volatile read; may be null and that's OK + return this.domain() instanceof Domain d ? Constables.describe(delegate, d) + .flatMap(delegateDesc -> Constables.describe(annotations, Constable::describeConstable) + .map(annosDesc -> DynamicConstantDesc.of(BSM_INVOKE, + ofConstructor(ClassDesc.of(this.getClass().getName()), + ClassDesc.of(delegate instanceof TypeMirror ? TypeMirror.class.getName() : Element.class.getName()), + CD_List, + ClassDesc.of(PrimordialDomain.class.getName())), + delegateDesc, + annosDesc, + ((Constable)d).describeConstable().orElseThrow()))) : + Optional.empty(); } /** - * Returns the {@link Domain} supplied at construction time. + * Returns the {@link PrimordialDomain} supplied at construction time. * - * @return the non-{@code null} {@link Domain} supplied at construction time + * @return the non-{@code null} {@link PrimordialDomain} supplied at construction time */ - public final Domain domain() { + public final PrimordialDomain domain() { return this.domain; } @@ -173,14 +255,31 @@ public final boolean equals(final Object other) { // (Symbol (Element) doesn't override it at all.) return this == other || switch (other) { case null -> false; - case UniversalConstruct uc when this.getClass() == uc.getClass() -> this.delegate().equals(uc.delegate()); + case UniversalConstruct uc when this.getClass() == uc.getClass() -> this.delegate().equals(uc.delegate()); default -> false; }; } @Override // AnnotatedConstruct + @SuppressWarnings("try") public final List getAnnotationMirrors() { - return UniversalAnnotation.of(this.delegate().getAnnotationMirrors(), this.domain()); + List annotations = this.annotations; // volatile read + if (annotations == null) { + try (var lock = this.domain().lock()) { + annotations = + this.annotations = UniversalAnnotation.of(this.delegate().getAnnotationMirrors(), this.domain()); // volatile read/write + } + assert annotations != null; + } + if (annotations.isEmpty()) { + return this.syntheticAnnotations; + } else if (this.syntheticAnnotations.isEmpty()) { + return annotations; + } else { + final List rv = new ArrayList<>(annotations); + rv.addAll(this.syntheticAnnotations); + return unmodifiableList(rv); + } } @Override // AnnotatedConstruct @@ -246,12 +345,10 @@ public final String toString() { */ @SuppressWarnings("unchecked") public static final T unwrap(T t) { - while (t instanceof UniversalConstruct uc) { + while (t instanceof UniversalConstruct uc) { t = (T)uc.delegate(); } return t; } - private static final void doNothing() {} - } diff --git a/src/main/java/org/microbean/construct/Unlockable.java b/src/main/java/org/microbean/construct/Unlockable.java index 851a93b..879c90b 100644 --- a/src/main/java/org/microbean/construct/Unlockable.java +++ b/src/main/java/org/microbean/construct/Unlockable.java @@ -1,6 +1,6 @@ /* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*- * - * Copyright © 2024 microBean™. + * Copyright © 2024–2025 microBean™. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -17,7 +17,7 @@ * An {@link AutoCloseable} extension whose {@link #close()} method throws no checked exceptions and unlocks something * that may have been previously locked in some unspecified manner. * - * @author Laird Nelson + * @author Laird Nelson * * @see #close() * diff --git a/src/main/java/org/microbean/construct/constant/Constables.java b/src/main/java/org/microbean/construct/constant/Constables.java index a3de239..790a947 100644 --- a/src/main/java/org/microbean/construct/constant/Constables.java +++ b/src/main/java/org/microbean/construct/constant/Constables.java @@ -17,12 +17,15 @@ import java.lang.constant.Constable; import java.lang.constant.ConstantDesc; import java.lang.constant.DynamicConstantDesc; -import java.lang.constant.MethodHandleDesc; import java.lang.constant.MethodTypeDesc; import java.util.List; +import java.util.Map; +import java.util.Map.Entry; import java.util.Optional; +import java.util.function.Function; + import javax.lang.model.AnnotatedConstruct; import javax.lang.model.element.Element; @@ -48,10 +51,18 @@ import org.microbean.construct.Domain; import static java.lang.constant.ConstantDescs.BSM_INVOKE; +import static java.lang.constant.ConstantDescs.CD_List; +import static java.lang.constant.ConstantDescs.CD_Map; +import static java.lang.constant.ConstantDescs.CD_Object; import static java.lang.constant.ConstantDescs.NULL; +import static java.lang.constant.DirectMethodHandleDesc.Kind.INTERFACE_STATIC; import static java.lang.constant.DirectMethodHandleDesc.Kind.VIRTUAL; +import static java.lang.constant.MethodHandleDesc.ofMethod; + +import static java.util.Arrays.fill; + import static org.microbean.construct.constant.ConstantDescs.CD_ArrayType; import static org.microbean.construct.constant.ConstantDescs.CD_CharSequence; import static org.microbean.construct.constant.ConstantDescs.CD_DeclaredType; @@ -107,11 +118,11 @@ public static final Optional describe(final Name n, fina case ConstantDesc cd -> Optional.of(cd); // future proofing? default -> (d instanceof Constable c ? c.describeConstable() : Optional.empty()) .map(domainDesc -> DynamicConstantDesc.of(BSM_INVOKE, - MethodHandleDesc.ofMethod(VIRTUAL, - ClassDesc.of(Domain.class.getName()), - "name", - MethodTypeDesc.of(CD_Name, - CD_CharSequence)), + ofMethod(VIRTUAL, + ClassDesc.of(Domain.class.getName()), + "name", + MethodTypeDesc.of(CD_Name, + CD_CharSequence)), domainDesc, d.toString(n))); }; @@ -206,14 +217,14 @@ public static final Optional describe(final ExecutableEl final int parameterCount = parameters.size(); final ConstantDesc[] args = new ConstantDesc[5 + parameterCount]; args[0] = - MethodHandleDesc.ofMethod(VIRTUAL, - ClassDesc.of(Domain.class.getName()), - "executableElement", - MethodTypeDesc.of(CD_ExecutableElement, - CD_TypeElement, - CD_TypeMirror, - CD_CharSequence, - CD_TypeMirror.arrayType())); + ofMethod(VIRTUAL, + ClassDesc.of(Domain.class.getName()), + "executableElement", + MethodTypeDesc.of(CD_ExecutableElement, + CD_TypeElement, + CD_TypeMirror, + CD_CharSequence, + CD_TypeMirror.arrayType())); args[1] = domainDesc; args[2] = describe(e.getEnclosingElement(), d).orElse(null); if (args[2] == null) { @@ -259,11 +270,11 @@ public static final Optional describe(final ModuleElemen case ConstantDesc cd -> Optional.of(cd); // future proofing? default -> describe(e.getQualifiedName(), d) // getQualifiedName() does not cause symbol completion .map(nameDesc -> DynamicConstantDesc.of(BSM_INVOKE, - MethodHandleDesc.ofMethod(VIRTUAL, - ClassDesc.of(Domain.class.getName()), - "moduleElement", - MethodTypeDesc.of(CD_ModuleElement, - CD_CharSequence)), + ofMethod(VIRTUAL, + ClassDesc.of(Domain.class.getName()), + "moduleElement", + MethodTypeDesc.of(CD_ModuleElement, + CD_CharSequence)), ((Constable)d).describeConstable().orElseThrow(), nameDesc)); }; @@ -288,11 +299,11 @@ public static final Optional describe(final PackageEleme case ConstantDesc cd -> Optional.of(cd); // future proofing? default -> describe(e.getQualifiedName(), d) // getQualifiedName() does not cause symbol completion .map(nameDesc -> DynamicConstantDesc.of(BSM_INVOKE, - MethodHandleDesc.ofMethod(VIRTUAL, - ClassDesc.of(Domain.class.getName()), - "packageElement", - MethodTypeDesc.of(CD_PackageElement, - CD_CharSequence)), + ofMethod(VIRTUAL, + ClassDesc.of(Domain.class.getName()), + "packageElement", + MethodTypeDesc.of(CD_PackageElement, + CD_CharSequence)), ((Constable)d).describeConstable().orElseThrow(), nameDesc)); }; @@ -317,11 +328,11 @@ public static final Optional describe(final TypeElement case ConstantDesc cd -> Optional.of(cd); // future proofing? default -> describe(e.getQualifiedName(), d) // getQualifiedName() does not cause symbol completion .map(nameDesc -> DynamicConstantDesc.of(BSM_INVOKE, - MethodHandleDesc.ofMethod(VIRTUAL, - ClassDesc.of(Domain.class.getName()), - "typeElement", - MethodTypeDesc.of(CD_TypeElement, - CD_CharSequence)), + ofMethod(VIRTUAL, + ClassDesc.of(Domain.class.getName()), + "typeElement", + MethodTypeDesc.of(CD_TypeElement, + CD_CharSequence)), ((Constable)d).describeConstable().orElseThrow(), nameDesc)); }; @@ -349,12 +360,12 @@ public static final Optional describe(final TypeParamete yield describe(e.getEnclosingElement(), d) .flatMap(parameterizableDesc -> describe(e.getSimpleName(), d) .map(nameDesc -> DynamicConstantDesc.of(BSM_INVOKE, - MethodHandleDesc.ofMethod(VIRTUAL, - ClassDesc.of(Domain.class.getName()), - "typeParameterElement", - MethodTypeDesc.of(CD_TypeParameterElement, - CD_Parameterizable, - CD_Name)), + ofMethod(VIRTUAL, + ClassDesc.of(Domain.class.getName()), + "typeParameterElement", + MethodTypeDesc.of(CD_TypeParameterElement, + CD_Parameterizable, + CD_Name)), ((Constable)d).describeConstable().orElseThrow(), parameterizableDesc, nameDesc))); @@ -384,11 +395,11 @@ public static final Optional describe(final RecordCompon try (var lock = d.lock()) { yield describe((TypeElement)e.getEnclosingElement(), d) .map(executableDesc -> DynamicConstantDesc.of(BSM_INVOKE, - MethodHandleDesc.ofMethod(VIRTUAL, - ClassDesc.of(Domain.class.getName()), - "recordComponentElement", - MethodTypeDesc.of(CD_RecordComponentElement, - CD_ExecutableElement)), + ofMethod(VIRTUAL, + ClassDesc.of(Domain.class.getName()), + "recordComponentElement", + MethodTypeDesc.of(CD_RecordComponentElement, + CD_ExecutableElement)), ((Constable)d).describeConstable().orElseThrow(), executableDesc)); } @@ -418,11 +429,11 @@ public static final Optional describe(final VariableElem yield describe(e.getSimpleName(), d) .flatMap(nameDesc -> describe(e.getEnclosingElement(), d) .map(enclosingElementDesc -> DynamicConstantDesc.of(BSM_INVOKE, - MethodHandleDesc.ofMethod(VIRTUAL, - ClassDesc.of(Domain.class.getName()), - "variableElement", - MethodTypeDesc.of(CD_Element, - CD_CharSequence)), + ofMethod(VIRTUAL, + ClassDesc.of(Domain.class.getName()), + "variableElement", + MethodTypeDesc.of(CD_Element, + CD_CharSequence)), ((Constable)d).describeConstable().orElseThrow(), nameDesc))); } @@ -486,11 +497,11 @@ public static final Optional describe(final ArrayType t, try (var lock = d.lock()) { yield describe(t.getComponentType(), d) .map(componentTypeDesc -> DynamicConstantDesc.of(BSM_INVOKE, - MethodHandleDesc.ofMethod(VIRTUAL, - ClassDesc.of(Domain.class.getName()), - "arrayTypeOf", - MethodTypeDesc.of(CD_ArrayType, - CD_TypeMirror)), + ofMethod(VIRTUAL, + ClassDesc.of(Domain.class.getName()), + "arrayTypeOf", + MethodTypeDesc.of(CD_ArrayType, + CD_TypeMirror)), ((Constable)d).describeConstable().orElseThrow(), componentTypeDesc)); } @@ -527,14 +538,14 @@ yield switch (t.getKind()) { final int typeArgumentCount = typeArguments.size(); final ConstantDesc[] args = new ConstantDesc[4 + typeArgumentCount]; final TypeMirror enclosingType = t.getEnclosingType(); - // Call - args[0] = MethodHandleDesc.ofMethod(VIRTUAL, - ClassDesc.of(Domain.class.getName()), - "declaredType", - MethodTypeDesc.of(CD_DeclaredType, - CD_DeclaredType, - CD_TypeElement, - CD_TypeMirror.arrayType())); + // Call + args[0] = ofMethod(VIRTUAL, + ClassDesc.of(Domain.class.getName()), + "declaredType", + MethodTypeDesc.of(CD_DeclaredType, + CD_DeclaredType, + CD_TypeElement, + CD_TypeMirror.arrayType())); args[1] = domainDesc; args[2] = enclosingType.getKind() == TypeKind.NONE ? NULL : describe(enclosingType, d).orElse(null); if (args[2] == null) { @@ -585,11 +596,11 @@ public static final Optional describe(final NoType t, fi try (var lock = d.lock()) { yield t.getKind().describeConstable() .map(typeKindDesc -> DynamicConstantDesc.of(BSM_INVOKE, - MethodHandleDesc.ofMethod(VIRTUAL, - ClassDesc.of(Domain.class.getName()), - "noType", - MethodTypeDesc.of(CD_NoType, - CD_TypeKind)), + ofMethod(VIRTUAL, + ClassDesc.of(Domain.class.getName()), + "noType", + MethodTypeDesc.of(CD_NoType, + CD_TypeKind)), domainDesc, typeKindDesc)); } @@ -616,10 +627,10 @@ public static final Optional describe(final NullType t, case ConstantDesc cd -> Optional.of(cd); // future proofing? default -> (d instanceof Constable c ? c.describeConstable() : Optional.empty()) .map(domainDesc -> DynamicConstantDesc.of(BSM_INVOKE, - MethodHandleDesc.ofMethod(VIRTUAL, - ClassDesc.of(Domain.class.getName()), - "nullType", - MethodTypeDesc.of(CD_NullType)), + ofMethod(VIRTUAL, + ClassDesc.of(Domain.class.getName()), + "nullType", + MethodTypeDesc.of(CD_NullType)), domainDesc)); }; } @@ -649,11 +660,11 @@ public static final Optional describe(final PrimitiveTyp try (var lock = d.lock()) { yield t.getKind().describeConstable() .map(typeKindDesc -> DynamicConstantDesc.of(BSM_INVOKE, - MethodHandleDesc.ofMethod(VIRTUAL, - ClassDesc.of(Domain.class.getName()), - "primitiveType", - MethodTypeDesc.of(CD_PrimitiveType, - CD_TypeKind)), + ofMethod(VIRTUAL, + ClassDesc.of(Domain.class.getName()), + "primitiveType", + MethodTypeDesc.of(CD_PrimitiveType, + CD_TypeKind)), domainDesc, typeKindDesc)); } @@ -691,12 +702,12 @@ public static final Optional describe(final TypeVariable } final String name = d.toString(e.getSimpleName()); yield Optional.of(DynamicConstantDesc.of(BSM_INVOKE, - MethodHandleDesc.ofMethod(VIRTUAL, - ClassDesc.of(Domain.class.getName()), - "typeVariable", - MethodTypeDesc.of(CD_TypeVariable, - CD_Parameterizable, - CD_CharSequence)), + ofMethod(VIRTUAL, + ClassDesc.of(Domain.class.getName()), + "typeVariable", + MethodTypeDesc.of(CD_TypeVariable, + CD_Parameterizable, + CD_CharSequence)), domainDesc, parameterizableDesc, name)); @@ -728,13 +739,12 @@ yield describe(t.getExtendsBound(), d) .flatMap(domainDesc -> describe(t.getExtendsBound(), d) .flatMap(extendsBoundDesc -> describe(t.getSuperBound(), d) .map(superBoundDesc -> DynamicConstantDesc.of(BSM_INVOKE, - MethodHandleDesc.ofMethod(VIRTUAL, - ClassDesc.of(Domain.class.getName()), - "wildcardType", - MethodTypeDesc.of(CD_WildcardType, - CD_TypeMirror, - CD_TypeMirror)), - + ofMethod(VIRTUAL, + ClassDesc.of(Domain.class.getName()), + "wildcardType", + MethodTypeDesc.of(CD_WildcardType, + CD_TypeMirror, + CD_TypeMirror)), domainDesc, extendsBoundDesc, superBoundDesc)))); @@ -743,4 +753,134 @@ yield describe(t.getExtendsBound(), d) }; } + /** + * Returns a nominal descriptor for the supplied {@link List}, or an {@linkplain Optional#empty() empty} {@link + * Optional} if the supplied {@link List} cannot be described. + * + * @param the supplied {@code list}'s element type + * + * @param list a {@link List} to be described; may be {@code null}; if non-{@code null} must be immutable and + * must not contain {@code null} elements or undefined behavior will result + * + * @param f a {@link Function} that accepts an element from the supplied {@code list} and returns a non-{@code null} + * {@link Optional} housing a nominal descriptor for it, or a non-{@code null} {@linkplain Optional#empty() empty} + * {@link Optional} if the element cannot be described; must not be {@code null} + * + * @return a non-{@code null} {@link Optional} + * + * @exception NullPointerException if {@code f} is {@code null} and needs to be used + * + * @see List#of(Object...) + */ + public static final Optional describe(final List list, + final Function> f) { + return switch (list) { + case null -> Optional.of(NULL); + case Constable c -> c.describeConstable(); + case ConstantDesc cd -> Optional.of(cd); // future proofing? + case List el when el.isEmpty() -> + Optional.of(DynamicConstantDesc.of(BSM_INVOKE, + ofMethod(INTERFACE_STATIC, + CD_List, + "of", + MethodTypeDesc.of(CD_List)))); + default -> { + int size = list.size(); + assert size > 0; + final MethodTypeDesc ofMethodTypeDesc; + if (size <= 10) { + // List.of() has explicit polymorphic overrides for parameter counts of up to 10, inclusive. + final ClassDesc[] parameterArray = new ClassDesc[size]; + fill(parameterArray, CD_Object); + ofMethodTypeDesc = MethodTypeDesc.of(CD_List, parameterArray); + } else { + // After 10 parameters, List.of() falls back to varargs. + ofMethodTypeDesc = MethodTypeDesc.of(CD_List, CD_Object.arrayType()); + } + final ConstantDesc[] args = new ConstantDesc[++size]; + args[0] = ofMethod(INTERFACE_STATIC, CD_List, "of", ofMethodTypeDesc); + for (int i = 1; i < size; i++) { + final ConstantDesc eDesc = f.apply(list.get(i)).orElse(null); + if (eDesc == null) { + yield Optional.empty(); + } + args[i] = eDesc; + } + yield Optional.of(DynamicConstantDesc.of(BSM_INVOKE, args)); + } + }; + } + + /** + * Returns a nominal descriptor for the supplied {@link Map}, or an {@linkplain Optional#empty() empty} {@link + * Optional} if the supplied {@link Map} cannot be described. + * + * @param the supplied {@code map}'s key type + * + * @param the supplied {@code map}'s value type + * + * @param map a {@link Map} to be described; may be {@code null}; if non-{@code null} must be immutable and + * must not contain {@code null} keys or values or undefined behavior will result + * + * @param kf a {@link Function} that accepts a key from the supplied {@code map} and returns a non-{@code null} {@link + * Optional} housing a nominal descriptor for it, or a non-{@code null} {@linkplain Optional#empty() empty} {@link + * Optional} if the key cannot be described; must not be {@code null} + * + * @param vf a {@link Function} that accepts a value from the supplied {@code map} and returns a non-{@code null} + * {@link Optional} housing a nominal descriptor for it, or a non-{@code null} {@linkplain Optional#empty() empty} + * {@link Optional} if the value cannot be described; must not be {@code null} + * + * @return a non-{@code null} {@link Optional} + * + * @exception NullPointerException if {@code kf} or {@code vf} is {@code null} and needs to be used + * + * @see Map#of(Object, Object) + */ + public static Optional describe(final Map map, + final Function> kf, + final Function> vf) { + return switch (map) { + case null -> Optional.of(NULL); + case Constable c -> c.describeConstable(); + case ConstantDesc c -> Optional.of(c); // future proofing? + case Map em when em.isEmpty() -> + Optional.of(DynamicConstantDesc.of(BSM_INVOKE, + ofMethod(INTERFACE_STATIC, + CD_Map, + "of", + MethodTypeDesc.of(CD_Map)))); + default -> { + int size = 2 * map.size(); // 2 * entry size to account for key and value + assert size > 0; + final MethodTypeDesc ofMethodTypeDesc; + if (size <= 20) { + // Map.of() has explicit polymorphic overrides for (even) parameter counts of up to 20, inclusive. + final ClassDesc[] parameterArray = new ClassDesc[size]; + fill(parameterArray, CD_Object); + ofMethodTypeDesc = MethodTypeDesc.of(CD_Map, parameterArray); + } else { + // After 20 parameters, Map.of() falls back to varargs. + ofMethodTypeDesc = MethodTypeDesc.of(CD_Map, CD_Object.arrayType()); + } + final ConstantDesc[] args = new ConstantDesc[++size]; + args[0] = ofMethod(INTERFACE_STATIC, CD_Map, "of", ofMethodTypeDesc); + int i = 1; + for (final Entry e : map.entrySet()) { + final ConstantDesc kDesc = kf.apply(e.getKey()).orElse(null); + if (kDesc == null) { + yield Optional.empty(); + } + final ConstantDesc vDesc = vf.apply(e.getValue()).orElse(null); + if (vDesc == null) { + yield Optional.empty(); + } + args[i] = kDesc; + args[++i] = vDesc; + ++i; + } + yield Optional.of(DynamicConstantDesc.of(BSM_INVOKE, args)); + } + }; + } + } diff --git a/src/main/java/org/microbean/construct/element/StringName.java b/src/main/java/org/microbean/construct/element/StringName.java index 12da20e..0091bc7 100644 --- a/src/main/java/org/microbean/construct/element/StringName.java +++ b/src/main/java/org/microbean/construct/element/StringName.java @@ -26,36 +26,42 @@ import javax.lang.model.element.Name; -import org.microbean.construct.Domain; +import org.microbean.construct.PrimordialDomain; import static java.lang.constant.ConstantDescs.BSM_INVOKE; /** * A {@link Name} implementation based on {@link String}s. * + *

This {@link Name} implementation differs from {@link SyntheticName} in that it involves usage of the {@link + * PrimordialDomain#toString(Name)} method, which gives a {@link PrimordialDomain} implementation a chance to cache the + * underlying resulting {@link Name}.

+ * * @param value the actual name; must not be {@code null} * - * @param domain a {@link Domain}; must not be {@code null} + * @param domain a {@link PrimordialDomain}; must not be {@code null} * * @author Laird Nelson * * @see Name * - * @see Domain#toString(CharSequence) + * @see SyntheticName + * + * @see PrimordialDomain#toString(CharSequence) */ -public final record StringName(String value, Domain domain) implements Constable, Name { +public final record StringName(String value, PrimordialDomain domain) implements Constable, Name { /** * Creates a new {@link StringName}. * * @param value the actual name; must not be {@code null} * - * @param domain a {@link Domain}; must not be {@code null} + * @param domain a {@link PrimordialDomain}; must not be {@code null} * * @exception NullPointerException if either argument is {@code null} */ - public StringName(final CharSequence value, final Domain domain) { - // We deliberately route even String-typed values through Domain#toString(CharSequence) in case the Domain wishes to + public StringName(final CharSequence value, final PrimordialDomain domain) { + // We deliberately route even String-typed values through PrimordialDomain#toString(CharSequence) in case the PrimordialDomain wishes to // cache the intermediate Name. this(switch (value) { case StringName sn -> sn.value(); @@ -68,7 +74,7 @@ public StringName(final CharSequence value, final Domain domain) { * * @param value the actual name; must not be {@code null} * - * @param domain a {@link Domain}; must not be {@code null} + * @param domain a {@link PrimordialDomain}; must not be {@code null} * * @exception NullPointerException if either argument is {@code null} */ @@ -110,7 +116,7 @@ public final Optional describeConstable() { .map(domainDesc -> DynamicConstantDesc.of(BSM_INVOKE, MethodHandleDesc.ofConstructor(ClassDesc.of(this.getClass().getName()), ClassDesc.of(CharSequence.class.getName()), - ClassDesc.of(Domain.class.getName())), + ClassDesc.of(PrimordialDomain.class.getName())), this.value(), domainDesc)); } @@ -157,21 +163,21 @@ public final String toString() { /** * Returns a {@link StringName} whose {@link #value()} method will return a {@link String} {@linkplain - * String#equals(Object) equal to} the {@linkplain Domain#toString(CharSequence) String conversion of} - * the supplied {@link CharSequence}, and whose {@link #domain()} method will return a {@link Domain} {@linkplain - * #equals(Object) equal to} the supplied {@link Domain}. + * String#equals(Object) equal to} the {@linkplain PrimordialDomain#toString(CharSequence) String conversion of} + * the supplied {@link CharSequence}, and whose {@link #domain()} method will return a {@link PrimordialDomain} {@linkplain + * #equals(Object) equal to} the supplied {@link PrimordialDomain}. * * @param cs a {@link CharSequence}; must not be {@code null} * - * @param domain a {@link Domain}; must not be {@code null} + * @param domain a {@link PrimordialDomain}; must not be {@code null} * * @return a {@link StringName}; never {@code null} * * @exception NullPointerException if either argument is {@code null} * - * @see Domain#toString(CharSequence) + * @see PrimordialDomain#toString(CharSequence) */ - public static final StringName of(final CharSequence cs, final Domain domain) { + public static final StringName of(final CharSequence cs, final PrimordialDomain domain) { return cs instanceof StringName sn ? sn : new StringName(domain.toString(cs), domain); } diff --git a/src/main/java/org/microbean/construct/element/SyntheticAnnotationMirror.java b/src/main/java/org/microbean/construct/element/SyntheticAnnotationMirror.java new file mode 100644 index 0000000..e0fc4a3 --- /dev/null +++ b/src/main/java/org/microbean/construct/element/SyntheticAnnotationMirror.java @@ -0,0 +1,292 @@ +/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*- + * + * Copyright © 2025 microBean™. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package org.microbean.construct.element; + +import java.lang.annotation.Annotation; + +import java.lang.constant.ClassDesc; +import java.lang.constant.Constable; +import java.lang.constant.ConstantDesc; +import java.lang.constant.DynamicConstantDesc; +import java.lang.constant.MethodTypeDesc; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Optional; + +import java.util.function.Function; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.AnnotationValueVisitor; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; + +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeMirror; + +import org.microbean.construct.constant.Constables; + +import static java.lang.constant.ConstantDescs.BSM_INVOKE; +import static java.lang.constant.ConstantDescs.CD_Map; +import static java.lang.constant.ConstantDescs.CD_Object; +import static java.lang.constant.ConstantDescs.NULL; + +import static java.lang.constant.DirectMethodHandleDesc.Kind.INTERFACE_STATIC; + +import static java.lang.constant.MethodHandleDesc.ofConstructor; + +import static java.util.Arrays.fill; + +import static java.util.Collections.unmodifiableMap; + +import static java.util.LinkedHashMap.newLinkedHashMap; + +import static java.util.Objects.requireNonNull; + +import static javax.lang.model.element.ElementKind.ANNOTATION_TYPE; +import static javax.lang.model.element.ElementKind.METHOD; + +/** + * An experimental {@link AnnotationMirror} implementation that is partially or wholly synthetic. + * + * @author Laird Nelson + */ +public final class SyntheticAnnotationMirror implements AnnotationMirror, Constable { + + private final TypeElement annotationTypeElement; + + private final Map elementValues; + + /** + * Creates a new {@link SyntheticAnnotationMirror}. + * + * @param annotationTypeElement a {@link TypeElement} representing an annotation type; must not be {@code null}; must + * return {@link javax.lang.model.element.ElementKind#ANNOTATION_TYPE ANNOTATION_TYPE} from its {@link + * Element#getKind() getKind()} method; {@link SyntheticAnnotationTypeElement} implementations are strongly preferred + * + * @param values a {@link Map} of annotation values indexed by annotation element name; must not be {@code null}; must + * contain only values that are permissible for annotation elements + * + * @exception NullPointerException if any argument is {@code null} + * + * @exception IllegalArgumentException if {@code annotationTypeElement} does not return {@link + * javax.lang.model.element.ElementKind#ANNOTATION_TYPE ANNOTATION_TYPE} from an invocation of its {@link + * Element#getKind() getKind()} method, or if {@code values} has more entries in it than {@code annotationTypeElement} + * has {@linkplain Element#getEnclosedElements() anotation elements} + */ + public SyntheticAnnotationMirror(final TypeElement annotationTypeElement, + final Map values) { + super(); + if (annotationTypeElement.getKind() != ANNOTATION_TYPE) { + throw new IllegalArgumentException("annotationTypeElement: " + annotationTypeElement); + } + this.annotationTypeElement = annotationTypeElement; + final LinkedHashMap m = newLinkedHashMap(values.size()); + for (final Element e : annotationTypeElement.getEnclosedElements()) { + if (e.getKind() == METHOD) { + final Object value = values.get(e.getSimpleName().toString()); + if (value != null) { + m.put((ExecutableElement)e, value instanceof AnnotationValue av ? av : new SyntheticAnnotationValue(value)); + } + } + } + if (values.size() > m.size()) { + throw new IllegalArgumentException("values: " + values); + } + this.elementValues = unmodifiableMap(m); + } + + + /* + * Instance methods. + */ + + @Override // Constable + public final Optional describeConstable() { + return this.annotationTypeElement instanceof Constable c ? c.describeConstable() : Optional.empty() + .flatMap(elementDesc -> Constables.describe(this.elementValues, + SyntheticAnnotationMirror::describeExecutableElement, + SyntheticAnnotationMirror::describeAnnotationValue) + .map(valuesDesc -> DynamicConstantDesc.of(BSM_INVOKE, + ofConstructor(ClassDesc.of(this.getClass().getName()), + ClassDesc.of(TypeElement.class.getName()), + CD_Map), + elementDesc, + valuesDesc))); + } + + + @Override // AnnotationMirror + public final DeclaredType getAnnotationType() { + return (DeclaredType)this.annotationTypeElement.asType(); + } + + @Override // AnnotationMirror + public final Map getElementValues() { + return this.elementValues; + } + + /** + * An experimental {@link AnnotationValue} implementation that is partially or wholly synthetic. + * + * @author Laird Nelson + */ + public static final class SyntheticAnnotationValue implements AnnotationValue, Constable { + + // Will be one of: + // + // * AnnotationMirror + // * List + // * TypeMirror + // * VariableElement (ENUM_CONSTANT) + // * Boolean + // * Byte + // * Character + // * Double + // * Float + // * Integer + // * Long + // * Short + // * String + private final Object value; + + /** + * Creates a new {@link SyntheticAnnotationValue}. + * + * @param value the value; must not be {@code null}; must be a legal {@link AnnotationValue} type + * + * @exception NullPointerException if {@code value} is {@code null} + * + * @exception IllegalArgumentException if {@code value} is not a legal {@link AnnotationValue} type + * + * @see AnnotationValue + */ + public SyntheticAnnotationValue(final Object value) { + super(); + this.value = value(value); + } + + @Override // AnnotationValue + @SuppressWarnings("unchecked") + public final R accept(final AnnotationValueVisitor v, final P p) { + return switch (this.getValue()) { + case null -> v.visitUnknown(this, p); // ...or AssertionError? + case AnnotationMirror a -> v.visitAnnotation(a, p); + case List l -> v.visitArray((List)l, p); + case TypeMirror t -> v.visitType(t, p); + case VariableElement e -> v.visitEnumConstant(e, p); + case Boolean b -> v.visitBoolean(b, p); + case Byte b -> v.visitByte(b, p); + case Character c -> v.visitChar(c, p); + case Double d -> v.visitDouble(d, p); + case Float f -> v.visitFloat(f, p); + case Integer i -> v.visitInt(i, p); + case Long l -> v.visitLong(l, p); + case Short s -> v.visitShort(s, p); + case String s -> v.visitString(s, p); + default -> v.visitUnknown(this, p); + }; + } + + @Override // Constable + public final Optional describeConstable() { + return this.value instanceof Constable c ? c.describeConstable() : Optional.empty() + .map(valueDesc -> DynamicConstantDesc.of(BSM_INVOKE, + ofConstructor(ClassDesc.of(this.getClass().getName()), + CD_Object), + valueDesc)); + } + + @Override // Object + public final boolean equals(final Object other) { + return this == other || switch (other) { + case null -> false; + case SyntheticAnnotationValue sav when this.getClass() == sav.getClass() -> this.value.equals(sav.value); + default -> false; + }; + } + + @Override // AnnotationValue + public final Object getValue() { + return this.value; + } + + @Override // Object + public final int hashCode() { + return this.value.hashCode(); + } + + @Override // Object + public final String toString() { + return this.value.toString(); + } + + private static final Object value(final Object value) { + return switch (value) { + case null -> throw new NullPointerException("value"); + + case AnnotationValue av -> av.getValue(); // not part of the spec; just good hygiene + case List l -> l.stream().map(SyntheticAnnotationValue::new).toList(); + + case TypeMirror t -> switch (t.getKind()) { + case BOOLEAN, BYTE, CHAR, DECLARED, DOUBLE, FLOAT, INT, LONG, SHORT, VOID /* I think? */ -> t; + default -> throw new IllegalArgumentException("value: " + value); + }; + + case VariableElement e -> switch (e.getKind()) { + case ENUM_CONSTANT -> e; + default -> throw new IllegalArgumentException("value: " + value); + }; + + case AnnotationMirror a -> a; + case Boolean b -> b; + case Byte b -> b; + case Character c -> c; + case Double d -> d; + case Float f -> f; + case Integer i -> i; + case Long l -> l; + case Short s -> s; + case String s -> s; + default -> throw new IllegalArgumentException("value: " + value); + }; + } + + } + + private static final Optional describeAnnotationValue(final Object v) { + return switch (v) { + case null -> Optional.empty(); // deliberately not Optional.of(NULL); annotation values cannot be null + case Constable c -> c.describeConstable(); + case ConstantDesc cd -> Optional.of(cd); + case List l -> Constables.describe(l, e -> e instanceof Constable c ? c.describeConstable() : Optional.empty()); + default -> Optional.empty(); + }; + } + + private static final Optional describeExecutableElement(final ExecutableElement e) { + return switch (e) { + case null -> throw new IllegalStateException(); + case Constable c -> c.describeConstable(); + case ConstantDesc cd -> Optional.of(cd); + default -> Optional.empty(); + }; + } + +} diff --git a/src/main/java/org/microbean/construct/element/SyntheticAnnotationTypeElement.java b/src/main/java/org/microbean/construct/element/SyntheticAnnotationTypeElement.java new file mode 100644 index 0000000..4a94e5f --- /dev/null +++ b/src/main/java/org/microbean/construct/element/SyntheticAnnotationTypeElement.java @@ -0,0 +1,535 @@ +/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*- + * + * Copyright © 2025 microBean™. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package org.microbean.construct.element; + +import java.lang.annotation.Annotation; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ElementVisitor; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.Name; +import javax.lang.model.element.NestingKind; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.TypeParameterElement; +import javax.lang.model.element.VariableElement; + +import javax.lang.model.type.ArrayType; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.ExecutableType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.type.TypeVariable; +import javax.lang.model.type.TypeVisitor; + +import org.microbean.construct.type.NoneType; + +import static java.util.Collections.unmodifiableList; + +import static java.util.Objects.requireNonNull; + +import static javax.lang.model.element.ElementKind.ANNOTATION_TYPE; +import static javax.lang.model.element.ElementKind.CLASS; +import static javax.lang.model.element.ElementKind.ENUM; + +import static javax.lang.model.element.Modifier.ABSTRACT; + +import static javax.lang.model.type.TypeKind.ARRAY; +import static javax.lang.model.type.TypeKind.DECLARED; + +/** + * An experimental {@link TypeElement} implementation that is wholly synthetic and suitable only for + * (partially) modeling {@linkplain AnnotationMirror#getAnnotationType() annotation types}. + * + * @author Laird Nelson + * + * @see SyntheticAnnotationMirror + */ +public final class SyntheticAnnotationTypeElement implements TypeElement { + + private static final Set modifiers = Set.of(ABSTRACT); + + private final List annotationMirrors; + + private final SyntheticName fqn; + + private final Type type; + + private final List elements; + + /** + * Creates a new {@link SyntheticAnnotationTypeElement}. + * + * @param annotationMirrors a {@link List} of {@link AnnotationMirror}s modeling the annotations this element has; + * must not be {@code null} + * + * @param fullyQualifiedName the fully qualified name of the synthetic element; must conform to Java classname + * restrictions; must not be {@code null} + * + * @param elements a {@link List} of {@link SyntheticAnnotationElement}s modeling the annotation elements; must not be + * {@code null} + * + * @exception NullPointerException if any argument is {@code null} + * + * @exception IllegalArgumentException if {@code fullyQualifiedName} does not conform to Java classname restrictions + * + * @see SyntheticAnnotationElement + * + * @see SyntheticAnnotationMirror + */ + public SyntheticAnnotationTypeElement(final List annotationMirrors, + final String fullyQualifiedName, + final List elements) { + super(); + this.annotationMirrors = List.copyOf(annotationMirrors); + this.fqn = SyntheticName.of(fullyQualifiedName); + this.type = new Type(); + final List elements0 = new ArrayList<>(elements.size()); + for (final SyntheticAnnotationElement e : elements) { + elements0.add(new InternalAnnotationElement(e.annotationMirrors(), e.type(), e.name())); + } + this.elements = unmodifiableList(elements0); + } + + @Override // Element + public final R accept(final ElementVisitor v, final P p) { + return v.visitType(this, p); + } + + @Override // Element + public final TypeMirror asType() { + return this.type; + } + + @Override // AnnotatedConstruct + public final List getAnnotationMirrors() { + return this.annotationMirrors; + } + + @Override // AnnotatedConstruct + public final A getAnnotation(final Class annotationType) { + throw new UnsupportedOperationException(); // deliberate + } + + @Override // AnnotatedConstruct + public final A[] getAnnotationsByType(final Class annotationType) { + throw new UnsupportedOperationException(); // deliberate + } + + @Override + public final List getEnclosedElements() { + return this.elements; + } + + @Override + public final Element getEnclosingElement() { + return null; // really should be a package but we're synthetic + } + + @Override + public final List getInterfaces() { + return List.of(); + } + + @Override + public final Set getModifiers() { + return modifiers; + } + + @Override + public final ElementKind getKind() { + return ElementKind.ANNOTATION_TYPE; + } + + @Override + public final NestingKind getNestingKind() { + return NestingKind.TOP_LEVEL; + } + + @Override + public final Name getQualifiedName() { + return this.fqn; + } + + @Override + public final Name getSimpleName() { + final String fqn = this.getQualifiedName().toString(); + final int i = fqn.lastIndexOf('.'); + return i >= 0 ? SyntheticName.of(fqn.substring(i + 1)) : this.getQualifiedName(); + } + + @Override + public final TypeMirror getSuperclass() { + return NoneType.of(); + } + + @Override + public final List getTypeParameters() { + return List.of(); + } + + @Override + public final String toString() { + return "@" + this.getQualifiedName().toString(); // TODO: robustify + } + + /** + * An experimental collection of information out of which a synthetic annotation element may be + * fashioned. + * + * @author Laird Nelson + */ + public static final class SyntheticAnnotationElement { + + private final List annotationMirrors; + + private final TypeMirror type; + + private final String name; + + /** + * Creates a new {@link SyntheticAnnotationElement}. + * + * @param annotationMirrors a {@link List} of {@link AnnotationMirror}s modeling the annotations this element has; + * must not be {@code null} + * + * @param type the type of the annotation element; must conform to Java annotation element type restrictions; must + * not be {@code null} + * + * @param name the name of the annotation element; must conform to Java method naming requirements; must not be + * {@code null} + * + * @exception NullPointerException if any argument is {@code null} + * + * @exception IllegalArgumentException if any argument does not conform to its requirements + */ + public SyntheticAnnotationElement(final List annotationMirrors, + final TypeMirror type, // the "return type" + final String name) { + super(); + this.annotationMirrors = List.copyOf(annotationMirrors); + this.type = requireNonNull(type, "type"); + this.name = requireNonNull(name, "name"); + } + + final List annotationMirrors() { + return this.annotationMirrors; + } + + @Override // Object + public final boolean equals(final Object other) { + return this == other || switch (other) { + case null -> false; + case SyntheticAnnotationElement sae when this.getClass() == sae.getClass() -> + Objects.equals(this.name, sae.name) && + Objects.equals(this.type, sae.type) && + Objects.equals(this.annotationMirrors, sae.annotationMirrors); + default -> false; + }; + } + + @Override // Object + public final int hashCode() { + int hashCode = 31; + int c = this.name.hashCode(); + hashCode = 17 * hashCode + c; + c = this.type.hashCode(); + hashCode = 17 * hashCode + c; + c = this.annotationMirrors.hashCode(); + hashCode = 17 * hashCode + c; + return hashCode; + } + + final String name() { + return this.name; + } + + final TypeMirror type() { + return this.type; + } + + } + + /** + * An experimental, synthetic {@link DeclaredType} implementation that models the synthetic + * annotation type declared by the enclosing synthetic {@link SyntheticAnnotationTypeElement}. + * + * @author Laird Nelson + * + * @see SyntheticAnnotationTypeElement + */ + private class Type implements DeclaredType { + + private static final Annotation[] EMPTY_ANNOTATION_ARRAY = new Annotation[0]; + + private Type() { + super(); + } + + @Override // TypeMirror + public final R accept(final TypeVisitor v, final P p) { + return v.visitDeclared(this, p); + } + + @Override // ExecutableType (AnnotatedConstruct) + public final A getAnnotation(final Class annotationType) { + return null; // deliberate + } + + @Override // ExecutableType (AnnotatedConstruct) + public final List getAnnotationMirrors() { + return List.of(); // deliberate + } + + @Override // ExecutableType (AnnotatedConstruct) + @SuppressWarnings("unchecked") + public final A[] getAnnotationsByType(final Class annotationType) { + return (A[])EMPTY_ANNOTATION_ARRAY; // deliberate + } + + @Override // DeclaredType (TypeMirror) + public final TypeKind getKind() { + return TypeKind.DECLARED; + } + + @Override + public final TypeMirror getEnclosingType() { + return NoneType.of(); + } + + @Override // DeclaredType (TypeMirror) + public final List getTypeArguments() { + return List.of(); + } + + @Override // DeclaredType + public final SyntheticAnnotationTypeElement asElement() { + return SyntheticAnnotationTypeElement.this; + } + + @Override + public final String toString() { + return this.asElement().toString(); + } + + } + + private final class InternalAnnotationElement implements ExecutableElement { + + private final List annotationMirrors; + + private final Type t; + + private final SyntheticName name; + + private InternalAnnotationElement(final List annotationMirrors, + final TypeMirror type, + final String name) { + super(); + this.annotationMirrors = List.copyOf(annotationMirrors); + this.t = new Type(type); + this.name = SyntheticName.of(name); + } + + @Override // Element + public final R accept(final ElementVisitor v, final P p) { + return v.visitExecutable(this, p); + } + + @Override // Element + public final Type asType() { + return this.t; + } + + @Override // AnnotatedConstruct + public final List getAnnotationMirrors() { + return this.annotationMirrors; + } + + @Override // AnnotatedConstruct + public final A getAnnotation(final Class annotationType) { + throw new UnsupportedOperationException(); // deliberate + } + + @Override // AnnotatedConstruct + public final A[] getAnnotationsByType(final Class annotationType) { + throw new UnsupportedOperationException(); // deliberate + } + + @Override + public AnnotationValue getDefaultValue() { + return null; // deliberate + } + + @Override + public final List getEnclosedElements() { + return List.of(); + } + + @Override + public final SyntheticAnnotationTypeElement getEnclosingElement() { + return SyntheticAnnotationTypeElement.this; + } + + @Override + public final Set getModifiers() { + throw new UnsupportedOperationException(); + } + + @Override + public final ElementKind getKind() { + return ElementKind.METHOD; + } + + @Override + public final List getParameters() { + return List.of(); + } + + @Override + public final TypeMirror getReceiverType() { + return asType().getReceiverType(); + } + + @Override + public final TypeMirror getReturnType() { + return asType().getReturnType(); + } + + @Override + public final Name getSimpleName() { + return this.name; + } + + @Override + public final List getThrownTypes() { + return asType().getThrownTypes(); + } + + @Override + public final List getTypeParameters() { + return List.of(); + } + + @Override + public final boolean isDefault() { + return false; + } + + @Override + public final boolean isVarArgs() { + return false; + } + + private static final class Type implements ExecutableType { + + private final TypeMirror type; + + private Type(final TypeMirror type) { + super(); + this.type = validate(type); + } + + private static TypeMirror validate(final TypeMirror type) { + TypeMirror t = type; + TypeKind k = t.getKind(); + if (k == ARRAY && t instanceof ArrayType a) { + t = a.getComponentType(); + k = t.getKind(); + } + if (k.isPrimitive()) { + return type; + } + if (k == DECLARED) { + final TypeElement e = (TypeElement)((DeclaredType)t).asElement(); + switch (e.getKind()) { + case ANNOTATION_TYPE: + case ENUM: + return type; + case CLASS: + final Name fqn = e.getQualifiedName(); + if (fqn.contentEquals("java.lang.String") || fqn.contentEquals("java.lang.Class")) { + return type; + } + break; + default: + break; + } + } + throw new IllegalArgumentException("type: " + type); + } + + @Override // TypeMirror + public final R accept(final TypeVisitor v, final P p) { + return v.visitExecutable(this, p); + } + + @Override // ExecutableType (AnnotatedConstruct) + public final A getAnnotation(final Class annotationType) { + throw new UnsupportedOperationException(); // deliberate + } + + @Override // ExecutableType (AnnotatedConstruct) + public final List getAnnotationMirrors() { + return List.of(); // can't actually put type annotations on an executable type to my knowledge + } + + @Override // ExecutableType (AnnotatedConstruct) + public final A[] getAnnotationsByType(final Class annotationType) { + throw new UnsupportedOperationException(); // deliberate + } + + @Override // ExecutableType (TypeMirror) + public final TypeKind getKind() { + return TypeKind.EXECUTABLE; + } + + @Override // ExecutableType + public final List getParameterTypes() { + return List.of(); + } + + @Override // ExecutableType + public final TypeMirror getReceiverType() { + return NoneType.of(); + } + + @Override // ExecutableType + public final TypeMirror getReturnType() { + return this.type; + } + + @Override // ExecutableType + public final List getThrownTypes() { + return List.of(); + } + + @Override // ExecutableType + public final List getTypeVariables() { + return List.of(); + } + + } + + } + +} diff --git a/src/main/java/org/microbean/construct/element/SyntheticName.java b/src/main/java/org/microbean/construct/element/SyntheticName.java new file mode 100644 index 0000000..116132a --- /dev/null +++ b/src/main/java/org/microbean/construct/element/SyntheticName.java @@ -0,0 +1,147 @@ +/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*- + * + * Copyright © 2025 microBean™. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package org.microbean.construct.element; + +import java.lang.constant.ClassDesc; +import java.lang.constant.Constable; +import java.lang.constant.DynamicConstantDesc; +import java.lang.constant.MethodHandleDesc; +import java.lang.constant.MethodTypeDesc; + +import java.util.Objects; +import java.util.Optional; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import java.util.stream.IntStream; + +import javax.lang.model.element.Name; + +import static java.lang.constant.ConstantDescs.BSM_INVOKE; +import static java.lang.constant.ConstantDescs.CD_String; + +import static java.lang.constant.DirectMethodHandleDesc.Kind.STATIC; + +import static java.util.Objects.requireNonNull; + +/** + * A {@link Name} implementation based on {@link String}s. + * + *

This {@link Name} implementation differs from {@link StringName} in that there is no {@link + * org.microbean.construct.PrimordialDomain} involved, and therefore no notion of any kind of delegate.

+ * + * @author
Laird Nelson + * + * @see Name + * + * @see StringName + */ +public final class SyntheticName implements Constable, Name { + + private static final ConcurrentMap names = new ConcurrentHashMap<>(); + + private final String value; + + /** + * Creates a new {@link SyntheticName}. + * + * @param value the actual name; must not be {@code null} + * + * @exception NullPointerException if {@code value} is {@code null} + */ + private SyntheticName(final String value) { + super(); + this.value = requireNonNull(value, "value"); + } + + @Override // Name (CharSequence) + public final char charAt(final int index) { + return this.value.charAt(index); + } + + @Override // Name (CharSequence) + public final IntStream chars() { + return this.value.chars(); + } + + @Override // Name (CharSequence) + public final IntStream codePoints() { + return this.value.codePoints(); + } + + @Override // Name + public final boolean contentEquals(final CharSequence cs) { + return this == cs || cs != null && this.value.contentEquals(cs.toString()); + } + + @Override // Constable + public final Optional> describeConstable() { + return + Optional.of(DynamicConstantDesc.of(BSM_INVOKE, + MethodHandleDesc.ofMethod(STATIC, + ClassDesc.of(this.getClass().getName()), + "of", + MethodTypeDesc.of(CD_String)), + this.value)); + } + + @Override // Object + public final boolean equals(final Object other) { + return this == other || switch (other) { + case null -> false; + case SyntheticName sn when this.getClass() == sn.getClass() -> Objects.equals(this.value, sn.value); + default -> false; + }; + } + + @Override // Object + public final int hashCode() { + return this.value.hashCode(); + } + + @Override // Name (CharSequence) + public final boolean isEmpty() { + return this.value.isEmpty(); + } + + @Override // Name (CharSequence) + public final int length() { + return this.value.length(); + } + + @Override // Name (CharSequence) + public final CharSequence subSequence(final int start, final int end) { + return this.value.subSequence(start, end); + } + + @Override // Name (CharSequence) + public final String toString() { + return this.value; + } + + /** + * Returns a (non-{@code null}) {@link SyntheticName} representing the supplied {@code name}. + * + * @param name a {@link String}; must not be {@code null} + * + * @return a {@link SyntheticName}; never {@code null} + * + * @exception NullPointerException if {@code name} is {@code null} + */ + public static final SyntheticName of(final String name) { + return names.computeIfAbsent(name, SyntheticName::new); + } + +} diff --git a/src/main/java/org/microbean/construct/element/UniversalAnnotation.java b/src/main/java/org/microbean/construct/element/UniversalAnnotation.java index a874152..2e9c705 100644 --- a/src/main/java/org/microbean/construct/element/UniversalAnnotation.java +++ b/src/main/java/org/microbean/construct/element/UniversalAnnotation.java @@ -13,6 +13,12 @@ */ package org.microbean.construct.element; +import java.lang.constant.ClassDesc; +import java.lang.constant.Constable; +import java.lang.constant.ConstantDesc; +import java.lang.constant.DynamicConstantDesc; +import java.lang.constant.MethodTypeDesc; + import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -21,6 +27,7 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.function.Supplier; @@ -29,10 +36,17 @@ import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.ExecutableElement; -import org.microbean.construct.Domain; +import org.microbean.construct.PrimordialDomain; +import org.microbean.construct.UniversalConstruct; import org.microbean.construct.type.UniversalType; +import static java.lang.constant.ConstantDescs.BSM_INVOKE; + +import static java.lang.constant.DirectMethodHandleDesc.Kind.STATIC; + +import static java.lang.constant.MethodHandleDesc.ofMethod; + import static java.util.Objects.requireNonNull; /** @@ -42,7 +56,7 @@ * * @see AnnotationMirror */ -public final class UniversalAnnotation implements AnnotationMirror { +public final class UniversalAnnotation implements AnnotationMirror, Constable { /* @@ -54,7 +68,7 @@ public final class UniversalAnnotation implements AnnotationMirror { // volatile not needed private Supplier delegateSupplier; - private final Domain domain; + private final PrimordialDomain domain; /* @@ -67,12 +81,12 @@ public final class UniversalAnnotation implements AnnotationMirror { * * @param delegate an {@link AnnotationMirror} to which operations will be delegated; must not be {@code null} * - * @param domain a {@link Domain}; must not be {@code null} + * @param domain a {@link PrimordialDomain}; must not be {@code null} * * @exception NullPointerException if either argument is {@code null} */ @SuppressWarnings("try") - public UniversalAnnotation(final AnnotationMirror delegate, final Domain domain) { + public UniversalAnnotation(final AnnotationMirror delegate, final PrimordialDomain domain) { super(); this.domain = requireNonNull(domain, "domain"); final AnnotationMirror unwrappedDelegate = unwrap(requireNonNull(delegate, "delegate")); @@ -111,12 +125,27 @@ public final AnnotationMirror delegate() { return delegate; } + @Override // Constable + public Optional describeConstable() { + return this.domain instanceof Constable c0 ? c0.describeConstable() : Optional.empty() + .flatMap(primordialDomainDesc -> this.delegate() instanceof Constable c1 ? c1.describeConstable() : Optional.empty() + .map(delegateDesc -> DynamicConstantDesc.of(BSM_INVOKE, + ofMethod(STATIC, + ClassDesc.of(this.getClass().getName()), + "of", + MethodTypeDesc.of(ClassDesc.of(this.getClass().getName()), + ClassDesc.of(AnnotationMirror.class.getName()), + ClassDesc.of(PrimordialDomain.class.getName()))), + delegateDesc, + primordialDomainDesc))); + } + /** - * Returns the {@link Domain} supplied at construction time. + * Returns the {@link PrimordialDomain} supplied at construction time. * - * @return the non-{@code null} {@link Domain} supplied at construction time + * @return the non-{@code null} {@link PrimordialDomain} supplied at construction time */ - public final Domain domain() { + public final PrimordialDomain domain() { return this.domain; } @@ -144,7 +173,7 @@ public final UniversalType getAnnotationType() { @SuppressWarnings("try") public final Map getElementValues() { final Map map = new LinkedHashMap<>(17); - final Domain d = this.domain(); + final PrimordialDomain d = this.domain(); // TODO: is this lock actually needed, given how delegateSupplier works? // try (var lock = d.lock()) { for (final Entry e : this.delegate().getElementValues().entrySet()) { @@ -173,15 +202,15 @@ public final int hashCode() { * * @param a an {@link AnnotationMirror}; must not be {@code null} * - * @param d a {@link Domain}; must not be {@code null} + * @param d a {@link PrimordialDomain}; must not be {@code null} * * @return a non-{@code null} {@link UniversalAnnotation} * * @exception NullPointerException if either argument is {@code null} * - * @see #UniversalAnnotation(AnnotationMirror, Domain) + * @see #UniversalAnnotation(AnnotationMirror, PrimordialDomain) */ - public static final UniversalAnnotation of(final AnnotationMirror a, final Domain d) { + public static final UniversalAnnotation of(final AnnotationMirror a, final PrimordialDomain d) { return a instanceof UniversalAnnotation ar ? ar : new UniversalAnnotation(a, d); } @@ -191,14 +220,14 @@ public static final UniversalAnnotation of(final AnnotationMirror a, final Domai * * @param as a {@link Collection} of {@link AnnotationMirror}s; must not be {@code null} * - * @param domain a {@link Domain}; must not be {@code null} + * @param domain a {@link PrimordialDomain}; must not be {@code null} * * @return a non-{@code null}, immutable {@link List} of {@link UniversalAnnotation}s * * @exception NullPointerException if either argument is {@code null} */ public static final List of(final Collection as, - final Domain domain) { + final PrimordialDomain domain) { if (as.isEmpty()) { return List.of(); } diff --git a/src/main/java/org/microbean/construct/element/UniversalAnnotationValue.java b/src/main/java/org/microbean/construct/element/UniversalAnnotationValue.java index 47a1353..4a66251 100644 --- a/src/main/java/org/microbean/construct/element/UniversalAnnotationValue.java +++ b/src/main/java/org/microbean/construct/element/UniversalAnnotationValue.java @@ -28,7 +28,7 @@ import javax.lang.model.type.TypeMirror; -import org.microbean.construct.Domain; +import org.microbean.construct.PrimordialDomain; import org.microbean.construct.type.UniversalType; @@ -48,7 +48,7 @@ public final class UniversalAnnotationValue implements AnnotationValue { // volatile not needed private Supplier delegateSupplier; - private final Domain domain; + private final PrimordialDomain domain; private String s; @@ -65,12 +65,12 @@ public final class UniversalAnnotationValue implements AnnotationValue { * * @param delegate an {@link AnnotationValue} to which operations will be delegated; must not be {@code null} * - * @param domain a {@link Domain}; must not be {@code null} + * @param domain a {@link PrimordialDomain}; must not be {@code null} * * @exception NullPointerException if either argument is {@code null} */ @SuppressWarnings("try") - public UniversalAnnotationValue(final AnnotationValue delegate, final Domain domain) { + public UniversalAnnotationValue(final AnnotationValue delegate, final PrimordialDomain domain) { super(); this.domain = requireNonNull(domain, "domain"); final AnnotationValue unwrappedDelegate = unwrap(requireNonNull(delegate, "delegate")); @@ -142,11 +142,11 @@ public final AnnotationValue delegate() { } /** - * Returns the {@link Domain} supplied at construction time. + * Returns the {@link PrimordialDomain} supplied at construction time. * - * @return the non-{@code null} {@link Domain} supplied at construction time + * @return the non-{@code null} {@link PrimordialDomain} supplied at construction time */ - public final Domain domain() { + public final PrimordialDomain domain() { return this.domain; } @@ -165,7 +165,7 @@ public final boolean equals(final Object other) { @Override // AnnotationValue @SuppressWarnings("unchecked") public final Object getValue() { - final Domain domain = this.domain(); + final PrimordialDomain domain = this.domain(); final Object value = this.v; return switch (value) { case null -> throw new AssertionError(); @@ -203,15 +203,15 @@ public final String toString() { * * @param av an {@link AnnotationValue}; may be {@code null} * - * @param domain a {@link Domain}; must not be {@code null} + * @param domain a {@link PrimordialDomain}; must not be {@code null} * * @return an {@link UniversalAnnotationValue}, or {@code null} (if {@code av} is {@code null}) * * @exception NullPointerException if {@code domain} is {@code null} * - * @see #UniversalAnnotationValue(AnnotationValue, Domain) + * @see #UniversalAnnotationValue(AnnotationValue, PrimordialDomain) */ - public static final UniversalAnnotationValue of(final AnnotationValue av, final Domain domain) { + public static final UniversalAnnotationValue of(final AnnotationValue av, final PrimordialDomain domain) { return switch (av) { case null -> null; case UniversalAnnotationValue uav -> uav; @@ -225,14 +225,14 @@ public static final UniversalAnnotationValue of(final AnnotationValue av, final * * @param avs a {@link Collection} of {@link AnnotationValue}s; must not be {@code null} * - * @param domain a {@link Domain}; must not be {@code null} + * @param domain a {@link PrimordialDomain}; must not be {@code null} * * @return a non-{@code null}, immutable {@link List} of {@link UniversalAnnotationValue}s * * @exception NullPointerException if either argument is {@code null} */ public static final List of(final Collection avs, - final Domain domain) { + final PrimordialDomain domain) { if (avs.isEmpty()) { return List.of(); } diff --git a/src/main/java/org/microbean/construct/element/UniversalDirective.java b/src/main/java/org/microbean/construct/element/UniversalDirective.java index 7474e82..94d4934 100644 --- a/src/main/java/org/microbean/construct/element/UniversalDirective.java +++ b/src/main/java/org/microbean/construct/element/UniversalDirective.java @@ -30,7 +30,7 @@ import javax.lang.model.element.ModuleElement.RequiresDirective; import javax.lang.model.element.ModuleElement.UsesDirective; -import org.microbean.construct.Domain; +import org.microbean.construct.PrimordialDomain; /** * A {@link Directive} implementation. @@ -42,7 +42,7 @@ public final class UniversalDirective implements ExportsDirective, OpensDirective, ProvidesDirective, RequiresDirective, UsesDirective { - private final Domain domain; + private final PrimordialDomain domain; // volatile not needed private Supplier delegateSupplier; @@ -52,13 +52,13 @@ public final class UniversalDirective * * @param delegate a {@link Directive} to which operations will be delegated; must not be {@code null} * - * @param domain a {@link Domain} from which the supplied {@code delegate} is presumed to have originated; must not be + * @param domain a {@link PrimordialDomain} from which the supplied {@code delegate} is presumed to have originated; must not be * {@code null} * * @exception NullPointerException if either argument is {@code null} */ @SuppressWarnings("try") - public UniversalDirective(final Directive delegate, final Domain domain) { + public UniversalDirective(final Directive delegate, final PrimordialDomain domain) { super(); this.domain = Objects.requireNonNull(domain, "domain"); final Directive unwrappedDelegate = unwrap(Objects.requireNonNull(delegate, "delegate")); @@ -192,15 +192,15 @@ public final String toString() { * * @param d an {@link Directive}; may be {@code null} in which case {@code null} will be returned * - * @param domain a {@link Domain}; must not be {@code null} + * @param domain a {@link PrimordialDomain}; must not be {@code null} * * @return a {@link UniversalDirective}, or {@code null} (if {@code e} is {@code null}) * * @exception NullPointerException if {@code domain} is {@code null} * - * @see #UniversalDirective(Directive, Domain) + * @see #UniversalDirective(Directive, PrimordialDomain) */ - public static final UniversalDirective of(final Directive d, final Domain domain) { + public static final UniversalDirective of(final Directive d, final PrimordialDomain domain) { return switch (d) { case null -> null; case UniversalDirective ud -> ud; @@ -214,13 +214,13 @@ public static final UniversalDirective of(final Directive d, final Domain domain * * @param es a {@link Collection} of {@link Directive}s; must not be {@code null} * - * @param domain a {@link Domain}; must not be {@code null} + * @param domain a {@link PrimordialDomain}; must not be {@code null} * * @return a non-{@code null}, immutable {@link List} of {@link UniversalDirective}s * * @exception NullPointerException if either argument is {@code null} */ - public static final List of(final Collection es, final Domain domain) { + public static final List of(final Collection es, final PrimordialDomain domain) { if (es.isEmpty()) { return List.of(); } diff --git a/src/main/java/org/microbean/construct/element/UniversalElement.java b/src/main/java/org/microbean/construct/element/UniversalElement.java index 8840957..1832d4b 100644 --- a/src/main/java/org/microbean/construct/element/UniversalElement.java +++ b/src/main/java/org/microbean/construct/element/UniversalElement.java @@ -22,6 +22,7 @@ import java.util.function.Supplier; +import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ElementVisitor; @@ -41,7 +42,7 @@ import javax.lang.model.type.TypeMirror; import org.microbean.construct.UniversalConstruct; -import org.microbean.construct.Domain; +import org.microbean.construct.PrimordialDomain; import org.microbean.construct.type.UniversalType; @@ -56,7 +57,7 @@ */ @SuppressWarnings("preview") public final class UniversalElement - extends UniversalConstruct + extends UniversalConstruct implements ExecutableElement, ModuleElement, PackageElement, @@ -73,16 +74,36 @@ public final class UniversalElement * * @param delegate an {@link Element} to which operations will be delegated; must not be {@code null} * - * @param domain a {@link Domain} from which the supplied {@code delegate} is presumed to have originated; must not be - * {@code null} + * @param domain a {@link PrimordialDomain} from which the supplied {@code delegate} is presumed to have originated; + * must not be {@code null} * * @exception NullPointerException if either argument is {@code null} + */ + public UniversalElement(final Element delegate, final PrimordialDomain domain) { + this(delegate, null, domain); + } + + /** + * Creates a new {@link UniversalElement}. + * + * @param delegate an {@link Element} to which operations will be delegated; must not be {@code null} + * + * @param annotations a {@link List} of {@link AnnotationMirror} instances representing annotations, often synthetic, + * that this {@link UniversalElement} should bear; may be {@code null} in which case only the annotations from the + * supplied {@code delegate} will be used + * + * @param domain a {@link PrimordialDomain} from which the supplied {@code delegate} is presumed to have originated; + * must not be {@code null} + * + * @exception NullPointerException if any argument is {@code null} * * @see #delegate() */ @SuppressWarnings("try") - public UniversalElement(final Element delegate, final Domain domain) { - super(delegate, domain); + private UniversalElement(final Element delegate, + final List annotations, + final PrimordialDomain domain) { + super(delegate, annotations, domain); this.enclosedElementsSupplier = () -> { final List ees; try (var lock = domain.lock()) { @@ -107,7 +128,8 @@ public final R accept(final ElementVisitor v, final P p) { BINDING_VARIABLE, ENUM_CONSTANT, EXCEPTION_PARAMETER, - FIELD, LOCAL_VARIABLE, + FIELD, + LOCAL_VARIABLE, PARAMETER, RESOURCE_VARIABLE -> v.visitVariable(this, p); case RECORD_COMPONENT -> v.visitRecordComponent(this, p); @@ -122,6 +144,11 @@ public final R accept(final ElementVisitor v, final P p) { }; } + @Override // UniversalConstruct> + protected final UniversalElement annotate(final List replacementAnnotations) { + return new UniversalElement(this.delegate(), replacementAnnotations, this.domain()); + } + @Override // Element public final UniversalType asType() { return UniversalType.of(this.delegate().asType(), this.domain()); @@ -394,13 +421,13 @@ private final List wrap(final Collection of(final Collection es, final Domain domain) { + public static final List of(final Collection es, final PrimordialDomain domain) { if (es.isEmpty()) { return List.of(); } @@ -417,15 +444,15 @@ public static final List of(final Collection null; case UniversalElement ue -> ue; diff --git a/src/main/java/org/microbean/construct/type/NoneType.java b/src/main/java/org/microbean/construct/type/NoneType.java new file mode 100644 index 0000000..912597d --- /dev/null +++ b/src/main/java/org/microbean/construct/type/NoneType.java @@ -0,0 +1,80 @@ +/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*- + * + * Copyright © 2025 microBean™. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package org.microbean.construct.type; + +import java.lang.annotation.Annotation; + +import java.util.List; + +import javax.lang.model.element.AnnotationMirror; + +import javax.lang.model.type.NoType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeVisitor; + +/** + * A useful implementation of {@link NoType} with a {@link TypeKind} of {@link TypeKind#NONE NONE}. + * + * @author Laird Nelson + */ +public final class NoneType implements NoType { + + private static final NoneType INSTANCE = new NoneType(); + + private static final Annotation[] EMPTY_ANNOTATION_ARRAY = new Annotation[0]; + + private NoneType() { + super(); + } + + @Override // TypeMirror + public final R accept(final TypeVisitor v, final P p) { + return v.visitNoType(this, p); + } + + @Override // NoType (AnnotatedConstruct) + public final A getAnnotation(final Class annotationType) { + return null; + } + + @Override // NoType (AnnotatedConstruct) + public final List getAnnotationMirrors() { + return List.of(); + } + + @Override // NoType (AnnotatedConstruct) + @SuppressWarnings("unchecked") + public final A[] getAnnotationsByType(final Class annotationType) { + return (A[])EMPTY_ANNOTATION_ARRAY; + } + + @Override // NoType (TypeMirror) + public final TypeKind getKind() { + return TypeKind.NONE; + } + + /** + * Returns a {@link NoneType}. + * + * @return a non-{@code null} {@link NoneType} + * + * @see NoType + * + * @see TypeKind#NONE + */ + public static final NoneType of() { + return INSTANCE; + } + +} diff --git a/src/main/java/org/microbean/construct/type/UniversalType.java b/src/main/java/org/microbean/construct/type/UniversalType.java index 7f587fe..a45f3cf 100644 --- a/src/main/java/org/microbean/construct/type/UniversalType.java +++ b/src/main/java/org/microbean/construct/type/UniversalType.java @@ -19,6 +19,8 @@ import java.util.List; import java.util.Optional; +import javax.lang.model.element.AnnotationMirror; + import javax.lang.model.type.ArrayType; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.ErrorType; @@ -35,8 +37,9 @@ import javax.lang.model.type.WildcardType; import org.microbean.construct.UniversalConstruct; -import org.microbean.construct.Domain; +import org.microbean.construct.PrimordialDomain; +import org.microbean.construct.element.UniversalAnnotation; import org.microbean.construct.element.UniversalElement; import static javax.lang.model.type.TypeKind.DECLARED; @@ -53,7 +56,7 @@ * @see UniversalConstruct */ public final class UniversalType - extends UniversalConstruct + extends UniversalConstruct implements ArrayType, ErrorType, ExecutableType, @@ -65,15 +68,12 @@ public final class UniversalType UnionType, WildcardType { - // Eventually this should become a lazy constant/stable value - private volatile UniversalType erasure; - /** * Creates a new {@link UniversalType}. * * @param delegate a {@link TypeMirror} to which operations will be delegated; must not be {@code null} * - * @param domain a {@link Domain} from which the supplied {@code delegate} is presumed to have originated; must not be + * @param domain a {@link PrimordialDomain} from which the supplied {@code delegate} is presumed to have originated; must not be * {@code null} * * @exception NullPointerException if either argument is {@code null} @@ -81,8 +81,30 @@ public final class UniversalType * @see #delegate() */ @SuppressWarnings("try") - public UniversalType(final TypeMirror delegate, final Domain domain) { - super(delegate, domain); + public UniversalType(final TypeMirror delegate, final PrimordialDomain domain) { + this(delegate, null, domain); + } + + /** + * Creates a new {@link UniversalType}. + * + * @param delegate a {@link TypeMirror} to which operations will be delegated; must not be {@code null} + * + * @param annotations a {@link List} of {@link AnnotationMirror} instances representing annotations, often synthetic, + * that this {@link UniversalType} should bear; may be {@code null} in which case only the annotations from the + * supplied {@code delegate} will be used + * + * @param domain a {@link PrimordialDomain} from which the supplied {@code delegate} is presumed to have originated; + * must not be {@code null} + * + * @exception NullPointerException if any argument is {@code null} + * + * @see #delegate() + */ + public UniversalType(final TypeMirror delegate, + final List annotations, + final PrimordialDomain domain) { + super(delegate, annotations, domain); } @Override // TypeMirror @@ -115,6 +137,11 @@ public final R accept(final TypeVisitor v, final P p) { }; } + @Override // UniversalConstruct> + protected UniversalType annotate(final List replacementAnnotations) { + return new UniversalType(this.delegate(), replacementAnnotations, this.domain()); + } + @Override // Various public final UniversalElement asElement() { return switch (this.getKind()) { @@ -130,6 +157,9 @@ public final UniversalElement asElement() { * * @return the element type of this {@link UniversalType} if it is an array type, or this {@link * UniversalType} if it is not; never {@code null} + * + * @spec https://docs.oracle.com/javase/specs/jls/se23/html/jls-10.html#jls-10.1 Java Language Specification, section + * 10.1 */ public final UniversalType elementType() { return this.elementType(this); @@ -142,25 +172,6 @@ private final UniversalType elementType(final UniversalType t) { }; } - /** - * Returns the erasure of this {@link UniversalType}. - * - * @return the erasure of this {@link UniversalType}; never {@code null} - * - * @spec https://docs.oracle.com/javase/specs/jls/se23/html/jls-4.html#jls-4.6 Java Language Specification, section - * 4.6 - */ - public final UniversalType erasure() { - UniversalType t = this.erasure; // volatile read - if (t == null) { - t = this.erasure = switch (this.getKind()) { // volatile write, read - case ARRAY, DECLARED, TYPEVAR -> this.wrap(this.domain().erasure(this)); - default -> this; - }; - } - return t; - } - /** * Returns {@code true} if and only if this {@link UniversalType} is declared by a {@linkplain * UniversalElement#generic() generic declaration}. @@ -235,7 +246,7 @@ public final UniversalType getLowerBound() { public final UniversalType getUpperBound() { return switch (this.getKind()) { case TYPEVAR -> this.wrap(((TypeVariable)this.delegate()).getUpperBound()); - default -> this.wrap(this.domain().javaLangObject().asType()); + default -> this.wrap(this.domain().javaLangObjectType()); }; } @@ -356,49 +367,6 @@ public final boolean raw() { }; } - /** - * Returns the {@link UniversalType} that is the raw usage of this {@link UniversalType}, if it is capable - * of having such a usage, or {@code null} if it is not. - * - * @return a {@link UniversalType} whose {@link #raw()} method is guaranteed to return {@code true}, or {@code null} - * - * @see #raw() - * - * @spec https://docs.oracle.com/javase/specs/jls/se23/html/jls-4.html#jls-4.8 Java Language Specification, section - * 4.8 - */ - public final UniversalType rawType() { - return switch (this.getKind()) { - case ARRAY -> this.elementType().rawType(); - default -> this.parameterized() ? this.erasure() : null; - }; - } - - /** - * Returns {@code true} if and only if this {@link UniversalType} is the same type as the supplied {@link - * TypeMirror}. - * - *

The definition of type sameness appears in the contract of the {@link Domain#sameType(TypeMirror, - * TypeMirror)} method, which, in turn, relies on the contract of the {@link - * javax.lang.model.util.Types#isSameType(TypeMirror, TypeMirror)} method.

- * - * @param t a {@link TypeMirror}; may be {@code null} in which case {@code false} will be returned - * - * @return {@code true} if and only if this {@link UniversalType} is the same type as the supplied {@link - * TypeMirror} - * - * @see Domain#sameType(TypeMirror, TypeMirror) - * - * @see javax.lang.model.util.Types#isSameType(TypeMirror, TypeMirror) - * - * @see #equals(Object) - * - * @see TypeMirror#equals(Object) - */ - public final boolean sameType(final TypeMirror t) { - return this.domain().sameType(this, t); - } - private final List wrap(final Collection ts) { return of(ts, this.domain()); } @@ -419,13 +387,13 @@ private final UniversalType wrap(final TypeMirror t) { * * @param ts a {@link Collection} of {@link TypeMirror}s; must not be {@code null} * - * @param domain a {@link Domain}; must not be {@code null} + * @param domain a {@link PrimordialDomain}; must not be {@code null} * * @return a non-{@code null}, immutable {@link List} of {@link UniversalType}s * * @exception NullPointerException if either argument is {@code null} */ - public static final List of(final Collection ts, final Domain domain) { + public static final List of(final Collection ts, final PrimordialDomain domain) { if (ts.isEmpty()) { return List.of(); } @@ -442,15 +410,15 @@ public static final List of(final Collection null; case UniversalType ut -> ut; diff --git a/src/test/java/org/microbean/construct/TestDefaultDomain.java b/src/test/java/org/microbean/construct/TestDefaultDomain.java index 9670ce2..3fe91c5 100644 --- a/src/test/java/org/microbean/construct/TestDefaultDomain.java +++ b/src/test/java/org/microbean/construct/TestDefaultDomain.java @@ -1,6 +1,6 @@ /* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*- * - * Copyright © 2024 microBean™. + * Copyright © 2024–2025 microBean™. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -13,11 +13,16 @@ */ package org.microbean.construct; +import java.lang.annotation.Inherited; + import java.util.List; +import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.ElementKind; import javax.lang.model.element.Name; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.DeclaredType; import javax.lang.model.type.PrimitiveType; import javax.lang.model.type.TypeKind; @@ -25,6 +30,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotSame; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -182,4 +188,26 @@ final void testSameTypes() { assertTrue(domain.sameType(t0, t1)); } + @Test + final void testAllAnnotations0() { + final TypeElement e0 = domain.typeElement("java.lang.annotation.Documented"); + final List as = domain.allAnnotationMirrors(e0); + assertEquals(3, as.size()); // @Documented, @Retention, @Target + } + + @Test + final void testAllAnnotation1() { + final TypeElement e0 = domain.typeElement(Bottom.class.getCanonicalName()); + final List as = domain.allAnnotationMirrors(e0); + assertEquals(1, as.size()); // @InheritMe + } + + @Inherited + @interface InheritMe {} + + @InheritMe + private static class Top {} + + private static class Bottom extends Top {} + }