From 4ee62c381464a39af18951b4b1b42bbc7ab390d5 Mon Sep 17 00:00:00 2001 From: Laird Nelson Date: Thu, 15 Jan 2026 19:46:13 -0800 Subject: [PATCH] Road grading. Makes annotations mutable on purpose. Signed-off-by: Laird Nelson --- README.md | 2 +- pom.xml | 2 +- .../java/org/microbean/construct/Domain.java | 14 +- .../construct/UniversalConstruct.java | 243 ++++++----- .../construct/constant/Constables.java | 17 + .../construct/element/AnnotationMirrors.java | 403 +++++++++++++++++- .../SyntheticAnnotationTypeElement.java | 138 +++++- .../element/UniversalAnnotation.java | 4 +- .../construct/element/UniversalElement.java | 19 +- .../construct/type/UniversalType.java | 19 +- .../element/TestCyclicAnnotationMirrors.java | 48 +++ 11 files changed, 756 insertions(+), 153 deletions(-) create mode 100644 src/test/java/org/microbean/construct/element/TestCyclicAnnotationMirrors.java diff --git a/README.md b/README.md index 680e33d..499c8b9 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ dependency: Always check https://search.maven.org/artifact/org.microbean/microbean-construct for up-to-date available versions. --> - 0.0.19 + 0.0.20 ``` diff --git a/pom.xml b/pom.xml index e7cea42..02fd805 100644 --- a/pom.xml +++ b/pom.xml @@ -271,7 +271,7 @@ org.sonatype.central central-publishing-maven-plugin - 0.9.0 + 0.10.0 true central.sonatype.com diff --git a/src/main/java/org/microbean/construct/Domain.java b/src/main/java/org/microbean/construct/Domain.java index 833df78..d9c033b 100644 --- a/src/main/java/org/microbean/construct/Domain.java +++ b/src/main/java/org/microbean/construct/Domain.java @@ -1511,10 +1511,10 @@ public default TypeVariable typeVariable(Parameterizable p, final CharSequence n } /** - * A convenience method that returns the first {@link VariableElement} with a {@linkplain ElementKind#isVariable() - * variable ElementKind} and {@linkplain Element#getSimpleName() bearing} the supplied {@code simpleName} - * that the supplied {@code enclosingElement} {@linkplain Element#getEnclosedElements() encloses}, or {@code - * null} if there is no such {@link VariableElement}. + * A convenience method that returns the first {@link VariableElement} {@linkplain Element#getSimpleName() bearing} + * the supplied {@code simpleName} that the supplied {@code enclosingElement} {@linkplain + * Element#getEnclosedElements() encloses}, or {@code null} if there is no such {@link + * VariableElement}. * * @param enclosingElement an {@link Element}; must not be {@code null} * @@ -1526,8 +1526,6 @@ public default TypeVariable typeVariable(Parameterizable p, final CharSequence n * * @see Element#getEnclosedElements() * - * @see ElementKind#isVariable() - * * @see Element#getSimpleName() * * @see VariableElement @@ -1539,7 +1537,7 @@ public default VariableElement variableElement(final Element enclosingElement, f case null -> throw new NullPointerException("enclosingElement"); case UniversalElement ue -> { for (final UniversalElement ee : ue.getEnclosedElements()) { - if (ee.getKind().isVariable() && ee.getSimpleName().contentEquals(simpleName)) { + if (ee.getSimpleName().contentEquals(simpleName)) { yield ee; } } @@ -1548,7 +1546,7 @@ public default VariableElement variableElement(final Element enclosingElement, f default -> { try (var lock = lock()) { for (final Element ee : enclosingElement.getEnclosedElements()) { - if (ee.getKind().isVariable() && ee.getSimpleName().contentEquals(simpleName)) { + if (ee.getSimpleName().contentEquals(simpleName)) { yield (VariableElement)ee; } } diff --git a/src/main/java/org/microbean/construct/UniversalConstruct.java b/src/main/java/org/microbean/construct/UniversalConstruct.java index 49d6f46..adef09a 100644 --- a/src/main/java/org/microbean/construct/UniversalConstruct.java +++ b/src/main/java/org/microbean/construct/UniversalConstruct.java @@ -1,6 +1,6 @@ /* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*- * - * Copyright © 2024–2025 microBean™. + * Copyright © 2024–2026 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 @@ -20,22 +20,23 @@ import java.lang.constant.ConstantDesc; import java.lang.constant.DynamicConstantDesc; -import java.util.ArrayList; import java.util.List; import java.util.Optional; +import java.util.concurrent.CopyOnWriteArrayList; + 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.element.QualifiedNameable; 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; @@ -46,8 +47,6 @@ import static java.lang.constant.MethodHandleDesc.ofConstructor; -import static java.util.Collections.unmodifiableList; - import static java.util.Objects.requireNonNull; /** @@ -56,8 +55,6 @@ * * @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 @@ -66,17 +63,24 @@ * * @see UniversalType */ -public abstract sealed class UniversalConstruct> +public abstract sealed class UniversalConstruct implements AnnotatedConstruct, Constable permits UniversalElement, UniversalType { + /* + * Static fields. + */ + + + private static final Annotation[] EMPTY_ANNOTATION_ARRAY = new Annotation[0]; + + /* * Instance fields. */ - // Retained only for Constable implementation private final PrimordialDomain domain; // Eventually this should become a lazy constant/stable value @@ -87,9 +91,7 @@ public abstract sealed class UniversalConstruct annotations; - - private final List syntheticAnnotations; + private volatile CopyOnWriteArrayList annotations; /* @@ -107,58 +109,33 @@ public abstract sealed class UniversalConstruct annotations, + protected UniversalConstruct(final List annotations, + final T delegate, final PrimordialDomain domain) { super(); 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); + if (annotations != null) { + this.annotations = new CopyOnWriteArrayList<>(annotations); } final T unwrappedDelegate = unwrap(requireNonNull(delegate, "delegate")); if (unwrappedDelegate == delegate) { @@ -175,7 +152,7 @@ protected UniversalConstruct(final T delegate, return unwrappedDelegate; }; } else { - assert delegate instanceof UniversalConstruct; + assert delegate instanceof UniversalConstruct; // Symbol completion already happened because unwrapping actually happened this.delegateSupplier = () -> unwrappedDelegate; } @@ -187,19 +164,6 @@ protected UniversalConstruct(final T delegate, */ - /** - * 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. * @@ -219,19 +183,22 @@ public final T delegate() { @Override // Constable public final Optional describeConstable() { - final T delegate = this.delegate(); - 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(); + final PrimordialDomain primordialDomain = this.domain(); + if (domain instanceof Domain d && d instanceof Constable dc) { + final T delegate = this.delegate(); + final List annotations = this.annotations; // volatile read; may be null and that's OK + return Constables.describe(delegate, d) + .flatMap(delegateDesc -> Constables.describe(annotations) + .map(annosDesc -> DynamicConstantDesc.of(BSM_INVOKE, + ofConstructor(ClassDesc.of(this.getClass().getName()), + CD_List, + ClassDesc.of(delegate instanceof TypeMirror ? TypeMirror.class.getName() : Element.class.getName()), + ClassDesc.of(PrimordialDomain.class.getName())), + annosDesc, + delegateDesc, + dc.describeConstable().orElseThrow()))); + } + return Optional.empty(); } /** @@ -255,49 +222,136 @@ 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; }; } + /** + * Returns a non-{@code null}, determinate, mutable, thread-safe {@link List} of {@link + * AnnotationMirror} instances representing the annotations to be considered directly present on this + * {@link UniversalConstruct} implementation. + * + * @return a non-{@code null}, determinate, mutable thread-safe {@link List} of {@link + * AnnotationMirror}s + * + * @see AnnotatedConstruct#getAnnotationMirrors() + */ @Override // AnnotatedConstruct @SuppressWarnings("try") - public final List getAnnotationMirrors() { - List annotations = this.annotations; // volatile read + public final List getAnnotationMirrors() { + CopyOnWriteArrayList 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 + this.annotations = annotations = // volatile write + new CopyOnWriteArrayList<>(UniversalAnnotation.of(this.delegate().getAnnotationMirrors(), this.domain())); } - 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); } + return annotations; } + /** + * Makes a best effort to return an {@link Annotation} of the appropriate type present on + * this {@link UniversalConstruct} implementation. + * + *

