From a3d1dca19d0e52d05c3f1b9df4837dd895cddc13 Mon Sep 17 00:00:00 2001
From: Laird Nelson
Date: Thu, 22 Jan 2026 20:44:07 -0800
Subject: [PATCH] Road grading; adds annotation-related features
Signed-off-by: Laird Nelson
---
README.md | 2 +-
pom.xml | 108 ++++++
.../microbean/construct/DefaultDomain.java | 8 +
.../java/org/microbean/construct/Domain.java | 132 +++++--
.../construct/element/AnnotationMirrors.java | 362 +++++++++++++++---
.../SyntheticAnnotationTypeElement.java | 22 +-
6 files changed, 527 insertions(+), 107 deletions(-)
diff --git a/README.md b/README.md
index 499c8b9..bb52726 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.20
+ 0.0.21
```
diff --git a/pom.xml b/pom.xml
index 86d6f13..37bffde 100644
--- a/pom.xml
+++ b/pom.xml
@@ -148,6 +148,114 @@
maven-checkstyle-plugin
3.6.0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ project.basedir=${project.basedir}
+ project.build.sourceEncoding=${project.build.sourceEncoding}
+
+
+
+
+ com.puppycrawl.tools
+ checkstyle
+ 13.0.0
+
+
maven-clean-plugin
diff --git a/src/main/java/org/microbean/construct/DefaultDomain.java b/src/main/java/org/microbean/construct/DefaultDomain.java
index c8fdb84..5fdb52e 100644
--- a/src/main/java/org/microbean/construct/DefaultDomain.java
+++ b/src/main/java/org/microbean/construct/DefaultDomain.java
@@ -152,6 +152,14 @@ public DefaultDomain(final ProcessingEnvironment pe, final Lock lock) {
*/
+ @Override // Domain
+ public List extends UniversalElement> allMembers(TypeElement e) {
+ e = unwrap(e);
+ try (var lock = lock()) {
+ return UniversalElement.of(this.elements().getAllMembers(e), this);
+ }
+ }
+
/**
* Returns a {@link UniversalType} representing an {@link javax.lang.model.type.ArrayType} whose {@linkplain
* javax.lang.model.type.ArrayType#getComponentType() component type} {@linkplain #sameType(TypeMirror, TypeMirror) is
diff --git a/src/main/java/org/microbean/construct/Domain.java b/src/main/java/org/microbean/construct/Domain.java
index d9c033b..b13968f 100644
--- a/src/main/java/org/microbean/construct/Domain.java
+++ b/src/main/java/org/microbean/construct/Domain.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
@@ -23,7 +23,8 @@
import java.util.ArrayList;
import java.util.List;
-import java.util.Objects;
+
+import javax.lang.model.AnnotatedConstruct;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
@@ -53,12 +54,15 @@
import javax.lang.model.util.Elements;
import javax.lang.model.util.Elements.Origin;
+import org.microbean.construct.element.AnnotationMirrors;
import org.microbean.construct.element.UniversalElement;
import org.microbean.construct.type.UniversalType;
import static java.util.Collections.unmodifiableList;
+import static java.util.Objects.requireNonNull;
+
import static javax.lang.model.type.TypeKind.DECLARED;
/**
@@ -96,6 +100,10 @@ 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.
*
+ * The default implementation of this method calls {@link UniversalElement#of(Element, Domain)} with the supplied
+ * {@code element} and {@code this} as arguments, calls the {@link
+ * AnnotationMirrors#allAnnotationMirrors(Element)} with the result, and returns the result.
+ *
* @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
@@ -103,37 +111,91 @@ public interface Domain extends PrimordialDomain {
*
* @exception NullPointerException if {@code element} is {@code null}
*
+ * @see AnnotationMirrors#allAnnotationMirrors(Element)
+ *
* @see javax.lang.model.util.Elements#getAllAnnotationMirrors(Element)
*/
- public default List extends AnnotationMirror> 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());
+ public default List extends AnnotationMirror> allAnnotationMirrors(final Element element) {
+ return AnnotationMirrors.allAnnotationMirrors(UniversalElement.of(element, this)); // handles locking, symbol completion
+ }
+
+ /**
+ * Returns a non-{@code null}, determinate, immutable {@link List} whose elements comprise the members of
+ * the supplied {@link TypeElement}, whether they are inherited or declared directly.
+ *
+ * If the supplied {@link TypeElement} is a {@linkplain ElementKind#CLASS class}, the returned {@link List} will
+ * also include the class' constructors, but not any local or anonymous classes.
+ *
+ * @param e a {@link TypeElement}; must not be {@code null}
+ *
+ * @return a non-{@code null}, determinate, immutable {@link List} whose elements comprise the members of
+ * the supplied {@link TypeElement}, whether they are inherited or declared directly
+ *
+ * @exception NullPointerException if {@code e} is {@code null}
+ *
+ * @see Elements#getAllMembers(TypeElement)
+ */
+ public abstract List extends Element> allMembers(TypeElement e);
+
+ /**
+ * An experimental method that returns an {@link Element} that is {@linkplain Element#equals(Object)
+ * equal to} the supplied {@link Element} but {@linkplain Element#getAnnotationMirrors() with} a {@link List} of
+ * annotations {@linkplain List#equals(Object) equal to} the supplied {@link List} of {@link AnnotationMirror}s.
+ *
+ * The supplied {@link Element} may be modified in place if the return value of its {@link
+ * Element#getAnnotationMirrors() getAnnotationMirrors()} method is mutable.
+ *
+ * No validation of any kind is performed on either argument.
+ *
+ * @param annotations a {@link List} of {@link AnnotationMirror}s; must not be {@code null}
+ *
+ * @param e an {@link Element}; must not be {@code null}
+ *
+ * @return an {@link Element} that is {@linkplain Element#equals(Object) equal to} the supplied {@link Element} but
+ * {@linkplain Element#getAnnotationMirrors() with} a {@link List} of annotations {@linkplain List#equals(Object)
+ * equal to} the supplied {@link List} of {@link AnnotationMirror}s
+ *
+ * @exception NullPointerException if any argument is {@code null}
+ *
+ * @see UniversalElement#getAnnotationMirrors()
+ */
+ public default Element annotate(final List extends AnnotationMirror> annotations, final Element e) {
+ final UniversalElement ue = UniversalElement.of(e, this);
+ final List l = ue.getAnnotationMirrors();
+ l.clear();
+ l.addAll(annotations);
+ return ue;
+ }
+
+ /**
+ * An experimental method that returns a {@link TypeMirror} that is {@linkplain
+ * TypeMirror#equals(Object) equal to} the supplied {@link TypeMirror} but {@linkplain
+ * TypeMirror#getAnnotationMirrors() with} a {@link List} of annotations {@linkplain List#equals(Object) equal to} the
+ * supplied {@link List} of {@link AnnotationMirror}s.
+ *
+ * The supplied {@link TypeMirror} may be modified in place if the return value of its {@link
+ * TypeMirror#getAnnotationMirrors() getAnnotationMirrors()} method is mutable.
+ *
+ * No validation of any kind is performed on either argument.
+ *
+ * @param annotations a {@link List} of {@link AnnotationMirror}s; must not be {@code null}
+ *
+ * @param t a {@link TypeMirror}; must not be {@code null}
+ *
+ * @return a {@link TypeMirror} that is {@linkplain TypeMirror#equals(Object) equal to} the supplied {@link
+ * TypeMirror} but {@linkplain TypeMirror#getAnnotationMirrors() with} a {@link List} of annotations {@linkplain
+ * List#equals(Object) equal to} the supplied {@link List} of {@link AnnotationMirror}s
+ *
+ * @exception NullPointerException if any argument is {@code null}
+ *
+ * @see UniversalType#getAnnotationMirrors()
+ */
+ public default TypeMirror annotate(final List extends AnnotationMirror> annotations, final TypeMirror t) {
+ final UniversalType ut = UniversalType.of(t, this);
+ final List l = ut.getAnnotationMirrors();
+ l.clear();
+ l.addAll(annotations);
+ return ut;
}
/**
@@ -970,7 +1032,7 @@ public default boolean parameterized(final TypeMirror t) {
// (Convenience.)
// (Unboxing.)
public default PrimitiveType primitiveType(final CharSequence canonicalName) {
- final String s = this.toString(Objects.requireNonNull(canonicalName, "canonicalName"));
+ final String s = this.toString(requireNonNull(canonicalName, "canonicalName"));
return switch (s) {
case "boolean", "java.lang.Boolean" -> this.primitiveType(TypeKind.BOOLEAN);
case "byte", "java.lang.Byte" -> this.primitiveType(TypeKind.BYTE);
@@ -1462,8 +1524,8 @@ public default TypeElement typeElement(final TypeKind primitiveTypeKind) {
*/
// (Convenience.)
public default TypeParameterElement typeParameterElement(Parameterizable p, final CharSequence name) {
- Objects.requireNonNull(p, "p");
- Objects.requireNonNull(name, "name");
+ requireNonNull(p, "p");
+ requireNonNull(name, "name");
while (p != null) {
switch (p) {
case UniversalElement ue:
@@ -1532,7 +1594,7 @@ public default TypeVariable typeVariable(Parameterizable p, final CharSequence n
*/
// (Convenience.)
public default VariableElement variableElement(final Element enclosingElement, final CharSequence simpleName) {
- Objects.requireNonNull(simpleName, "simpleName");
+ requireNonNull(simpleName, "simpleName");
return switch (enclosingElement) {
case null -> throw new NullPointerException("enclosingElement");
case UniversalElement ue -> {
diff --git a/src/main/java/org/microbean/construct/element/AnnotationMirrors.java b/src/main/java/org/microbean/construct/element/AnnotationMirrors.java
index 6f8df60..9c79830 100644
--- a/src/main/java/org/microbean/construct/element/AnnotationMirrors.java
+++ b/src/main/java/org/microbean/construct/element/AnnotationMirrors.java
@@ -40,11 +40,14 @@
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Name;
import javax.lang.model.element.QualifiedNameable;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.ArrayType;
import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.PrimitiveType;
import javax.lang.model.type.TypeMirror;
import static java.util.Collections.unmodifiableList;
@@ -63,7 +66,12 @@
import static javax.lang.model.element.ElementKind.ANNOTATION_TYPE;
import static javax.lang.model.element.ElementKind.CLASS;
+import static javax.lang.model.element.ElementKind.METHOD;
+import static javax.lang.model.element.Modifier.ABSTRACT;
+import static javax.lang.model.element.Modifier.PUBLIC;
+
+import static javax.lang.model.type.TypeKind.ARRAY;
import static javax.lang.model.type.TypeKind.DECLARED;
import static javax.lang.model.util.ElementFilter.methodsIn;
@@ -107,7 +115,7 @@ public static final List extends AnnotationMirror> allAnnotationMirrors(Elemen
WHILE_LOOP:
while (e.getKind() == CLASS && e instanceof TypeElement te) {
final TypeMirror sct = te.getSuperclass();
- if (sct.getKind() != DECLARED) {
+ if (sct.getKind() != DECLARED || !(sct instanceof DeclaredType)) {
break;
}
e = ((DeclaredType)sct).asElement();
@@ -136,11 +144,11 @@ public static final List extends AnnotationMirror> allAnnotationMirrors(Elemen
* instances indexed by its {@link ExecutableElement}s to which they apply.
*
* Each {@link ExecutableElement} represents an annotation element and meets the
- * requirements of such an element.
+ * href="https://docs.oracle.com/javase/specs/jls/se25/html/jls-9.html#jls-9.6.1">annotation interface element and
+ * meets the requirements of such an element.
*
- * Each {@link AnnotationValue} represents the value of an annotation element and meets the requirements for
- * annotation values.
+ * Each {@link AnnotationValue} represents the value of an annotation interface element and meets the requirements
+ * for annotation values.
*
* This method is a more capable, better-typed replacement of the {@link
* javax.lang.model.util.Elements#getElementValuesWithDefaults(AnnotationMirror)} method, and should be preferred.
@@ -213,8 +221,9 @@ public static final AnnotationMirror get(final AnnotatedConstruct ac, final Char
}
/**
- * 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.
+ * A convenience method that returns a value for an annotation interface element {@linkplain
+ * ExecutableElement#getSimpleName() 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}
*
@@ -228,12 +237,38 @@ public static final AnnotationMirror get(final AnnotatedConstruct ac, final Char
* @see AnnotationValue
*
* @see #allAnnotationValues(AnnotationMirror)
+ *
+ * @see #get(Map, CharSequence)
*/
public static final Object get(final AnnotationMirror am, final CharSequence name) {
+ return name == null ? null : get(allAnnotationValues(am), name);
+ }
+
+ /**
+ * A convenience method that returns a value for an annotation interface element {@linkplain
+ * ExecutableElement#getSimpleName() named} by the supplied {@link CharSequence} and found in the supplied {@link
+ * Map}, or {@code null} if no such value exists.
+ *
+ * @param values a {@link Map} as returned by the {@link #allAnnotationValues(AnnotationMirror)} method; 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 a suitable {@link AnnotationValue}
+ * found in the supplied {@link Map}, or {@code null}
+ *
+ * @exception NullPointerException if {@code values} is {@code null}
+ *
+ * @see AnnotationValue
+ *
+ * @see #allAnnotationValues(AnnotationMirror)
+ */
+ public static final Object get(final Map extends ExecutableElement, ? extends AnnotationValue> values,
+ final CharSequence name) {
if (name == null) {
return null;
}
- for (final Entry extends Element, ? extends AnnotationValue> e : allAnnotationValues(am).entrySet()) {
+ for (final Entry extends Element, ? extends AnnotationValue> e : values.entrySet()) {
if (e.getKey().getSimpleName().contentEquals(name)) {
final AnnotationValue av = e.getValue();
return av == null ? null : av.getValue();
@@ -359,7 +394,7 @@ public static final boolean sameAnnotation(final AnnotationMirror am0, final Ann
* @param am1 an {@link AnnotationMirror}; may be {@code null}
*
* @param p a {@link Predicate} that returns {@code true} if a given {@link ExecutableElement}, representing an
- * annotation element, is to be included in the computation; may be {@code null} in which case it is as if
+ * annotation interface element, is to be included in the computation; may be {@code null} in which case it is as if
* {@code ()-> true} were supplied instead
*
* @return {@code true} if the supplied {@link AnnotationMirror}s represent the same (underlying, otherwise opaque)
@@ -377,9 +412,6 @@ public static final boolean sameAnnotation(final AnnotationMirror am0,
} else if (am0 == null || am1 == null) {
return false;
}
- if (p == null) {
- p = x -> true;
- }
final QualifiedNameable qn0 = (QualifiedNameable)am0.getAnnotationType().asElement();
final QualifiedNameable qn1 = (QualifiedNameable)am1.getAnnotationType().asElement();
if (qn0 != qn1 && !qn0.getQualifiedName().contentEquals(qn1.getQualifiedName())) {
@@ -387,9 +419,15 @@ public static final boolean sameAnnotation(final AnnotationMirror am0,
}
final SequencedMap m0 = allAnnotationValues(am0);
final SequencedMap m1 = allAnnotationValues(am1);
- if (m0.size() != m1.size()) {
+ if (m0 == m1) {
+ // Unlikely, but hey
+ return true;
+ } else if (m0.size() != m1.size()) {
return false;
}
+ if (p == null) {
+ p = AnnotationMirrors::returnTrue;
+ }
final Iterator> i0 = m0.entrySet().iterator();
final Iterator> i1 = m1.entrySet().iterator();
while (i0.hasNext()) {
@@ -406,24 +444,22 @@ public static final boolean sameAnnotation(final AnnotationMirror am0,
}
/**
- * 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
+ * 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.
*
- * The first and possibly only element in the {@link Stream} that is returned is guaranteed to be the supplied
- * {@link AnnotationMirror}.
+ * Cycles are avoided via usage of the {@link #sameAnnotation(AnnotationMirror, AnnotationMirror)} method.
*
- * @param am an {@link AnnotationMirror}; must not be {@code null}
+ * @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 am} is {@code null}
+ * @exception NullPointerException if {@code ac} is {@code null}
*
- * @see #streamBreadthFirst(AnnotatedConstruct)
+ * @see #sameAnnotation(AnnotationMirror, AnnotationMirror)
*/
- public static final Stream streamBreadthFirst(final AnnotationMirror am) {
- return concat(Stream.of(am), streamBreadthFirst(am.getAnnotationType().asElement()));
+ public static final Stream streamBreadthFirst(final AnnotatedConstruct ac) {
+ return streamBreadthFirst(ac, AnnotationMirrors::returnTrue);
}
/**
@@ -431,54 +467,120 @@ public static final Stream streamBreadthFirst(final Annotation
* {@linkplain AnnotatedConstruct#getAnnotationMirrors() annotations}, and their (meta-) annotations, in breadth-first
* order.
*
+ * Cycles and duplicates are avoided via usage of the {@link #sameAnnotation(AnnotationMirror, AnnotationMirror,
+ * Predicate)} method.
+ *
* @param ac an {@link AnnotatedConstruct}; must not be {@code null}
*
+ * @param p a {@link Predicate} that returns {@code true} if a given {@link ExecutableElement}, representing an
+ * annotation interface element, is to be included in comparison operations; may be {@code null} in which case it is
+ * as if {@code ()-> true} were supplied instead
+ *
* @return a non-{@code null} {@link Stream} of {@link AnnotationMirror}s
*
* @exception NullPointerException if {@code ac} is {@code null}
+ *
+ * @see #streamBreadthFirst(Collection, Predicate)
+ *
+ * @see #sameAnnotation(AnnotationMirror, AnnotationMirror)
*/
- public static final Stream streamBreadthFirst(final AnnotatedConstruct ac) {
- final List extends AnnotationMirror> ams = ac.getAnnotationMirrors();
- final Set names = newHashSet(ams.size());
+ public static final Stream streamBreadthFirst(final AnnotatedConstruct ac,
+ final Predicate super ExecutableElement> p) {
+ return streamBreadthFirst(ac.getAnnotationMirrors(), p);
+ }
+
+ /**
+ * Returns a non-{@code null}, sequential, {@link Stream} that traverses the supplied {@link AnnotationMirror}s, and
+ * their (meta-) annotations, in breadth-first order.
+ *
+ * Cycles and duplicates are avoided via usage of the {@link #sameAnnotation(AnnotationMirror, AnnotationMirror)}
+ * method. Consequently the returned {@link Stream} yields only semantically distinct elements.
+ *
+ * @param ams a {@link Collection} of {@link AnnotationMirror}s; must not be {@code null}
+ *
+ * @return a non-{@code null} {@link Stream} of (distinct) {@link AnnotationMirror}s
+ *
+ * @exception NullPointerException if {@code ams} is {@code null}
+ *
+ * @see #sameAnnotation(AnnotationMirror, AnnotationMirror)
+ */
+ public static final Stream streamBreadthFirst(final Collection extends AnnotationMirror> ams) {
+ return streamBreadthFirst(ams, AnnotationMirrors::returnTrue);
+ }
+
+ /**
+ * Returns a non-{@code null}, sequential, {@link Stream} that traverses the supplied {@link AnnotationMirror}s, and
+ * their (meta-) annotations, in breadth-first order.
+ *
+ * Cycles and duplicates are avoided via usage of the {@link #sameAnnotation(AnnotationMirror, AnnotationMirror,
+ * Predicate)} method. Consequently the returned {@link Stream} yields only semantically distinct elements.
+ *
+ * @param ams a {@link Collection} of {@link AnnotationMirror}s; must not be {@code null}
+ *
+ * @param p a {@link Predicate} that returns {@code true} if a given {@link ExecutableElement}, representing an
+ * annotation element, is to be included in comparison operations; may be {@code null} in which case it is as if
+ * {@code ()-> true} were supplied instead
+ *
+ * @return a non-{@code null} {@link Stream} of (distinct) {@link AnnotationMirror}s
+ *
+ * @exception NullPointerException if {@code ams} is {@code null}
+ *
+ * @see #sameAnnotation(AnnotationMirror, AnnotationMirror, Predicate)
+ */
+ public static final Stream streamBreadthFirst(final Collection extends AnnotationMirror> ams,
+ final Predicate super ExecutableElement> p) {
+ if (ams.isEmpty()) {
+ return empty();
+ }
+ final Collection seen = new ArrayList<>();
final Queue q = new ArrayDeque<>();
- ams.forEach(a -> {
- names.add(((QualifiedNameable)a.getAnnotationType().asElement()).getQualifiedName().toString());
- q.add(a);
- });
+ DEDUP_LOOP_0:
+ for (final AnnotationMirror a0 : ams) {
+ for (final AnnotationMirror a1 : seen) {
+ if (sameAnnotation(a0, a1, p)) {
+ continue DEDUP_LOOP_0;
+ }
+ }
+ q.add(a0);
+ seen.add(a0);
+ }
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);
+ DEDUP_LOOP_1:
+ for (final AnnotationMirror a1 : a0.getAnnotationType().asElement().getAnnotationMirrors()) {
+ for (final AnnotationMirror a2 : seen) {
+ if (sameAnnotation(a1, a2, p)) {
+ continue DEDUP_LOOP_1;
+ }
+ }
+ q.add(a1);
+ seen.add(a1);
+ }
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.
+ * 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.
*
- * The first and possibly only element in the {@link Stream} that is returned is guaranteed to be the supplied
- * {@link AnnotationMirror}.
+ * Cycles are avoided via usage of the {@link #sameAnnotation(AnnotationMirror, AnnotationMirror)} method.
*
- * @param am an {@link AnnotationMirror}; must not be {@code null}
+ * @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 am} is {@code null}
+ * @exception NullPointerException if {@code ac} is {@code null}
*
- * @see #streamDepthFirst(AnnotatedConstruct)
+ * @see #streamDepthFirst(AnnotatedConstruct, Predicate)
+ *
+ * @see #sameAnnotation(AnnotationMirror, AnnotationMirror)
*/
- // (Convenience.)
- public static final Stream streamDepthFirst(final AnnotationMirror am) {
- return concat(Stream.of(am), streamDepthFirst(am.getAnnotationType().asElement()));
+ public static final Stream streamDepthFirst(final AnnotatedConstruct ac) {
+ return streamDepthFirst(ac, AnnotationMirrors::returnTrue); // 17 == arbitrary
}
/**
@@ -486,16 +588,72 @@ public static final Stream streamDepthFirst(final AnnotationMi
* {@linkplain AnnotatedConstruct#getAnnotationMirrors() annotations}, and their (meta-) annotations, in depth-first
* order.
*
+ * Cycles and duplicates are avoided via usage of the {@link #sameAnnotation(AnnotationMirror, AnnotationMirror, Predicate)}
+ * method.
+ *
* @param ac an {@link AnnotatedConstruct}; must not be {@code null}
*
+ * @param p a {@link Predicate} that returns {@code true} if a given {@link ExecutableElement}, representing an
+ * annotation element, is to be included in comparison operations; may be {@code null} in which case it is as if
+ * {@code ()-> true} were supplied instead
+ *
* @return a non-{@code null} {@link Stream} of {@link AnnotationMirror}s
*
* @exception NullPointerException if {@code ac} is {@code null}
+ *
+ * @see #sameAnnotation(AnnotationMirror, AnnotationMirror)
*/
- public static final Stream streamDepthFirst(final AnnotatedConstruct ac) {
- return streamDepthFirst(ac, newHashSet(17)); // 17 == arbitrary
+ public static final Stream streamDepthFirst(final AnnotatedConstruct ac,
+ final Predicate super ExecutableElement> p) {
+ return streamDepthFirst(ac, new ArrayList<>(17), p); // 17 == arbitrary
+ }
+
+ /**
+ * Returns a non-{@code null}, sequential {@link Stream} that traverses the supplied {@link AnnotationMirror}s, and
+ * their (meta-) annotations, in depth-first order.
+ *
+ * Cycles and duplicates are avoided via usage of the {@link #sameAnnotation(AnnotationMirror, AnnotationMirror)}
+ * method.
+ *
+ * @param ams a non-{@code null} {@link Collection} of {@link AnnotationMirror}s
+ *
+ * @return a non-{@code null} {@link Stream} of {@link AnnotationMirror}s
+ *
+ * @exception NullPointerException if {@code ams} is {@code null}
+ *
+ * @see #streamDepthFirst(Collection, Predicate)
+ *
+ * @see #sameAnnotation(AnnotationMirror, AnnotationMirror)
+ */
+ public static final Stream streamDepthFirst(final Collection extends AnnotationMirror> ams) {
+ return streamDepthFirst(ams, AnnotationMirrors::returnTrue); // 17 == arbitrary
+ }
+
+ /**
+ * Returns a non-{@code null}, sequential {@link Stream} that traverses the supplied {@link AnnotationMirror}s, and
+ * their (meta-) annotations, in depth-first order.
+ *
+ * Cycles and duplicates are avoided via usage of the {@link #sameAnnotation(AnnotationMirror, AnnotationMirror,
+ * Predicate)} method.
+ *
+ * @param ams a non-{@code null} {@link Collection} of {@link AnnotationMirror}s
+ *
+ * @param p a {@link Predicate} that returns {@code true} if a given {@link ExecutableElement}, representing an
+ * annotation element, is to be included in comparison operations; may be {@code null} in which case it is as if
+ * {@code ()-> true} were supplied instead
+ *
+ * @return a non-{@code null} {@link Stream} of {@link AnnotationMirror}s
+ *
+ * @exception NullPointerException if {@code ams} is {@code null}
+ *
+ * @see #sameAnnotation(AnnotationMirror, AnnotationMirror)
+ */
+ public static final Stream streamDepthFirst(final Collection extends AnnotationMirror> ams,
+ final Predicate super ExecutableElement> p) {
+ return ams.isEmpty() ? empty() : streamDepthFirst(ams, new ArrayList<>(17), p); // 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
@@ -506,6 +664,8 @@ public static final Stream streamDepthFirst(final AnnotatedCon
* @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
+ *
+ * @exception NullPointerException if {@code a} is {@code null}
*/
public static final Set targetElementTypes(final AnnotationMirror a) {
return targetElementTypes((TypeElement)a.getAnnotationType().asElement());
@@ -521,6 +681,8 @@ public static final Set targetElementTypes(final AnnotationMirror a
* @return a non-{@code null}, determinate, immutable {@link Set} of {@link ElementType}s describing restrictions
* concerning where the supplied annotation interface may be applied
*
+ * @exception NullPointerException if {@code annotationInterface} is {@code null}
+ *
* @see java.lang.annotation.ElementType
*
* @see java.lang.annotation.Target
@@ -548,20 +710,112 @@ public static final Set targetElementTypes(final TypeElement annota
return EMPTY_ELEMENT_TYPES;
}
+ /**
+ * Returns {@code true} if and only if the supplied {@link ExecutableElement} represents a valid annotation
+ * interface element as defined by the Java Language Specification.
+ *
+ * @param e an {@link ExecutableElement}; must not be {@code null}
+ *
+ * @return {@code true} if and only if the supplied {@link ExecutableElement} represents a valid annotation
+ * interface element as defined by the Java Language Specification
+ *
+ * @exception NullPointerException if {@code e} is {@code null}
+ *
+ * @spec https://docs.oracle.com/javase/specs/jls/se25/html/jls-9.html#jls-9.6.1 Java Language Specification, section
+ * 9.6.1
+ */
+ public static final boolean validAnnotationInterfaceElement(final ExecutableElement e) {
+ return
+ e.getKind() == METHOD &&
+ e.getEnclosingElement().getKind() == ANNOTATION_TYPE &&
+ e.getTypeParameters().isEmpty() &&
+ e.getParameters().isEmpty() &&
+ e.getThrownTypes().isEmpty() &&
+ validAnnotationInterfaceElementType(e.getReturnType()) &&
+ (e.getModifiers().isEmpty() ||
+ e.getModifiers().equals(EnumSet.of(ABSTRACT)) ||
+ e.getModifiers().equals(EnumSet.of(PUBLIC)));
+ }
+
+ /**
+ * Returns {@code true} if and only if the supplied {@link TypeMirror} is a valid type for an annotation
+ * interface element as defined by the Java Language Specification.
+ *
+ * @param t a {@link TypeMirror}; must not be {@code null}
+ *
+ * @return {@code true} if and only if the supplied {@link TypeMirror} is a valid type for an annotation
+ * interface element as defined by the Java Language Specification
+ *
+ * @exception NullPointerException if {@code t} is {@code null}
+ *
+ * @spec https://docs.oracle.com/javase/specs/jls/se25/html/jls-9.html#jls-9.6.1 Java Language Specification, section
+ * 9.6.1
+ */
+ public static final boolean validAnnotationInterfaceElementType(final TypeMirror t) {
+ return switch (t) {
+ case null -> throw new NullPointerException("t");
+ case ArrayType at when at.getKind() == ARRAY -> validAnnotationInterfaceElementScalarType(at.getComponentType());
+ default -> validAnnotationInterfaceElementScalarType(t);
+ };
+ }
+
+ static final boolean validAnnotationInterfaceElementScalarType(final TypeMirror t) {
+ return switch (t) {
+ case null -> throw new NullPointerException("t");
+ case PrimitiveType pt when pt.getKind().isPrimitive() -> true;
+ case DeclaredType dt when dt.getKind() == DECLARED -> {
+ final TypeElement te = (TypeElement)dt.asElement();
+ yield switch (te.getKind()) {
+ case ANNOTATION_TYPE, ENUM -> true;
+ case CLASS -> {
+ final Name fqn = te.getQualifiedName();
+ yield fqn.contentEquals("java.lang.Class") || fqn.contentEquals("java.lang.String");
+ }
+ default -> false;
+ };
+ }
+ default -> false;
+ };
+ }
+
@SuppressWarnings("unchecked")
private static final SequencedMap emptySequencedMap() {
return (SequencedMap)EMPTY_MAP;
}
+ private static final boolean returnTrue(final X ignored) {
+ return true;
+ }
+
private static final Stream streamDepthFirst(final AnnotatedConstruct ac,
- final Set names) {
+ final Collection seen,
+ final Predicate super ExecutableElement> p) {
+ return streamDepthFirst(ac.getAnnotationMirrors(), seen, p);
+ }
+
+ private static final Stream streamDepthFirst(final Collection extends AnnotationMirror> ams,
+ final Collection seen,
+ final Predicate super ExecutableElement> p) {
+ if (ams.isEmpty()) {
+ return empty();
+ }
// See https://www.techempower.com/blog/2016/10/19/efficient-multiple-stream-concatenation-in-java/
return
- Stream.of(ac.getAnnotationMirrors()
+ Stream.of(ams
.stream()
- .flatMap(a -> {
- final TypeElement e = (TypeElement)a.getAnnotationType().asElement();
- return names.add(e.getQualifiedName().toString()) ? streamDepthFirst(e, names) : empty();
+ .sequential()
+ .flatMap(a0 -> {
+ for (final AnnotationMirror a1 : seen) {
+ if (sameAnnotation(a0, a1, p)) {
+ return empty();
+ }
+ }
+ seen.add(a0);
+ return streamDepthFirst(a0.getAnnotationType().asElement().getAnnotationMirrors(), seen, p);
}))
.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 202b13d..067d367 100644
--- a/src/main/java/org/microbean/construct/element/SyntheticAnnotationTypeElement.java
+++ b/src/main/java/org/microbean/construct/element/SyntheticAnnotationTypeElement.java
@@ -58,6 +58,8 @@
import static javax.lang.model.type.TypeKind.ARRAY;
import static javax.lang.model.type.TypeKind.DECLARED;
+import static org.microbean.construct.element.AnnotationMirrors.validAnnotationInterfaceElementScalarType;
+
/**
* An experimental {@link TypeElement} implementation that is wholly synthetic and suitable only for
* (partially) modeling {@linkplain AnnotationMirror#getAnnotationType() annotation types}.
@@ -420,24 +422,10 @@ public final String toString() {
// See https://docs.oracle.com/javase/specs/jls/se25/html/jls-9.html#jls-9.6.1
private static final TypeMirror validateScalarType(final TypeMirror type) {
- return switch (type) {
- case null -> throw new NullPointerException("t");
- case PrimitiveType t when t.getKind().isPrimitive() -> t;
- case DeclaredType t when t.getKind() == DECLARED -> {
- final TypeElement te = (TypeElement)t.asElement();
- yield switch (te.getKind()) {
- case ANNOTATION_TYPE, ENUM -> t;
- default -> {
- final Name fqn = te.getQualifiedName();
- if (fqn.contentEquals("java.lang.Class") || fqn.contentEquals("java.lang.String")) {
- yield t;
- }
- throw new IllegalArgumentException("type: " + type);
- }
- };
+ if (validAnnotationInterfaceElementScalarType(type)) {
+ return type;
}
- default -> throw new IllegalArgumentException("type: " + type);
- };
+ throw new IllegalArgumentException("type: " + type);
}