See the specification for the {@link AnnotatedConstruct#getAnnotation(Class)} method for important details.

+ * + *

{@link UniversalConstruct} implementations deliberately permit modification of their {@linkplain + * #getAnnotationMirrors() annotations}. Consequently, this override first checks to see if there is at least one + * {@link AnnotationMirror} whose {@linkplain AnnotationMirror#getAnnotationType() annotation type} is declared by a + * {@link javax.lang.model.element.TypeElement} whose {@linkplain + * javax.lang.model.element.TypeElement#getQualifiedName() qualified name} is {@linkplain + * javax.lang.model.element.Name#contentEquals(CharSequence) equal to} the {@linkplain Class#getCanonicalName() + * canonical name} of the supplied {@link Class}. If there is, then the {@link AnnotatedConstruct#getAnnotation(Class) + * getAnnotation(Class)} method is invoked on the {@linkplain #delegate() delegate} and its result is + * returned. Otherwise, {@code null} is returned.

+ * + *

There are circumstances where the {@link Annotation} returned by this method may not accurately reflect a + * synthetic annotation added to this {@link AnnotatedConstruct} implementation's {@linkplain #getAnnotationMirrors() + * annotations}.

+ * + *

In general, the use of this method is discouraged.

+ * + * @param annotationType a {@link Class} that is an annotation interface; must not be {@code null} + * + * @return an appropriate {@link Annotation}, or {@code null} + * + * @exception NullPointerException if {@code annotationType} is {@code null} + * + * @see AnnotatedConstruct#getAnnotation(Class) + * + * @deprecated The use of this method is discouraged. + */ + @Deprecated @Override // AnnotatedConstruct @SuppressWarnings("try") public final A getAnnotation(final Class annotationType) { - // TODO: is this lock actually needed, given how delegateSupplier works? - try (var lock = this.domain().lock()) { - return this.delegate().getAnnotation(annotationType); + if (!annotationType.isAnnotation()) { + return null; } + final String canonicalName = annotationType.getCanonicalName(); + for (final AnnotationMirror a : this.getAnnotationMirrors()) { + if (((QualifiedNameable)a.getAnnotationType().asElement()).getQualifiedName().contentEquals(canonicalName)) { + // TODO: is this lock actually needed, given how delegateSupplier works? + try (var lock = this.domain().lock()) { + return this.delegate().getAnnotation(annotationType); + } + } + } + return null; + } + /** + * Makes a best effort to return an array of {@link Annotation}s of the appropriate type + * associated with this {@link UniversalConstruct} implementation. + * + *

See the specification for the {@link AnnotatedConstruct#getAnnotationsByType(Class)} method for important + * details.

+ * + *

{@link UniversalConstruct} implementations deliberately permit modification of their {@linkplain + * #getAnnotationMirrors() annotations}. Consequently, this override first checks to see if there is at least one + * {@link AnnotationMirror} whose {@linkplain AnnotationMirror#getAnnotationType() annotation type} is declared by a + * {@link javax.lang.model.element.TypeElement} whose {@linkplain + * javax.lang.model.element.TypeElement#getQualifiedName() qualified name} is {@linkplain + * javax.lang.model.element.Name#contentEquals(CharSequence) equal to} the {@linkplain Class#getCanonicalName() + * canonical name} of the supplied {@link Class}. If there is, then the {@link + * AnnotatedConstruct#getAnnotationsByType(Class) getAnnotationsByType(Class)} method is invoked on the {@linkplain + * #delegate() delegate} and its result is returned. Otherwise, an empty array is returned.

+ * + *

There are circumstances where the {@link Annotation} array returned by this method may not accurately reflect + * synthetic annotations added to this {@link AnnotatedConstruct} implementation's {@linkplain #getAnnotationMirrors() + * annotations}.

+ * + *

In general, the use of this method is discouraged.

+ * + * @param annotationType a {@link Class} that is an annotation interface; must not be {@code null} + * + * @return an appropriate {@link Annotation}, or {@code null} + * + * @exception NullPointerException if {@code annotationType} is {@code null} + * + * @see AnnotatedConstruct#getAnnotation(Class) + * + * @deprecated The use of this method is discouraged. + */ + @Deprecated @Override // AnnotatedConstruct - @SuppressWarnings("try") + @SuppressWarnings({"try", "unchecked"}) public final
A[] getAnnotationsByType(final Class annotationType) { - // TODO: is this lock actually needed, given how delegateSupplier works? - try (var lock = this.domain().lock()) { - return this.delegate().getAnnotationsByType(annotationType); + if (!annotationType.isAnnotation()) { + return (A[])EMPTY_ANNOTATION_ARRAY; + } + final String canonicalName = annotationType.getCanonicalName(); + for (final AnnotationMirror a : this.getAnnotationMirrors()) { + if (((QualifiedNameable)a.getAnnotationType().asElement()).getQualifiedName().contentEquals(canonicalName)) { + // TODO: is this lock actually needed, given how delegateSupplier works? + try (var lock = this.domain().lock()) { + return this.delegate().getAnnotationsByType(annotationType); + } + } } + return (A[])EMPTY_ANNOTATION_ARRAY; } @Override // Object @@ -320,7 +374,6 @@ public final String toString() { try (var lock = this.domain().lock()) { s = this.s = this.delegate().toString(); // volatile write, read } - assert s != null; } return s; } @@ -345,7 +398,7 @@ 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; diff --git a/src/main/java/org/microbean/construct/constant/Constables.java b/src/main/java/org/microbean/construct/constant/Constables.java index 790a947..bcb36eb 100644 --- a/src/main/java/org/microbean/construct/constant/Constables.java +++ b/src/main/java/org/microbean/construct/constant/Constables.java @@ -753,6 +753,23 @@ 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 + * + * @return a non-{@code null} {@link Optional} + * + * @see #describe(List, Function) + */ + public static final Optional describe(final List list) { + return describe(list, e -> e instanceof Constable c ? c.describeConstable() : Optional.empty()); + } + /** * 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. diff --git a/src/main/java/org/microbean/construct/element/AnnotationMirrors.java b/src/main/java/org/microbean/construct/element/AnnotationMirrors.java index 8ac21b1..6f8df60 100644 --- a/src/main/java/org/microbean/construct/element/AnnotationMirrors.java +++ b/src/main/java/org/microbean/construct/element/AnnotationMirrors.java @@ -13,23 +13,59 @@ */ package org.microbean.construct.element; +import java.lang.annotation.ElementType; +import java.lang.annotation.RetentionPolicy; + +import java.util.ArrayDeque; +import java.util.ArrayList; import java.util.Collection; +import java.util.EnumSet; import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Objects; +import java.util.Queue; import java.util.SequencedMap; +import java.util.Set; import java.util.function.Predicate; +import java.util.stream.Stream; + +import javax.lang.model.AnnotatedConstruct; + import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.QualifiedNameable; +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 static java.util.Collections.unmodifiableList; import static java.util.Collections.unmodifiableSequencedMap; +import static java.util.Collections.unmodifiableSet; import static java.util.LinkedHashMap.newLinkedHashMap; +import static java.util.HashSet.newHashSet; + +import static java.util.function.Function.identity; + +import static java.util.stream.Stream.concat; +import static java.util.stream.Stream.empty; +import static java.util.stream.Stream.iterate; + +import static javax.lang.model.element.ElementKind.ANNOTATION_TYPE; +import static javax.lang.model.element.ElementKind.CLASS; + +import static javax.lang.model.type.TypeKind.DECLARED; + import static javax.lang.model.util.ElementFilter.methodsIn; /** @@ -40,6 +76,8 @@ */ public final class AnnotationMirrors { + private static final Set EMPTY_ELEMENT_TYPES = unmodifiableSet(EnumSet.noneOf(ElementType.class)); + private static final SequencedMap EMPTY_MAP = unmodifiableSequencedMap(newLinkedHashMap(0)); private static final SameAnnotationValueVisitor sameAnnotationValueVisitor = new SameAnnotationValueVisitor(); @@ -48,6 +86,51 @@ private AnnotationMirrors() { super(); } + /** + * Returns a determinate, non-{@code null}, immutable {@link List} of {@link AnnotationMirror}s present on + * the supplied {@link Element}. + * + *

This method is a more capable, better-typed replacement of the {@link + * javax.lang.model.util.Elements#getAllAnnotationMirrors(Element)} method, and should be preferred.

+ * + * @param e an {@link Element}; must not be {@code null} + * + * @return a determinate, non-{@code null}, immutable {@link List} of {@link AnnotationMirror}s present on + * the supplied {@link Element} + * + * @exception NullPointerException if {@code e} is {@code null} + * + * @see javax.lang.model.util.Elements#getAllAnnotationMirrors(Element) + */ + public static final List allAnnotationMirrors(Element e) { + final List allAnnotations = new LinkedList<>(e.getAnnotationMirrors()); + WHILE_LOOP: + while (e.getKind() == CLASS && e instanceof TypeElement te) { + final TypeMirror sct = te.getSuperclass(); + if (sct.getKind() != DECLARED) { + break; + } + e = ((DeclaredType)sct).asElement(); + final List superclassAnnotations = e.getAnnotationMirrors(); + if (!superclassAnnotations.isEmpty()) { + int added = 0; + for (final AnnotationMirror superclassAnnotation : superclassAnnotations) { + if (inherited(superclassAnnotation)) { + for (final AnnotationMirror a : allAnnotations.subList(added, allAnnotations.size())) { + if (((QualifiedNameable)superclassAnnotation.getAnnotationType().asElement()).getQualifiedName().contentEquals(((QualifiedNameable)a.getAnnotationType().asElement()).getQualifiedName())) { + continue WHILE_LOOP; + } + } + // javac prepends superclass annotations, resulting in a strage order; we duplicate it + allAnnotations.addFirst(superclassAnnotation); + ++added; + } + } + } + } + return allAnnotations.isEmpty() ? List.of() : unmodifiableList(allAnnotations); + } + /** * For the supplied {@link AnnotationMirror}, returns an immutable, determinate {@link Map} of {@link AnnotationValue} * instances indexed by its {@link ExecutableElement}s to which they apply. @@ -90,6 +173,168 @@ public static final SequencedMap allAnnotati return m.isEmpty() ? emptySequencedMap() : unmodifiableSequencedMap(m); } + /** + * Returns the (determinate) {@link AnnotationMirror} directly present on the supplied {@link + * AnnotatedConstruct} whose {@linkplain AnnotationMirror#getAnnotationType() annotation type}'s {@link + * javax.lang.model.type.DeclaredType#asElement() TypeElement} {@linkplain TypeElement#getQualifiedName() bears} the + * supplied {@code fullyQualifiedName}, or {@code null} if no such {@link AnnotationMirror} exists. + * + * @param ac an {@link AnnotatedConstruct}; must not be {@code null} + * + * @param name a {@link CharSequence}; may be {@code null} in which case {@code null} will be returned + * + * @return an {@link AnnotationMirror}, or {@code null} + * + * @exception NullPointerException if {@code ac} is {@code null} + * + * @see #streamBreadthFirst(AnnotatedConstruct) + * + * @see #streamDepthFirst(AnnotatedConstruct) + * + * @see AnnotationMirror#getAnnotationType() + * + * @see javax.lang.model.type.DeclaredType#asElement() + * + * @see TypeElement#getQualifiedName() + * + * @see javax.lang.model.element.Name#contentEquals(CharSequence) + */ + public static final AnnotationMirror get(final AnnotatedConstruct ac, final CharSequence name) { + if (name == null) { + return null; + } + for (final AnnotationMirror am : ac.getAnnotationMirrors()) { + final TypeElement e = (TypeElement)am.getAnnotationType().asElement(); + if (e.getQualifiedName().contentEquals(name)) { + return am; + } + } + return null; + } + + /** + * A convenience method that returns a value for an annotation element named by the supplied {@link CharSequence} and + * logically owned by the supplied {@link AnnotationMirror}, or {@code null} if no such value exists. + * + * @param am an {@link AnnotationMirror}; must not be {@code null} + * + * @param name a {@link CharSequence}; may be {@code null} in which case {@code null} will be returned + * + * @return the result of invoking {@link AnnotationValue#getValue() getValue()} on an {@link AnnotationValue}, or + * {@code null} + * + * @exception NullPointerException if {@code am} is {@code null} + * + * @see AnnotationValue + * + * @see #allAnnotationValues(AnnotationMirror) + */ + public static final Object get(final AnnotationMirror am, final CharSequence name) { + if (name == null) { + return null; + } + for (final Entry e : allAnnotationValues(am).entrySet()) { + if (e.getKey().getSimpleName().contentEquals(name)) { + final AnnotationValue av = e.getValue(); + return av == null ? null : av.getValue(); + } + } + return null; + } + + /** + * Returns {@code true} if and only if the supplied {@link AnnotationMirror}'s {@linkplain + * AnnotationMirror#getAnnotationType() annotation type} is {@linkplain javax.lang.model.type.DeclaredType#asElement() + * declared by} an element that has been (meta-) {@linkplain Element#getAnnotationMirrors() annotated} with {@link + * java.lang.annotation.Inherited}. + * + * @param a an {@link AnnotationMirror}; must not be {@code null} + * + * @return {@code true} if and only if the supplied {@link AnnotationMirror}'s {@linkplain + * AnnotationMirror#getAnnotationType() annotation type} is {@linkplain javax.lang.model.type.DeclaredType#asElement() + * declared by} an element that has been (meta-) {@linkplain Element#getAnnotationMirrors() annotated} with {@link + * java.lang.annotation.Inherited} + * + * @exception NullPointerException if {@code a} is {@code null} + * + * @see #inherited(TypeElement) + */ + public static final boolean inherited(final AnnotationMirror a) { + return inherited((TypeElement)a.getAnnotationType().asElement()); + } + + /** + * Returns {@code true} if and only if the supplied {@link TypeElement} represents an {@linkplain + * javax.lang.model.element.ElementKind#ANNOTATION_TYPE annotation interface} and if it has been (meta-) annotated + * with {@link java.lang.annotation.Inherited}. + * + * @param annotationInterface a {@link TypeElement} representing an {@linkplain + * javax.lang.model.element.ElementKind#ANNOTATION_TYPE annotation interface}; must not be {@code null} + * + * @return {@code true} if and only if the supplied {@link TypeElement} represents an {@linkplain + * javax.lang.model.element.ElementKind#ANNOTATION_TYPE annotation interface} and if it has been (meta-) annotated + * with {@link java.lang.annotation.Inherited} + * + * @exception NullPointerException if {@code annotationInterface} is {@code null} + * + * @spec https://docs.oracle.com/javase/specs/jls/se25/html/jls-9.html#jls-9.6.4.3 Java Language Specification, + * section 9.6.4.3 + */ + public static final boolean inherited(final TypeElement annotationInterface) { + if (annotationInterface.getKind() == ANNOTATION_TYPE) { + for (final AnnotationMirror ma : annotationInterface.getAnnotationMirrors()) { + if (((QualifiedNameable)ma.getAnnotationType().asElement()).getQualifiedName().contentEquals("java.lang.annotation.Inherited")) { + return true; + } + } + } + return false; + } + + /** + * Returns a {@link RetentionPolicy} for the supplied {@link AnnotationMirror}, or {@link RetentionPolicy#CLASS} if, + * for any reason, a retention policy cannot be found or computed. + * + * @param a an {@link AnnotationMirror}; must not be {@code null} + * + * @return the {@link RetentionPolicy} for the supplied {@link AnnotationMirror}; never {@code null} + * + * @exception NullPointerException if {@code a} is {@code null} + * + * @see #retentionPolicy(TypeElement) + */ + public static final RetentionPolicy retentionPolicy(final AnnotationMirror a) { + return retentionPolicy((TypeElement)a.getAnnotationType().asElement()); + } + + /** + * Returns a {@link RetentionPolicy} for the supplied {@link TypeElement} representing an {@linkplain + * javax.lang.model.element.ElementKind#ANNOTATION_TYPE annotation interface}, or {@link RetentionPolicy#CLASS} if, + * for any reason, a retention policy cannot be found or computed. + * + * @param annotationInterface a {@link TypeElement}; must be non-{@code null} and should represent an {@linkplain + * javax.lang.model.element.ElementKind#ANNOTATION_TYPE annotation interface} + * + * @return the {@link RetentionPolicy} for the supplied {@link TypeElement}; never {@code null} + * + * @exception NullPointerException if {@code annotationInterface} is {@code null} + * + * @see RetentionPolicy + * + * @spec https://docs.oracle.com/javase/specs/jls/se25/html/jls-9.html#jls-9.6.4.2 Java Language Specification, + * section 9.6.4.2 + */ + public static final RetentionPolicy retentionPolicy(final TypeElement annotationInterface) { + if (annotationInterface.getKind() == ANNOTATION_TYPE) { + for (final AnnotationMirror ma : annotationInterface.getAnnotationMirrors()) { + if (((QualifiedNameable)ma.getAnnotationType().asElement()).getQualifiedName().contentEquals("java.lang.annotation.Retention")) { + return RetentionPolicy.valueOf(((VariableElement)get(ma, "value")).getSimpleName().toString()); + } + } + } + return RetentionPolicy.CLASS; + } + /** * Determines whether the two {@link AnnotationMirror}s represent the same (otherwise opaque) annotation. * @@ -161,33 +406,146 @@ public static final boolean sameAnnotation(final AnnotationMirror am0, } /** - * A convenience method that returns a value for an annotation element named by the supplied {@link CharSequence} and - * logically owned by the supplied {@link AnnotationMirror}, or {@code null} if no such value exists. + * Returns a non-{@code null}, sequential, non-empty {@link Stream} that traverses the supplied {@link + * AnnotationMirror}'s {@linkplain AnnotationMirror#getAnnotationType() annotation interface}'s {@link TypeElement}'s + * {@linkplain AnnotatedConstruct#getAnnotationMirrors() annotations}, and their (meta-) annotations, in breadth-first + * order. + * + *

The first and possibly only element in the {@link Stream} that is returned is guaranteed to be the supplied + * {@link AnnotationMirror}.

* * @param am an {@link AnnotationMirror}; must not be {@code null} * - * @param name a {@link CharSequence}; may be {@code null} in which case {@code null} will be returned + * @return a non-{@code null} {@link Stream} of {@link AnnotationMirror}s * - * @return the result of invoking {@link AnnotationValue#getValue() getValue()} on an {@link AnnotationValue}, or - * {@code null} + * @exception NullPointerException if {@code am} is {@code null} + * + * @see #streamBreadthFirst(AnnotatedConstruct) + */ + public static final Stream streamBreadthFirst(final AnnotationMirror am) { + return concat(Stream.of(am), streamBreadthFirst(am.getAnnotationType().asElement())); + } + + /** + * Returns a non-{@code null}, sequential, {@link Stream} that traverses the supplied {@link AnnotatedConstruct}'s + * {@linkplain AnnotatedConstruct#getAnnotationMirrors() annotations}, and their (meta-) annotations, in breadth-first + * order. + * + * @param ac an {@link AnnotatedConstruct}; must not be {@code null} + * + * @return a non-{@code null} {@link Stream} of {@link AnnotationMirror}s + * + * @exception NullPointerException if {@code ac} is {@code null} + */ + public static final Stream streamBreadthFirst(final AnnotatedConstruct ac) { + final List ams = ac.getAnnotationMirrors(); + final Set names = newHashSet(ams.size()); + final Queue q = new ArrayDeque<>(); + ams.forEach(a -> { + names.add(((QualifiedNameable)a.getAnnotationType().asElement()).getQualifiedName().toString()); + q.add(a); + }); + return + iterate(q.poll(), + Objects::nonNull, + a0 -> { + a0.getAnnotationType() + .asElement() + .getAnnotationMirrors() + .stream() + .filter(a1 -> names.add(((QualifiedNameable)a1.getAnnotationType().asElement()).getQualifiedName().toString())) + .forEach(q::add); + return q.poll(); + }); + } + + /** + * A convenience method that returns a non-{@code null}, sequential, non-empty {@link Stream} that traverses the + * supplied {@link AnnotationMirror}'s {@linkplain AnnotationMirror#getAnnotationType() annotation interface}'s {@link + * TypeElement}'s {@linkplain AnnotatedConstruct#getAnnotationMirrors() annotations}, and their (meta-) annotations, + * in depth-first order. + * + *

The first and possibly only element in the {@link Stream} that is returned is guaranteed to be the supplied + * {@link AnnotationMirror}.

+ * + * @param am an {@link AnnotationMirror}; must not be {@code null} + * + * @return a non-{@code null} {@link Stream} of {@link AnnotationMirror}s * * @exception NullPointerException if {@code am} is {@code null} * - * @see AnnotationValue + * @see #streamDepthFirst(AnnotatedConstruct) + */ + // (Convenience.) + public static final Stream streamDepthFirst(final AnnotationMirror am) { + return concat(Stream.of(am), streamDepthFirst(am.getAnnotationType().asElement())); + } + + /** + * Returns a non-{@code null}, sequential {@link Stream} that traverses the supplied {@link AnnotatedConstruct}'s + * {@linkplain AnnotatedConstruct#getAnnotationMirrors() annotations}, and their (meta-) annotations, in depth-first + * order. * - * @see #allAnnotationValues(AnnotationMirror) + * @param ac an {@link AnnotatedConstruct}; must not be {@code null} + * + * @return a non-{@code null} {@link Stream} of {@link AnnotationMirror}s + * + * @exception NullPointerException if {@code ac} is {@code null} */ - public static final Object get(final AnnotationMirror am, final CharSequence name) { - if (name == null) { - return null; - } - for (final Entry e : allAnnotationValues(am).entrySet()) { - if (e.getKey().getSimpleName().contentEquals(name)) { - final AnnotationValue av = e.getValue(); - return av == null ? null : av.getValue(); + public static final Stream streamDepthFirst(final AnnotatedConstruct ac) { + return streamDepthFirst(ac, newHashSet(17)); // 17 == arbitrary + } + + /** + * Returns a non-{@code null}, determinate, immutable {@link Set} of {@link ElementType}s describing restrictions + * concerning where the annotation interface {@linkplain AnnotationMirror#getAnnotationType() represented} by the + * supplied {@link AnnotationMirror} may be applied. + * + * @param a an {@link AnnotationMirror}; must not be {@code null} + * + * @return a non-{@code null}, determinate, immutable {@link Set} of {@link ElementType}s describing restrictions + * concerning where the annotation interface {@linkplain AnnotationMirror#getAnnotationType() represented} by the + * supplied {@link AnnotationMirror} may be applied + */ + public static final Set targetElementTypes(final AnnotationMirror a) { + return targetElementTypes((TypeElement)a.getAnnotationType().asElement()); + } + + /** + * Returns a non-{@code null}, determinate, immutable {@link Set} of {@link ElementType}s describing restrictions + * concerning where the supplied annotation interface may be applied. + * + * @param annotationInterface a {@link TypeElement} representing an {@linkplain + * javax.lang.model.element.ElementKind#ANNOTATION_TYPE annotation interface}; must not be {@code null} + * + * @return a non-{@code null}, determinate, immutable {@link Set} of {@link ElementType}s describing restrictions + * concerning where the supplied annotation interface may be applied + * + * @see java.lang.annotation.ElementType + * + * @see java.lang.annotation.Target + * + * @spec https://docs.oracle.com/javase/specs/jls/se25/html/jls-9.html#jls-9.6.4.1 Java Language Specification, + * section 9.6.4.1 + */ + public static final Set targetElementTypes(final TypeElement annotationInterface) { + if (annotationInterface.getKind() == ANNOTATION_TYPE) { + for (final AnnotationMirror ma : annotationInterface.getAnnotationMirrors()) { + if (((QualifiedNameable)ma.getAnnotationType().asElement()).getQualifiedName().contentEquals("java.lang.annotation.Target")) { + @SuppressWarnings("unchecked") + final List elementTypes = (List)get(ma, "value"); + if (elementTypes.isEmpty()) { + break; + } + final Set s = EnumSet.noneOf(ElementType.class); + for (final AnnotationValue av : elementTypes) { + s.add(ElementType.valueOf(((VariableElement)av.getValue()).getSimpleName().toString())); + } + return unmodifiableSet(s); + } } } - return null; + return EMPTY_ELEMENT_TYPES; } @SuppressWarnings("unchecked") @@ -195,4 +553,17 @@ private static final SequencedMap emptySequencedMap() { return (SequencedMap)EMPTY_MAP; } + private static final Stream streamDepthFirst(final AnnotatedConstruct ac, + final Set names) { + // See https://www.techempower.com/blog/2016/10/19/efficient-multiple-stream-concatenation-in-java/ + return + Stream.of(ac.getAnnotationMirrors() + .stream() + .flatMap(a -> { + final TypeElement e = (TypeElement)a.getAnnotationType().asElement(); + return names.add(e.getQualifiedName().toString()) ? streamDepthFirst(e, names) : empty(); + })) + .flatMap(identity()); + } + } diff --git a/src/main/java/org/microbean/construct/element/SyntheticAnnotationTypeElement.java b/src/main/java/org/microbean/construct/element/SyntheticAnnotationTypeElement.java index 2dff366..202b13d 100644 --- a/src/main/java/org/microbean/construct/element/SyntheticAnnotationTypeElement.java +++ b/src/main/java/org/microbean/construct/element/SyntheticAnnotationTypeElement.java @@ -20,6 +20,8 @@ import java.util.Objects; import java.util.Set; +import java.util.concurrent.CopyOnWriteArrayList; + import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.Element; @@ -82,7 +84,7 @@ public final class SyntheticAnnotationTypeElement implements TypeElement { */ - private final List annotationMirrors; + private final CopyOnWriteArrayList annotationMirrors; private final SyntheticName fqn; @@ -98,6 +100,23 @@ public final class SyntheticAnnotationTypeElement implements TypeElement { */ + /** + * Creates a new {@link SyntheticAnnotationTypeElement}, mostly, if not exclusively, for use by {@link + * SyntheticAnnotationMirror} instances. + * + * @param fullyQualifiedName the fully qualified name of the synthetic element; must conform to Java classname + * restrictions; must not be {@code null} + * + * @exception NullPointerException if {@code fullyQualifiedName} is {@code null} + * + * @exception IllegalArgumentException if {@code fullyQualifiedName} does not conform to Java classname restrictions + * + * @see #SyntheticAnnotationTypeElement(List, SyntheticName, List) + */ + public SyntheticAnnotationTypeElement(final String fullyQualifiedName) { + this(List.of(), new SyntheticName(fullyQualifiedName), List.of()); + } + /** * Creates a new {@link SyntheticAnnotationTypeElement}, mostly, if not exclusively, for use by {@link * SyntheticAnnotationMirror} instances. @@ -115,6 +134,27 @@ public SyntheticAnnotationTypeElement(final SyntheticName fullyQualifiedName) { this(List.of(), fullyQualifiedName, List.of()); } + /** + * Creates a new {@link SyntheticAnnotationTypeElement}, mostly, if not exclusively, for use by {@link + * SyntheticAnnotationMirror} instances. + * + * @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 #SyntheticAnnotationTypeElement(List, SyntheticName, List) + */ + public SyntheticAnnotationTypeElement(final String fullyQualifiedName, + final List elements) { + this(List.of(), new SyntheticName(fullyQualifiedName), elements); + } + /** * Creates a new {@link SyntheticAnnotationTypeElement}, mostly, if not exclusively, for use by {@link * SyntheticAnnotationMirror} instances. @@ -136,6 +176,67 @@ public SyntheticAnnotationTypeElement(final SyntheticName fullyQualifiedName, this(List.of(), fullyQualifiedName, elements); } + /** + * Creates a new {@link SyntheticAnnotationTypeElement}, mostly, if not exclusively, for use by {@link + * SyntheticAnnotationMirror} instances. + * + * @param annotationMirror an {@link AnnotationMirror}s modeling the (sole) annotation 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} + * + * @exception NullPointerException if any argument is {@code null} + * + * @exception IllegalArgumentException if {@code fullyQualifiedName} does not conform to Java classname restrictions + * + * @see #SyntheticAnnotationTypeElement(List, SyntheticName, List) + */ + public SyntheticAnnotationTypeElement(final AnnotationMirror annotationMirror, final String fullyQualifiedName) { + this(List.of(annotationMirror), new SyntheticName(fullyQualifiedName), List.of()); + } + + /** + * Creates a new {@link SyntheticAnnotationTypeElement}, mostly, if not exclusively, for use by {@link + * SyntheticAnnotationMirror} instances. + * + * @param annotationMirror an {@link AnnotationMirror}s modeling the (sole) annotation 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} + * + * @exception NullPointerException if any argument is {@code null} + * + * @exception IllegalArgumentException if {@code fullyQualifiedName} does not conform to Java classname restrictions + * + * @see #SyntheticAnnotationTypeElement(List, SyntheticName, List) + */ + public SyntheticAnnotationTypeElement(final AnnotationMirror annotationMirror, final SyntheticName fullyQualifiedName) { + this(List.of(annotationMirror), fullyQualifiedName, List.of()); + } + + /** + * Creates a new {@link SyntheticAnnotationTypeElement}, mostly, if not exclusively, for use by {@link + * SyntheticAnnotationMirror} instances. + * + * @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} + * + * @exception NullPointerException if any argument is {@code null} + * + * @exception IllegalArgumentException if {@code fullyQualifiedName} does not conform to Java classname restrictions + * + * @see #SyntheticAnnotationTypeElement(List, SyntheticName, List) + */ + public SyntheticAnnotationTypeElement(final List annotationMirrors, + final String fullyQualifiedName) { + this(annotationMirrors, new SyntheticName(fullyQualifiedName), List.of()); + } + /** * Creates a new {@link SyntheticAnnotationTypeElement}, mostly, if not exclusively, for use by {@link * SyntheticAnnotationMirror} instances. @@ -157,6 +258,31 @@ public SyntheticAnnotationTypeElement(final List ann this(annotationMirrors, fullyQualifiedName, List.of()); } + /** + * Creates a new {@link SyntheticAnnotationTypeElement}, mostly, if not exclusively, for use by {@link + * SyntheticAnnotationMirror} instances. + * + * @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 #SyntheticAnnotationTypeElement(List, SyntheticName, List) + */ + public SyntheticAnnotationTypeElement(final List annotationMirrors, + final String fullyQualifiedName, + final List elements) { + this(annotationMirrors, new SyntheticName(fullyQualifiedName), elements); + } + /** * Creates a new {@link SyntheticAnnotationTypeElement}, mostly, if not exclusively, for use by {@link * SyntheticAnnotationMirror} instances. @@ -182,7 +308,7 @@ public SyntheticAnnotationTypeElement(final List ann final SyntheticName fullyQualifiedName, final List elements) { super(); - this.annotationMirrors = List.copyOf(annotationMirrors); + this.annotationMirrors = new CopyOnWriteArrayList<>(annotationMirrors); final String fqn = fullyQualifiedName.toString(); final int i = fqn.lastIndexOf('.'); this.sn = i >= 0 ? new SyntheticName(fqn.substring(i + 1)) : fullyQualifiedName; @@ -216,7 +342,7 @@ public final TypeMirror asType() { } @Override // TypeElement (AnnotatedConstruct) - public final List getAnnotationMirrors() { + public final List getAnnotationMirrors() { return this.annotationMirrors; } @@ -598,7 +724,7 @@ private final class InternalAnnotationElement implements ExecutableElement { */ - private final List annotationMirrors; + private final CopyOnWriteArrayList annotationMirrors; private final Type t; @@ -617,7 +743,7 @@ private InternalAnnotationElement(final List annotat final SyntheticName name, final SyntheticAnnotationValue defaultValue) { super(); - this.annotationMirrors = List.copyOf(annotationMirrors); + this.annotationMirrors = new CopyOnWriteArrayList<>(annotationMirrors); this.t = new Type(type); this.name = requireNonNull(name, "name"); this.defaultValue = defaultValue; @@ -640,7 +766,7 @@ public final Type asType() { } @Override // ExecutableElement (AnnotatedConstruct) - public final List getAnnotationMirrors() { + public final List getAnnotationMirrors() { return this.annotationMirrors; } diff --git a/src/main/java/org/microbean/construct/element/UniversalAnnotation.java b/src/main/java/org/microbean/construct/element/UniversalAnnotation.java index c6304ef..4268369 100644 --- a/src/main/java/org/microbean/construct/element/UniversalAnnotation.java +++ b/src/main/java/org/microbean/construct/element/UniversalAnnotation.java @@ -223,8 +223,8 @@ public static final UniversalAnnotation of(final AnnotationMirror a, final Primo * * @exception NullPointerException if either argument is {@code null} */ - public static final List of(final Collection as, - final PrimordialDomain domain) { + public static final List of(final Collection as, + final PrimordialDomain domain) { if (as.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 fc01090..8366e6b 100644 --- a/src/main/java/org/microbean/construct/element/UniversalElement.java +++ b/src/main/java/org/microbean/construct/element/UniversalElement.java @@ -56,7 +56,7 @@ */ @SuppressWarnings("preview") // isUnnamed() usage public final class UniversalElement - extends UniversalConstruct + extends UniversalConstruct implements ExecutableElement, ModuleElement, PackageElement, @@ -79,18 +79,18 @@ public final class UniversalElement * @exception NullPointerException if either argument is {@code null} */ public UniversalElement(final Element delegate, final PrimordialDomain domain) { - this(delegate, null, domain); + this(null, delegate, 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 {@linkplain * SyntheticAnnotationMirror 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 delegate an {@link Element} to which operations will be delegated; 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} * @@ -99,10 +99,10 @@ public UniversalElement(final Element delegate, final PrimordialDomain domain) { * @see #delegate() */ @SuppressWarnings("try") - private UniversalElement(final Element delegate, - final List annotations, + private UniversalElement(final List annotations, + final Element delegate, final PrimordialDomain domain) { - super(delegate, annotations, domain); + super(annotations, delegate, domain); this.enclosedElementsSupplier = () -> { final List ees; try (var lock = domain.lock()) { @@ -143,11 +143,6 @@ 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()); diff --git a/src/main/java/org/microbean/construct/type/UniversalType.java b/src/main/java/org/microbean/construct/type/UniversalType.java index cd2a158..c3e78fd 100644 --- a/src/main/java/org/microbean/construct/type/UniversalType.java +++ b/src/main/java/org/microbean/construct/type/UniversalType.java @@ -54,7 +54,7 @@ * @see UniversalConstruct */ public final class UniversalType - extends UniversalConstruct + extends UniversalConstruct implements ArrayType, ErrorType, ExecutableType, @@ -80,18 +80,18 @@ public final class UniversalType */ @SuppressWarnings("try") public UniversalType(final TypeMirror delegate, final PrimordialDomain domain) { - this(delegate, null, domain); + this(null, delegate, 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 delegate a {@link TypeMirror} to which operations will be delegated; 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} * @@ -99,10 +99,10 @@ public UniversalType(final TypeMirror delegate, final PrimordialDomain domain) { * * @see #delegate() */ - public UniversalType(final TypeMirror delegate, - final List annotations, + public UniversalType(final List annotations, + final TypeMirror delegate, final PrimordialDomain domain) { - super(delegate, annotations, domain); + super(annotations, delegate, domain); } @Override // TypeMirror @@ -135,11 +135,6 @@ 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()) { diff --git a/src/test/java/org/microbean/construct/element/TestCyclicAnnotationMirrors.java b/src/test/java/org/microbean/construct/element/TestCyclicAnnotationMirrors.java new file mode 100644 index 0000000..62e441f --- /dev/null +++ b/src/test/java/org/microbean/construct/element/TestCyclicAnnotationMirrors.java @@ -0,0 +1,48 @@ +/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*- + * + * Copyright © 2026 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.util.List; + +import javax.lang.model.element.AnnotationMirror; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; + +final class TestCyclicAnnotationMirrors { + + private TestCyclicAnnotationMirrors() { + super(); + } + + @Test + final void test() { + // java.lang.annotation.Documented, to take one arbitrary example, is annotated + // with @java.lang.annotation.Documented. Prove you can do this with synthetic annotation constructs. This + // relies on some degree of mutability; I've chosen to locate it in getAnnotationMirrors(), which returns a + // thread-safe, mutable List. + final SyntheticAnnotationTypeElement documented = new SyntheticAnnotationTypeElement("java.lang.annotation.Documented"); + final SyntheticAnnotationMirror documentedAnnotation = new SyntheticAnnotationMirror(documented); + assertSame(documented, documentedAnnotation.getAnnotationType().asElement()); + final List ams = documented.getAnnotationMirrors(); + assertEquals(0, ams.size()); + documented.getAnnotationMirrors().add(documentedAnnotation); + assertSame(ams, documented.getAnnotationMirrors()); + assertEquals(1, ams.size()); + assertSame(documentedAnnotation, ams.get(0)); + } + +}