From 67e38cc201b781686cf45b84f59798400251c79c Mon Sep 17 00:00:00 2001
From: Laird Nelson
Date: Wed, 18 Feb 2026 17:37:05 -0800
Subject: [PATCH] Road grading; cleans documentation; adds notion of
well-defined annotation sameness
Signed-off-by: Laird Nelson
---
README.md | 2 +-
pom.xml | 4 +-
src/main/java/module-info.java | 4 +-
.../construct/BlockingCompilationTask.java | 7 +-
.../microbean/construct/DefaultDomain.java | 34 +--
.../java/org/microbean/construct/Domain.java | 8 +-
.../microbean/construct/PrimordialDomain.java | 14 +-
.../org/microbean/construct/Processor.java | 3 +-
.../RuntimeProcessingEnvironmentSupplier.java | 17 +-
.../construct/SymbolCompletionLock.java | 7 +-
.../construct/UniversalConstruct.java | 43 +++-
.../construct/element/AnnotationMirrors.java | 228 +++++++++++++-----
.../AnnotationValueHashcodeVisitor.java | 191 +++++++++++++++
.../element/SameAnnotationValueVisitor.java | 126 +++++++---
.../element/SyntheticAnnotationMirror.java | 2 +-
.../SyntheticAnnotationTypeElement.java | 49 ++--
.../construct/element/UniversalElement.java | 50 +++-
.../construct/element/package-info.java | 6 +-
.../org/microbean/construct/package-info.java | 8 +-
.../construct/type/UniversalType.java | 36 ++-
.../construct/type/package-info.java | 6 +-
21 files changed, 676 insertions(+), 169 deletions(-)
create mode 100644 src/main/java/org/microbean/construct/element/AnnotationValueHashcodeVisitor.java
diff --git a/README.md b/README.md
index f751ace..34a5678 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.22
+ 0.0.23
```
diff --git a/pom.xml b/pom.xml
index 3fa70ff..785b87c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -253,7 +253,7 @@
com.puppycrawl.tools
checkstyle
- 13.0.0
+ 13.2.0
@@ -284,7 +284,7 @@
maven-dependency-plugin
- 3.9.0
+ 3.10.0
maven-deploy-plugin
diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java
index 5e6fae9..2b74541 100644
--- a/src/main/java/module-info.java
+++ b/src/main/java/module-info.java
@@ -1,6 +1,6 @@
/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
*
- * Copyright © 2024 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
@@ -13,7 +13,7 @@
*/
/**
- * Provides packages related to Java constructs.
+ * Provides packages related to valid Java language constructs.
*
* @author Laird Nelson
*
diff --git a/src/main/java/org/microbean/construct/BlockingCompilationTask.java b/src/main/java/org/microbean/construct/BlockingCompilationTask.java
index 623b751..3b6def8 100644
--- a/src/main/java/org/microbean/construct/BlockingCompilationTask.java
+++ b/src/main/java/org/microbean/construct/BlockingCompilationTask.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
@@ -170,7 +170,7 @@ public final void run() {
options.add("-verbose");
}
- // Use an appropriate compiler-supplied thread-safe name table.
+ // Use an appropriate compiler-supplied name table. None are thread-safe, sadly.
installNameTableOptions(options::add);
// Propagate the current classpath to the compiler environment.
@@ -193,7 +193,7 @@ public final void run() {
moduleLocations),
diagnosticLogger,
options,
- List.of("java.lang.Deprecated"), // arbitrary, but is always read by the compiler no matter what so incurs no extra reads
+ List.of("java.lang.Deprecated"), // arbitrary, but always read by the compiler no matter what so incurs no extra reads
null); // no compilation units; we're -proc:only
task.setLocale(locale);
task.addModules(additionalRootModuleNames);
@@ -252,6 +252,7 @@ private final Set additionalRootModuleNames(final Consumer super ReadO
return Collections.unmodifiableSet(additionalRootModuleNames);
}
+ // (Invoked only by method reference.)
private final void obtrudeException() {
// Ideally we'd use CancellationException but see
// https://github.com/openjdk/jdk/blob/jdk-25%2B3/src/java.base/share/classes/java/util/concurrent/CompletableFuture.java#L2210.
diff --git a/src/main/java/org/microbean/construct/DefaultDomain.java b/src/main/java/org/microbean/construct/DefaultDomain.java
index 5fdb52e..8e68663 100644
--- a/src/main/java/org/microbean/construct/DefaultDomain.java
+++ b/src/main/java/org/microbean/construct/DefaultDomain.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
@@ -55,7 +55,7 @@
import static java.lang.constant.ConstantDescs.BSM_INVOKE;
/**
- * A {@linkplain Domain domain of Java constructs} that can be used at annotation processing time or at runtime.
+ * A {@linkplain Domain domain of valid Java constructs} that can be used at annotation processing time or at runtime.
*
* @author Laird Nelson
*
@@ -83,13 +83,15 @@ public DefaultDomain() {
* 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()}
- * will be used instead
+ * @param pe a {@link ProcessingEnvironment}; may be {@code null} (the expected value for runtime
+ * usage), in which case the return value of an invocation of {@link Supplier#get()} on the return value of
+ * an invocation of {@link RuntimeProcessingEnvironmentSupplier#of()} will be used instead
*
* @see #DefaultDomain(ProcessingEnvironment, Lock)
*
- * @see SymbolCompletionLock
+ * @see RuntimeProcessingEnvironmentSupplier#get()
+ *
+ * @see SymbolCompletionLock#INSTANCE
*/
public DefaultDomain(final ProcessingEnvironment pe) {
this(pe, null);
@@ -103,9 +105,9 @@ public DefaultDomain(final ProcessingEnvironment pe) {
*
* @see #DefaultDomain(ProcessingEnvironment, Lock)
*
- * @see RuntimeProcessingEnvironmentSupplier
+ * @see RuntimeProcessingEnvironmentSupplier#get()
*
- * @see SymbolCompletionLock
+ * @see SymbolCompletionLock#INSTANCE
*/
public DefaultDomain(final Lock lock) {
this(null, lock);
@@ -115,18 +117,18 @@ public DefaultDomain(final Lock lock) {
* 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()}
- * will be used instead
+ * @param pe a {@link ProcessingEnvironment}; may be {@code null} (the expected value for runtime
+ * usage), in which case the return value of an invocation of {@link Supplier#get()} on the return value of
+ * an invocation of {@link RuntimeProcessingEnvironmentSupplier#of()} will be used instead
*
* @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} therefore will not be safe
* for concurrent use by multiple threads
*
- * @see RuntimeProcessingEnvironmentSupplier
+ * @see RuntimeProcessingEnvironmentSupplier#get()
*
- * @see SymbolCompletionLock
+ * @see SymbolCompletionLock#INSTANCE
*/
public DefaultDomain(final ProcessingEnvironment pe, final Lock lock) {
super();
@@ -159,7 +161,7 @@ public List extends UniversalElement> allMembers(TypeElement e) {
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
@@ -611,8 +613,10 @@ public UniversalType wildcardType(TypeMirror extendsBound, TypeMirror superBound
*/
+ // (Invoked only by method reference.)
private static final void doNothing() {}
+ // (Invoked only by method reference.)
private static final Unlockable noopLock() {
return DefaultDomain::doNothing;
}
@@ -626,7 +630,7 @@ private static final E unwrap(final E e) {
}
private static final TypeMirror[] unwrap(final TypeMirror[] ts) {
- if (ts == null || ts.length <= 0) {
+ if (ts == null || ts.length == 0) {
return ts;
}
final TypeMirror[] rv = new TypeMirror[ts.length];
diff --git a/src/main/java/org/microbean/construct/Domain.java b/src/main/java/org/microbean/construct/Domain.java
index b13968f..6857648 100644
--- a/src/main/java/org/microbean/construct/Domain.java
+++ b/src/main/java/org/microbean/construct/Domain.java
@@ -66,10 +66,10 @@
import static javax.lang.model.type.TypeKind.DECLARED;
/**
- * A representation of a domain of valid Java constructs.
+ * A {@link PrimordialDomain} that represents a domain of valid Java language constructs.
*
- * A domain is a set of valid Java constructs. A {@link Domain}
- * provides access to a domain and its members.
+ * A domain is a set of valid Java language constructs. A {@link
+ * Domain} provides access to a domain and its members.
*
* A Java construct is either a type or an element.
@@ -91,6 +91,8 @@
*
* @see PrimordialDomain
*
+ * @see DefaultDomain
+ *
* @see JDK-8055219
*/
@SuppressWarnings("try")
diff --git a/src/main/java/org/microbean/construct/PrimordialDomain.java b/src/main/java/org/microbean/construct/PrimordialDomain.java
index eb11a26..d2922f5 100644
--- a/src/main/java/org/microbean/construct/PrimordialDomain.java
+++ b/src/main/java/org/microbean/construct/PrimordialDomain.java
@@ -1,6 +1,6 @@
/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
*
- * Copyright © 2025 microBean™.
+ * Copyright © 2025–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
@@ -42,6 +42,8 @@
* @see #noType(TypeKind)
*
* @see #nullType()
+ *
+ * @see Domain
*/
public interface PrimordialDomain {
@@ -95,7 +97,7 @@ public interface PrimordialDomain {
/**
* Returns a (non-{@code null}, determinate) {@link NullType} representing the null type.
*
- * Implementations of thsi method must not return {@code null}.
+ * Implementations of this method must not return {@code null}.
*
* @return a {@link NullType} representing the null type; never {@code null}
*
@@ -110,9 +112,9 @@ public interface PrimordialDomain {
* 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.
+ * In many implementations of {@link PrimordialDomain}s, 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.
*
@@ -139,5 +141,5 @@ public default String toString(final CharSequence name) {
default -> name.toString();
};
}
-
+
}
diff --git a/src/main/java/org/microbean/construct/Processor.java b/src/main/java/org/microbean/construct/Processor.java
index 20e0cff..768c696 100644
--- a/src/main/java/org/microbean/construct/Processor.java
+++ b/src/main/java/org/microbean/construct/Processor.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
@@ -204,6 +204,7 @@ public final boolean process(final Set extends TypeElement> annotations, final
return false;
}
+ // (Invoked only by method reference.)
private static final void sink() {}
}
diff --git a/src/main/java/org/microbean/construct/RuntimeProcessingEnvironmentSupplier.java b/src/main/java/org/microbean/construct/RuntimeProcessingEnvironmentSupplier.java
index 3b15ee2..d61331d 100644
--- a/src/main/java/org/microbean/construct/RuntimeProcessingEnvironmentSupplier.java
+++ b/src/main/java/org/microbean/construct/RuntimeProcessingEnvironmentSupplier.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
@@ -29,6 +29,9 @@
/**
* A utility class that can {@linkplain #of() supply} a {@link ProcessingEnvironment} suitable for use at runtime.
*
+ * Most users should simply {@linkplain DefaultDomain#DefaultDomain() use an appropriate DefaultDomain}
+ * instead of working directly with instances of this class.
+ *
* @author Laird Nelson
*
* @see #get()
@@ -39,7 +42,7 @@
*
* @see ProcessingEnvironment
*
- * @see Domain
+ * @see DefaultDomain#DefaultDomain()
*/
public final class RuntimeProcessingEnvironmentSupplier implements AutoCloseable, Supplier {
@@ -103,9 +106,12 @@ public final void close() {
* {@link ProcessingEnvironment} instances are not guaranteed to be safe for concurrent use by multiple
* threads.
*
+ * Most users should simply use an appropriate {@link DefaultDomain} instead of working directly with instances of
+ * this class.
+ *
* @return a non-{@code null} {@link ProcessingEnvironment}
*
- * @see Domain
+ * @see DefaultDomain#DefaultDomain()
*/
@Override // Supplier
public final ProcessingEnvironment get() {
@@ -139,11 +145,14 @@ private static final BlockingCompilationTask install(final Consumer super Bloc
/**
* Returns a non-{@code null} {@link RuntimeProcessingEnvironmentSupplier}.
*
+ * Most users should simply use an appropriate {@link DefaultDomain} instead of working directly with instances of
+ * this class.
+ *
* @return a non-{@code null} {@link RuntimeProcessingEnvironmentSupplier}
*
* @see ProcessingEnvironment
*
- * @see Domain
+ * @see DefaultDomain#DefaultDomain()
*/
public static final RuntimeProcessingEnvironmentSupplier of() {
return INSTANCE;
diff --git a/src/main/java/org/microbean/construct/SymbolCompletionLock.java b/src/main/java/org/microbean/construct/SymbolCompletionLock.java
index 2dc9590..ffd35bb 100644
--- a/src/main/java/org/microbean/construct/SymbolCompletionLock.java
+++ b/src/main/java/org/microbean/construct/SymbolCompletionLock.java
@@ -1,6 +1,6 @@
/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
*
- * Copyright © 2024 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
@@ -19,6 +19,9 @@
* A class holding a {@link ReentrantLock} that should be used to serialize symbol completion and name
* expansion.
*
+ * Most users should simply {@linkplain DefaultDomain#DefaultDomain() use an appropriate DefaultDomain}
+ * instead of working directly with instances of this class.
+ *
* @author Laird Nelson
*
* @see #INSTANCE
@@ -26,6 +29,8 @@
* @see Domain#lock()
*
* @see Domain#toString(CharSequence)
+ *
+ * @see DefaultDomain#DefaultDomain()
*/
public final class SymbolCompletionLock {
diff --git a/src/main/java/org/microbean/construct/UniversalConstruct.java b/src/main/java/org/microbean/construct/UniversalConstruct.java
index adef09a..28aba4e 100644
--- a/src/main/java/org/microbean/construct/UniversalConstruct.java
+++ b/src/main/java/org/microbean/construct/UniversalConstruct.java
@@ -20,6 +20,7 @@
import java.lang.constant.ConstantDesc;
import java.lang.constant.DynamicConstantDesc;
+import java.util.Collection;
import java.util.List;
import java.util.Optional;
@@ -64,7 +65,7 @@
* @see UniversalType
*/
public abstract sealed class UniversalConstruct
- implements AnnotatedConstruct, Constable
+ implements AnnotatedConstruct, Cloneable, Constable
permits UniversalElement, UniversalType {
@@ -100,7 +101,27 @@ public abstract sealed class UniversalConstruct
/**
- * Creates a new {@link AnnotatedConstruct}.
+ * Creates a new {@link UniversalConstruct} that is a copy of the supplied {@link UniversalConstruct}.
+ *
+ * @param uc a non-{@code null} {@link UniversalConstruct}
+ *
+ * @exception NullPointerException if {@code uc} is {@code null}
+ *
+ * @see #clone()
+ */
+ protected UniversalConstruct(final UniversalConstruct uc) {
+ super();
+ this.domain = uc.domain;
+ this.delegateSupplier = uc.delegateSupplier;
+ this.s = uc.s;
+ final Collection extends AnnotationMirror> annotations = uc.annotations; // volatile read
+ if (annotations != null) {
+ this.annotations = new CopyOnWriteArrayList<>(annotations);
+ }
+ }
+
+ /**
+ * Creates a new {@link UniversalConstruct}.
*
* @param delegate a delegate to which operations are delegated; must not be {@code null}
*
@@ -116,7 +137,7 @@ protected UniversalConstruct(final T delegate, final PrimordialDomain domain) {
}
/**
- * Creates a new {@link AnnotatedConstruct}.
+ * Creates a new {@link UniversalConstruct}.
*
* @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
@@ -164,6 +185,21 @@ protected UniversalConstruct(final List extends AnnotationMirror> annotations,
*/
+ @Override // Cloneable
+ @SuppressWarnings("unchecked")
+ protected UniversalConstruct clone() {
+ final UniversalConstruct clone;
+ try {
+ clone = (UniversalConstruct)super.clone();
+ } catch (final CloneNotSupportedException e) {
+ throw new AssertionError(e.getMessage(), e);
+ }
+ if (clone.annotations != null) {
+ clone.annotations = new CopyOnWriteArrayList<>(this.getAnnotationMirrors());
+ }
+ return clone;
+ }
+
/**
* Returns the delegate to which operations are delegated.
*
@@ -299,7 +335,6 @@ public final A getAnnotation(final Class annotationTyp
}
}
return null;
-
}
/**
diff --git a/src/main/java/org/microbean/construct/element/AnnotationMirrors.java b/src/main/java/org/microbean/construct/element/AnnotationMirrors.java
index 9c79830..ca72bb6 100644
--- a/src/main/java/org/microbean/construct/element/AnnotationMirrors.java
+++ b/src/main/java/org/microbean/construct/element/AnnotationMirrors.java
@@ -88,8 +88,6 @@ public final class AnnotationMirrors {
private static final SequencedMap, ?> EMPTY_MAP = unmodifiableSequencedMap(newLinkedHashMap(0));
- private static final SameAnnotationValueVisitor sameAnnotationValueVisitor = new SameAnnotationValueVisitor();
-
private AnnotationMirrors() {
super();
}
@@ -140,21 +138,21 @@ public static final List extends AnnotationMirror> allAnnotationMirrors(Elemen
}
/**
- * 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.
+ * For the supplied {@link AnnotationMirror}, returns an immutable, determinate {@link SequencedMap} of {@link
+ * AnnotationValue} instances indexed by the {@link ExecutableElement}s to which they apply.
*
* Each {@link ExecutableElement} represents an annotation interface element and
* meets the requirements of such an element.
*
* Each {@link AnnotationValue} represents the value of an annotation interface element and meets the requirements
- * for annotation values.
+ * of such a value.
*
* This method is a more capable, better-typed replacement of the {@link
* javax.lang.model.util.Elements#getElementValuesWithDefaults(AnnotationMirror)} method, and should be preferred.
*
* @param a an {@link AnnotationMirror}; may be {@code null} in which case an empty, immutable, determinate {@link
- * Map} will be returned
+ * SequencedMap} will be returned
*
* @return an immutable, determinate {@link Map} of {@link AnnotationValue} instances indexed by {@link
* ExecutableElement}s
@@ -181,6 +179,78 @@ public static final SequencedMap allAnnotati
return m.isEmpty() ? emptySequencedMap() : unmodifiableSequencedMap(m);
}
+ /**
+ * Returns {@code true} if and only if the supplied {@link Collection} of {@link AnnotationMirror}s contains an {@link
+ * AnnotationMirror} that is {@linkplain #sameAnnotation(AnnotationMirror, AnnotationMirror, Predicate) the same} as
+ * the supplied {@link AnnotationMirror}.
+ *
+ * @param c a non-{@code null} {@link Collection} of {@link AnnotationMirror}s
+ *
+ * @param a a non-{@code null} {@link AnnotationMirror}
+ *
+ * @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 e -> true} were supplied instead
+ *
+ * @return {@code true} if and only if the supplied {@link Collection} of {@link AnnotationMirror}s contains an {@link
+ * AnnotationMirror} that is {@linkplain #sameAnnotation(AnnotationMirror, AnnotationMirror, Predicate) the same} as
+ * the supplied {@link AnnotationMirror}
+ *
+ * @exception NullPointerException if {@code c} or {@code a} is {@code null}
+ *
+ * @see #sameAnnotation(AnnotationMirror, AnnotationMirror, Predicate)
+ */
+ public static final boolean contains(final Collection extends AnnotationMirror> c,
+ final AnnotationMirror a,
+ final Predicate super ExecutableElement> p) {
+ if (c.isEmpty()) {
+ return false;
+ }
+ for (final AnnotationMirror ca : c) {
+ if (sameAnnotation(ca, a, p)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns {@code true} if and only if {@code c0} contains {@linkplain #sameAnnotation(AnnotationMirror,
+ * AnnotationMirror, Predicate) the same} {@link AnnotationMirror}s as are found in {@code c1},
+ *
+ * @param c0 a non-{@code null} {@link Collection} of {@link AnnotationMirror}s
+ *
+ * @param c1 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 e -> true} were supplied instead
+ *
+ * @return {@code true} if and only if {@code c0} contains {@linkplain #sameAnnotation(AnnotationMirror,
+ * AnnotationMirror, Predicate) the same} {@link AnnotationMirror}s as are found in {@code c1}
+ *
+ * @exception NullPointerException if either {@code c0} or {@code c1} is {@code null}
+ *
+ * @see #sameAnnotation(AnnotationMirror, AnnotationMirror, Predicate)
+ */
+ public static final boolean containsAll(final Collection extends AnnotationMirror> c0,
+ final Collection extends AnnotationMirror> c1,
+ final Predicate super ExecutableElement> p) {
+ if (c0.size() < c1.size()) {
+ return false;
+ }
+ OUTER_LOOP:
+ for (final AnnotationMirror a0 : c0) {
+ for (final AnnotationMirror a1 : c1) {
+ if (sameAnnotation(a0, a1, p)) {
+ continue OUTER_LOOP;
+ }
+ }
+ return false;
+ }
+ return true;
+ }
+
/**
* Returns the (determinate) {@link AnnotationMirror} directly present on the supplied {@link
* AnnotatedConstruct} whose {@linkplain AnnotationMirror#getAnnotationType() annotation type}'s {@link
@@ -277,6 +347,44 @@ public static final Object get(final Map extends ExecutableElement, ? extends
return null;
}
+ /**
+ * An experimental method that returns a hashcode for an {@link AnnotationMirror} according as much
+ * as possible to the rules described in the {@link java.lang.annotation.Annotation#hashCode()} contract.
+ *
+ * @param a an {@link AnnotationMirror} for which a hashcode should be computed; may be {@code null} in which case
+ * {@code 0} will be returned
+ *
+ * @return a hashcode for the supplied {@link AnnotationMirror} computed according to the rules described in the
+ * {@link java.lang.annotation.Annotation#hashCode()} contract
+ *
+ * @see #hashCode(AnnotationMirror, Predicate)
+ *
+ * @see java.lang.annotation.Annotation#hashCode()
+ */
+ public static final int hashCode(final AnnotationMirror a) {
+ return hashCode(a, null);
+ }
+
+ /**
+ * An experimental method that returns a hashcode for an {@link AnnotationMirror} according as much
+ * as possible to the rules described in the {@link java.lang.annotation.Annotation#hashCode()} contract.
+ *
+ * @param a an {@link AnnotationMirror} for which a hashcode should be computed; may be {@code null} in which case
+ * {@code 0} will be returned
+ *
+ * @param p a {@link Predicate} that accepts a (non-{@code null}) {@link ExecutableElement} representing an annotation
+ * element and returns {@code true} if and only if the element should be included; may be {@code null} in which case a
+ * {@link Predicate} semantically identical to {@code ee -> true} will be used instead
+ *
+ * @return a hashcode for the supplied {@link AnnotationMirror} computed according to the rules described in the
+ * {@link java.lang.annotation.Annotation#hashCode()} contract
+ *
+ * @see java.lang.annotation.Annotation#hashCode()
+ */
+ public static final int hashCode(final AnnotationMirror a, final Predicate super ExecutableElement> p) {
+ return a == null ? 0 : new AnnotationValueHashcodeVisitor().visitAnnotation(a, p == null ? ee -> true : p).intValue();
+ }
+
/**
* 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()
@@ -383,7 +491,7 @@ public static final RetentionPolicy retentionPolicy(final TypeElement annotation
* @see #sameAnnotation(AnnotationMirror, AnnotationMirror)
*/
public static final boolean sameAnnotation(final AnnotationMirror am0, final AnnotationMirror am1) {
- return sameAnnotation(am0, am1, x -> true);
+ return sameAnnotation(am0, am1, null);
}
/**
@@ -395,60 +503,57 @@ public static final boolean sameAnnotation(final AnnotationMirror am0, final Ann
*
* @param p a {@link Predicate} that returns {@code true} if a given {@link ExecutableElement}, representing an
* 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
+ * {@code e -> true} were supplied instead
*
* @return {@code true} if the supplied {@link AnnotationMirror}s represent the same (underlying, otherwise opaque)
* annotation; {@code false} otherwise
*
* @see SameAnnotationValueVisitor
*
+ * @see SameAnnotationValueVisitor#visitAnnotation(AnnotationMirror, Object)
+ *
* @see #allAnnotationValues(AnnotationMirror)
*/
public static final boolean sameAnnotation(final AnnotationMirror am0,
final AnnotationMirror am1,
- Predicate super ExecutableElement> p) {
- if (am0 == am1) {
- return true;
- } else if (am0 == null || am1 == null) {
- return false;
- }
- final QualifiedNameable qn0 = (QualifiedNameable)am0.getAnnotationType().asElement();
- final QualifiedNameable qn1 = (QualifiedNameable)am1.getAnnotationType().asElement();
- if (qn0 != qn1 && !qn0.getQualifiedName().contentEquals(qn1.getQualifiedName())) {
- return false;
- }
- final SequencedMap m0 = allAnnotationValues(am0);
- final SequencedMap m1 = allAnnotationValues(am1);
- 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()) {
- final Entry e0 = i0.next();
- final Entry e1 = i1.next();
- final ExecutableElement ee0 = e0.getKey();
- if (p.test(ee0) &&
- (!e0.getKey().getSimpleName().contentEquals(e1.getKey().getSimpleName()) ||
- !sameAnnotationValueVisitor.visit(e0.getValue(), e1.getValue().getValue()))) {
- return false;
- }
- }
- return !i1.hasNext();
+ final Predicate super ExecutableElement> p) {
+ return am0 == am1 || new SameAnnotationValueVisitor(p).visitAnnotation(am0, am1);
}
/**
- * Returns a non-{@code null}, sequential, {@link Stream} that traverses the supplied {@link AnnotatedConstruct}'s
+ * Returns {@code true} if {@code c0} has all the {@linkplain #sameAnnotation(AnnotationMirror, AnnotationMirror,
+ * Predicate) same annotations} as {@code c1}, and if {@code c1} has all the {@linkplain
+ * #sameAnnotation(AnnotationMirror, AnnotationMirror, Predicate) same annotations} as {@code c0}.
+ *
+ * @param c0 a non-{@code null} {@link Collection} of {@link AnnotationMirror}s
+ *
+ * @param c1 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 interface element, is to be included in the computation; may be {@code null} in which case it is as if
+ * {@code e -> true} were supplied instead
+ *
+ * @return {@code true} if {@code c0} has all the {@linkplain #sameAnnotation(AnnotationMirror, AnnotationMirror,
+ * Predicate) same annotations} as {@code c1}, and if {@code c1} has all the {@linkplain
+ * #sameAnnotation(AnnotationMirror, AnnotationMirror, Predicate) same annotations} as {@code c0}
+ *
+ * @exception NullPointerException if either {@code c0} or {@code c1} is {@code null}
+ *
+ * @see #sameAnnotation(AnnotationMirror, AnnotationMirror, Predicate)
+ */
+ public static final boolean sameAnnotations(final Collection extends AnnotationMirror> c0,
+ final Collection extends AnnotationMirror> c1,
+ final Predicate super ExecutableElement> p) {
+ return c0.size() == c1.size() && containsAll(c0, c1, p) && containsAll(c1, c0, p);
+ }
+
+ /**
+ * 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.
*
- * Cycles are avoided via usage of the {@link #sameAnnotation(AnnotationMirror, AnnotationMirror)} method.
+ * 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 ac an {@link AnnotatedConstruct}; must not be {@code null}
*
@@ -463,18 +568,18 @@ public static final Stream streamBreadthFirst(final AnnotatedC
}
/**
- * Returns a non-{@code null}, sequential, {@link Stream} that traverses the supplied {@link AnnotatedConstruct}'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.
*
* Cycles and duplicates are avoided via usage of the {@link #sameAnnotation(AnnotationMirror, AnnotationMirror,
- * Predicate)} method.
+ * Predicate)} method. Consequently the returned {@link Stream} yields only semantically distinct elements.
*
* @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
+ * as if {@code e -> true} were supplied instead
*
* @return a non-{@code null} {@link Stream} of {@link AnnotationMirror}s
*
@@ -490,7 +595,7 @@ public static final Stream streamBreadthFirst(final AnnotatedC
}
/**
- * Returns a non-{@code null}, sequential, {@link Stream} that traverses the supplied {@link AnnotationMirror}s, and
+ * 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)}
@@ -509,7 +614,7 @@ public static final Stream streamBreadthFirst(final Collection
}
/**
- * Returns a non-{@code null}, sequential, {@link Stream} that traverses the supplied {@link AnnotationMirror}s, and
+ * 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,
@@ -519,7 +624,7 @@ public static final Stream streamBreadthFirst(final Collection
*
* @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
+ * {@code e -> true} were supplied instead
*
* @return a non-{@code null} {@link Stream} of (distinct) {@link AnnotationMirror}s
*
@@ -567,7 +672,8 @@ public static final Stream streamBreadthFirst(final Collection
* {@linkplain AnnotatedConstruct#getAnnotationMirrors() annotations}, and their (meta-) annotations, in depth-first
* order.
*
- * Cycles are avoided via usage of the {@link #sameAnnotation(AnnotationMirror, AnnotationMirror)} method.
+ * 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 ac an {@link AnnotatedConstruct}; must not be {@code null}
*
@@ -588,16 +694,16 @@ public static final Stream streamDepthFirst(final AnnotatedCon
* {@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.
+ * 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 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
+ * {@code e -> true} were supplied instead
*
- * @return a non-{@code null} {@link Stream} of {@link AnnotationMirror}s
+ * @return a non-{@code null}, sequential {@link Stream} of {@link AnnotationMirror}s
*
* @exception NullPointerException if {@code ac} is {@code null}
*
@@ -613,11 +719,11 @@ public static final Stream streamDepthFirst(final AnnotatedCon
* their (meta-) annotations, in depth-first order.
*
* Cycles and duplicates are avoided via usage of the {@link #sameAnnotation(AnnotationMirror, AnnotationMirror)}
- * method.
+ * method. Consequently the returned {@link Stream} yields only semantically distinct elements.
*
* @param ams a non-{@code null} {@link Collection} of {@link AnnotationMirror}s
*
- * @return a non-{@code null} {@link Stream} of {@link AnnotationMirror}s
+ * @return a non-{@code null}, seqwuential {@link Stream} of {@link AnnotationMirror}s
*
* @exception NullPointerException if {@code ams} is {@code null}
*
@@ -634,15 +740,15 @@ public static final Stream streamDepthFirst(final Collection
* their (meta-) annotations, in depth-first order.
*
* Cycles and duplicates are avoided via usage of the {@link #sameAnnotation(AnnotationMirror, AnnotationMirror,
- * Predicate)} method.
+ * Predicate)} method. Consequently the returned {@link Stream} yields only semantically distinct elements.
*
* @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
+ * {@code e -> true} were supplied instead
*
- * @return a non-{@code null} {@link Stream} of {@link AnnotationMirror}s
+ * @return a non-{@code null}, sequential {@link Stream} of {@link AnnotationMirror}s
*
* @exception NullPointerException if {@code ams} is {@code null}
*
diff --git a/src/main/java/org/microbean/construct/element/AnnotationValueHashcodeVisitor.java b/src/main/java/org/microbean/construct/element/AnnotationValueHashcodeVisitor.java
new file mode 100644
index 0000000..b5b3af0
--- /dev/null
+++ b/src/main/java/org/microbean/construct/element/AnnotationValueHashcodeVisitor.java
@@ -0,0 +1,191 @@
+/* -*- 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 java.util.Map;
+
+import java.util.function.Predicate;
+
+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.VariableElement;
+
+import javax.lang.model.type.TypeMirror;
+
+import javax.lang.model.util.AbstractAnnotationValueVisitor14;
+
+import static javax.lang.model.element.ElementKind.METHOD;
+
+/**
+ * An {@link AbstractAnnotationValueVisitor14} that computes a hashcode for an {@link AnnotationValue}, emulating as
+ * closely as possible the rules described by the {@link java.lang.annotation.Annotation#hashCode()} contract.
+ *
+ * @author Laird Nelson
+ *
+ * @see SameAnnotationValueVisitor
+ */
+// Goal is to emulate as much as possible the rules from java.lang.annotation.Annotation which capture what it means for
+// two annotations to be "the same", and read in part as follows:
+//
+// The hash code of an annotation is the sum of the hash codes of its members (including those with default values). The
+// hash code of an annotation member is (127 times the hash code of the member-name as computed by String.hashCode())
+// XOR the hash code of the member-value. The hash code of a member-value depends on its type as defined below:
+//
+// * The hash code of a primitive value v is equal to WrapperType.valueOf(v).hashCode() [or, equivalently,
+// WrapperType.hashCode(v)], where WrapperType is the wrapper type corresponding to the primitive type of v (Byte,
+// Character, Double, Float, Integer, Long, Short, or Boolean).
+//
+// * The hash code of a string, enum, class, or annotation member-value v is computed as by calling v.hashCode(). (In
+// the case of annotation member values, this is a recursive definition.)
+//
+// * The hash code of an array member-value is computed by calling the appropriate overloading of Arrays.hashCode on
+// the value. (There is one overloading for each primitive type, and one for object reference types.)
+//
+public final class AnnotationValueHashcodeVisitor extends AbstractAnnotationValueVisitor14> {
+
+ /**
+ * Creates a new {@link AnnotationValueHashcodeVisitor}.
+ */
+ public AnnotationValueHashcodeVisitor() {
+ super();
+ }
+
+ @Override // AbstractAnnotationValueVisitor14q
+ public final Integer visitAnnotation(final AnnotationMirror am0, Predicate super ExecutableElement> p) {
+ if (am0 == null) {
+ return 0;
+ }
+ if (p == null) {
+ p = ee -> true;
+ }
+ int hashCode = 0;
+ final Map extends ExecutableElement, ? extends AnnotationValue> explicitValues = am0.getElementValues();
+ for (final Element e : am0.getAnnotationType().asElement().getEnclosedElements()) {
+ if (e.getKind() == METHOD && e instanceof ExecutableElement ee && p.test(ee)) {
+ final AnnotationValue v = explicitValues.containsKey(ee) ? explicitValues.get(ee) : ee.getDefaultValue();
+ // An annotation member value is either explicit or default but cannot be null.
+ assert v != null : "v == null; ee: " + ee;
+ // "The hash code of an annotation is the sum of the hash codes of its members (including those with default values). The
+ // hash code of an annotation member is (127 times the hash code of the member-name as computed by String.hashCode())
+ // XOR the hash code of the member-value."
+ hashCode += ((127 * ee.getSimpleName().toString().hashCode()) ^ this.visit(v, p).intValue());
+ }
+ }
+ return hashCode;
+ }
+
+ @Override // AbstractAnnotationValueVisitor14
+ public final Integer visitArray(final List extends AnnotationValue> l0, final Predicate super ExecutableElement> ignored) {
+ // "The hash code of an array[-typed annotation] member-value is computed by calling the appropriate overloading of
+ // Arrays.hashCode on the value. (There is one overloading for each primitive type, and one for object reference
+ // types.)"
+ //
+ // More cumbersome than you might think. Somewhat conveniently, in general Arrays.hashCode(something) will perform
+ // the same calculation as an equivalent List. So perform the calculation on a "de-AnnotationValueized" List.
+ //
+ // (Implementation note: in the JDK as of this writing, ArraySupport will do fancy stuff with vectors, which
+ // presumably List's hashCode calculations do not. If this ends up being some kind of hot spot, we could turn the
+ // List into an array appropriately.)
+ return l0.stream().map(AnnotationValue::getValue).toList().hashCode();
+ }
+
+ @Override // AbstractAnnotationValueVisitor14
+ public final Integer visitBoolean(final boolean b0, final Predicate super ExecutableElement> ignored) {
+ // "The hash code of a primitive value v is equal to WrapperType.valueOf(v).hashCode(), where WrapperType is the
+ // wrapper type corresponding to the primitive type of v (Byte, Character, Double, Float, Integer, Long, Short, or
+ // Boolean)."
+ return Boolean.hashCode(b0);
+ }
+
+ @Override // AbstractAnnotationValueVisitor14
+ public final Integer visitByte(final byte b0, final Predicate super ExecutableElement> ignored) {
+ // "The hash code of a primitive value v is equal to WrapperType.valueOf(v).hashCode(), where WrapperType is the
+ // wrapper type corresponding to the primitive type of v (Byte, Character, Double, Float, Integer, Long, Short, or
+ // Boolean)."
+ return Byte.hashCode(b0);
+ }
+
+ @Override // AbstractAnnotationValueVisitor14
+ public final Integer visitChar(final char c0, final Predicate super ExecutableElement> ignored) {
+ // "The hash code of a primitive value v is equal to WrapperType.valueOf(v).hashCode(), where WrapperType is the
+ // wrapper type corresponding to the primitive type of v (Byte, Character, Double, Float, Integer, Long, Short, or
+ // Boolean)."
+ return Character.hashCode(c0);
+ }
+
+ @Override // AbstractAnnotationValueVisitor14
+ public final Integer visitDouble(final double d0, final Predicate super ExecutableElement> ignored) {
+ // "The hash code of a primitive value v is equal to WrapperType.valueOf(v).hashCode(), where WrapperType is the
+ // wrapper type corresponding to the primitive type of v (Byte, Character, Double, Float, Integer, Long, Short, or
+ // Boolean)."
+ return Double.hashCode(d0);
+ }
+
+ @Override // AbstractAnnotationValueVisitor14
+ public final Integer visitEnumConstant(final VariableElement ve0, final Predicate super ExecutableElement> ignored) {
+ // "The hash code of a string, enum, class, or annotation member-value v is computed as by calling v.hashCode(). (In
+ // the case of annotation member values, this is a recursive definition.)"
+ return ve0 == null ? 0 : ve0.hashCode();
+ }
+
+ @Override // AbstractAnnotationValueVisitor14
+ public final Integer visitFloat(final float f0, final Predicate super ExecutableElement> ignored) {
+ // "The hash code of a primitive value v is equal to WrapperType.valueOf(v).hashCode(), where WrapperType is the
+ // wrapper type corresponding to the primitive type of v (Byte, Character, Double, Float, Integer, Long, Short, or
+ // Boolean)."
+ return Float.hashCode(f0);
+ }
+
+ @Override // AbstractAnnotationValueVisitor14
+ public final Integer visitInt(final int i0, final Predicate super ExecutableElement> ignored) {
+ // "The hash code of a primitive value v is equal to WrapperType.valueOf(v).hashCode(), where WrapperType is the
+ // wrapper type corresponding to the primitive type of v (Byte, Character, Double, Float, Integer, Long, Short, or
+ // Boolean)."
+ return Integer.hashCode(i0);
+ }
+
+ @Override // AbstractAnnotationValueVisitor14
+ public final Integer visitLong(final long l0, final Predicate super ExecutableElement> ignored) {
+ // "The hash code of a primitive value v is equal to WrapperType.valueOf(v).hashCode(), where WrapperType is the
+ // wrapper type corresponding to the primitive type of v (Byte, Character, Double, Float, Integer, Long, Short, or
+ // Boolean)."
+ return Long.hashCode(l0);
+ }
+
+ @Override // AbstractAnnotationValueVisitor14
+ public final Integer visitShort(final short s0, final Predicate super ExecutableElement> ignored) {
+ // "The hash code of a primitive value v is equal to WrapperType.valueOf(v).hashCode(), where WrapperType is the
+ // wrapper type corresponding to the primitive type of v (Byte, Character, Double, Float, Integer, Long, Short, or
+ // Boolean)."
+ return Short.hashCode(s0);
+ }
+
+ @Override // AbstractAnnotationValueVisitor14
+ public final Integer visitString(final String s0, final Predicate super ExecutableElement> ignored) {
+ // "The hash code of a string, enum, class, or annotation member-value v is computed as by calling v.hashCode(). (In
+ // the case of annotation member values, this is a recursive definition.)"
+ return s0 == null ? 0 : s0.hashCode();
+ }
+
+ @Override // AbstractAnnotationValueVisitor14
+ public final Integer visitType(final TypeMirror t0, final Predicate super ExecutableElement> ignored) {
+ // "The hash code of a string, enum, class, or annotation member-value v is computed as by calling v.hashCode(). (In
+ // the case of annotation member values, this is a recursive definition.)"
+ return t0 == null ? 0 : t0.hashCode();
+ }
+
+}
diff --git a/src/main/java/org/microbean/construct/element/SameAnnotationValueVisitor.java b/src/main/java/org/microbean/construct/element/SameAnnotationValueVisitor.java
index 73c2266..83ca640 100644
--- a/src/main/java/org/microbean/construct/element/SameAnnotationValueVisitor.java
+++ b/src/main/java/org/microbean/construct/element/SameAnnotationValueVisitor.java
@@ -18,6 +18,8 @@
import java.util.Map.Entry;
import java.util.Objects;
+import java.util.function.Predicate;
+
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.ExecutableElement;
@@ -47,6 +49,10 @@
* AnnotationValue#getValue() represented} by two {@link AnnotationValue} implementations are to be considered the
* same.
*
+ * This class implements the rules described by the {@link java.lang.annotation.Annotation#equals(Object)} contract,
+ * for want of a more authoritative source. This contract appears to define in a mostly agnostic manner what it means
+ * for two annotations to be "the same".
+ *
* Unlike some other annotation-processing-related facilities, the relation represented by this {@link
* SameAnnotationValueVisitor} does not require that the values being logically compared originate from {@link
* AnnotationValue} instances from the same vendor or toolkit.
@@ -60,18 +66,32 @@
* javax.lang.model.element.Name#contentEquals(CharSequence) equal contents}.
*
* Any two {@link VariableElement}s representing enum constants encountered during traversal are considered equal if
- * their {@linkplain VariableElement#getSimpleName() simple names} have {@linkplain
- * javax.lang.model.element.Name#contentEquals(CharSequence) equal contents}.
+ * they belong to the same enum class and their {@linkplain VariableElement#getSimpleName() simple names} have
+ * {@linkplain javax.lang.model.element.Name#contentEquals(CharSequence) equal contents}.
*
* @author Laird Nelson
*
* @see AnnotationValue#accept(javax.lang.model.element.AnnotationValueVisitor, Object)
*
* @see AnnotationMirrors#allAnnotationValues(AnnotationMirror)
+ *
+ * @see AnnotationValueHashcodeVisitor
+ *
+ * @see java.lang.annotation.Annotation#equals(Object)
*/
public final class SameAnnotationValueVisitor extends AbstractAnnotationValueVisitor14 {
+
+ /*
+ * Instance fields.
+ */
+
+
+ private final Predicate super ExecutableElement> p;
+
+
+
/*
* Constructors.
*/
@@ -79,9 +99,23 @@ public final class SameAnnotationValueVisitor extends AbstractAnnotationValueVis
/**
* Creates a new {@link SameAnnotationValueVisitor}.
+ *
+ * @see #SameAnnotationValueVisitor(Predicate)
*/
public SameAnnotationValueVisitor() {
+ this(null);
+ }
+
+ /**
+ * Creates a new {@link SameAnnotationValueVisitor}.
+ *
+ * @param p a {@link Predicate} that returns {@code true} if a given {@link ExecutableElement}, representing an
+ * 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
+ */
+ public SameAnnotationValueVisitor(final Predicate super ExecutableElement> p) {
super();
+ this.p = p == null ? ee -> true : p;
}
@@ -96,6 +130,9 @@ public final Boolean visitAnnotation(final AnnotationMirror am0, final Object v1
case null -> false;
case AnnotationValue av1 -> this.visitAnnotation(am0, av1.getValue());
case AnnotationMirror am1 -> {
+ if (!((QualifiedNameable)am0.getAnnotationType().asElement()).getQualifiedName().contentEquals(((QualifiedNameable)am1.getAnnotationType().asElement()).getQualifiedName())) {
+ yield false;
+ }
final Iterator> i0 = allAnnotationValues(am0).entrySet().iterator();
final Iterator> i1 = allAnnotationValues(am1).entrySet().iterator();
while (i0.hasNext()) {
@@ -104,12 +141,13 @@ public final Boolean visitAnnotation(final AnnotationMirror am0, final Object v1
}
final Entry e0 = i0.next();
final Entry e1 = i1.next();
- if (!e0.getKey().getSimpleName().contentEquals(e1.getKey().getSimpleName()) ||
- !this.visit(e0.getValue(), e1.getValue().getValue())) {
+ final ExecutableElement ee0 = e0.getKey();
+ if (!ee0.getSimpleName().contentEquals(e1.getKey().getSimpleName()) ||
+ this.p.test(ee0) && !this.visit(e0.getValue(), e1.getValue().getValue())) {
yield false;
}
}
- yield i1.hasNext();
+ yield !i1.hasNext();
}
default -> false;
};
@@ -126,10 +164,10 @@ public final Boolean visitArray(final List extends AnnotationValue> l0, final
yield false;
}
for (int i = 0; i < size; i++) {
- if (l1.get(i) instanceof AnnotationValue av1 && this.visit(av1, l0.get(i).getValue())) {
- continue;
+ // Yes, order is important (!)
+ if (!(l1.get(i) instanceof AnnotationValue av1) || !this.visit(av1, l0.get(i).getValue())) {
+ yield false;
}
- yield false;
}
yield true;
}
@@ -139,17 +177,32 @@ public final Boolean visitArray(final List extends AnnotationValue> l0, final
@Override // AbstractAnnotationValueVisitor14
public final Boolean visitBoolean(final boolean b0, final Object v1) {
- return v1 instanceof AnnotationValue av1 ? this.visitBoolean(b0, av1.getValue()) : Boolean.valueOf(b0).equals(v1);
+ return switch (v1) {
+ case null -> false;
+ case AnnotationValue av1 -> this.visitBoolean(b0, av1.getValue());
+ case Boolean b1 -> b0 && b1.booleanValue();
+ default -> false;
+ };
}
@Override // AbstractAnnotationValueVisitor14
public final Boolean visitByte(final byte b0, final Object v1) {
- return v1 instanceof AnnotationValue av1 ? this.visitByte(b0, av1.getValue()) : Byte.valueOf(b0).equals(v1);
+ return switch (v1) {
+ case null -> false;
+ case AnnotationValue av1 -> this.visitByte(b0, av1.getValue());
+ case Byte b1 -> b0 == b1.byteValue();
+ default -> false;
+ };
}
@Override // AbstractAnnotationValueVisitor14
public final Boolean visitChar(final char c0, final Object v1) {
- return v1 instanceof AnnotationValue av1 ? this.visitChar(c0, av1.getValue()) : Character.valueOf(c0).equals(v1);
+ return switch (v1) {
+ case null -> false;
+ case AnnotationValue av1 -> this.visitChar(c0, av1.getValue());
+ case Character c1 -> c0 == c1.charValue();
+ default -> false;
+ };
}
@Override // AbstractAnnotationValueVisitor14
@@ -181,17 +234,32 @@ public final Boolean visitFloat(final float f0, final Object v1) {
@Override // AbstractAnnotationValueVisitor14
public final Boolean visitInt(final int i0, final Object v1) {
- return v1 instanceof AnnotationValue av1 ? this.visitInt(i0, av1.getValue()) : Integer.valueOf(i0).equals(v1);
+ return switch (v1) {
+ case null -> false;
+ case AnnotationValue av1 -> this.visitInt(i0, av1.getValue());
+ case Integer i1 -> i0 == i1.intValue();
+ default -> false;
+ };
}
@Override // AbstractAnnotationValueVisitor14
public final Boolean visitLong(final long l0, final Object v1) {
- return v1 instanceof AnnotationValue av1 ? this.visitLong(l0, av1.getValue()) : Long.valueOf(l0).equals(v1);
+ return switch (v1) {
+ case null -> false;
+ case AnnotationValue av1 -> this.visitLong(l0, av1.getValue());
+ case Long l1 -> l0 == l1.longValue();
+ default -> false;
+ };
}
@Override // AbstractAnnotationValueVisitor14
public final Boolean visitShort(final short s0, final Object v1) {
- return v1 instanceof AnnotationValue av1 ? this.visitShort(s0, av1.getValue()) : Short.valueOf(s0).equals(v1);
+ return switch (v1) {
+ case null -> false;
+ case AnnotationValue av1 -> this.visitShort(s0, av1.getValue());
+ case Short s1 -> s0 == s1.shortValue();
+ default -> false;
+ };
}
@Override // AbstractAnnotationValueVisitor14
@@ -203,57 +271,51 @@ public final Boolean visitString(final String s0, final Object v1) {
public final Boolean visitType(final TypeMirror t0, final Object v1) {
return t0 == v1 || v1 != null && switch (t0) {
case null -> false;
- case AnnotationValue av1 -> this.visitType(t0, av1.getValue());
- case ArrayType a0 when a0.getKind() == ARRAY -> this.visitArrayType(a0, v1); // e.g. Object[].class
- case DeclaredType dt0 when dt0.getKind() == DECLARED -> this.visitDeclaredType(dt0, v1); // e.g. Foo.class
- case PrimitiveType p0 when p0.getKind().isPrimitive() -> this.visitPrimitiveType(p0, v1); // e.g. int.class
- case NoType n0 when n0.getKind() == VOID -> this.visitNoType(n0, v1); // e.g. void.class
+ case ArrayType a0 when a0.getKind() == ARRAY -> this.privateVisitArrayType(a0, v1); // e.g. Object[].class
+ case DeclaredType dt0 when dt0.getKind() == DECLARED -> this.privateVisitDeclaredType(dt0, v1); // e.g. Foo.class
+ case PrimitiveType p0 when p0.getKind().isPrimitive() -> this.privateVisitPrimitiveType(p0, v1); // e.g. int.class
+ case NoType n0 when n0.getKind() == VOID -> this.privateVisitNoType(n0, v1); // e.g. void.class
default -> t0.equals(v1);
};
}
- @Override // AbstractAnnotationValueVisitor14
- public final Boolean visitUnknown(final AnnotationValue av0, final Object v1) {
- return v1 instanceof AnnotationValue av1 ? this.visitUnknown(av0, av1.getValue()) : Objects.equals(av0, v1);
- }
-
/*
* Private instance methods.
*/
- private final Boolean visitArrayType(final ArrayType a0, final Object v1) {
+ private final Boolean privateVisitArrayType(final ArrayType a0, final Object v1) {
assert a0.getKind() == ARRAY;
- assert !(v1 instanceof AnnotationValue);
return switch (v1) {
+ case AnnotationValue av1 -> this.privateVisitArrayType(a0, av1.getValue());
case ArrayType a1 when a1.getKind() == ARRAY -> this.visitType(a0.getComponentType(), a1.getComponentType());
default -> false;
};
}
- private final Boolean visitDeclaredType(final DeclaredType dt0, final Object v1) {
+ private final Boolean privateVisitDeclaredType(final DeclaredType dt0, final Object v1) {
assert dt0.getKind() == DECLARED;
- assert !(v1 instanceof AnnotationValue);
return switch (v1) {
+ case AnnotationValue av1 -> this.privateVisitDeclaredType(dt0, av1.getValue());
case DeclaredType dt1 when dt1.getKind() == DECLARED -> ((QualifiedNameable)dt0.asElement()).getQualifiedName().contentEquals((((QualifiedNameable)dt1.asElement()).getQualifiedName()));
default -> false;
};
}
- private final Boolean visitNoType(final NoType n0, final Object v1) {
+ private final Boolean privateVisitNoType(final NoType n0, final Object v1) {
assert n0.getKind() == VOID;
- assert !(v1 instanceof AnnotationValue);
return switch (v1) {
+ case AnnotationValue av1 -> this.privateVisitNoType(n0, av1.getValue());
case NoType n1 -> n1.getKind() == VOID;
default -> false;
};
}
- private final Boolean visitPrimitiveType(final PrimitiveType p0, final Object v1) {
+ private final Boolean privateVisitPrimitiveType(final PrimitiveType p0, final Object v1) {
assert p0.getKind().isPrimitive();
- assert !(v1 instanceof AnnotationValue);
return switch (v1) {
+ case AnnotationValue av1 -> this.privateVisitPrimitiveType(p0, av1.getValue());
case PrimitiveType p1 -> p1.getKind() == p0.getKind();
default -> false;
};
diff --git a/src/main/java/org/microbean/construct/element/SyntheticAnnotationMirror.java b/src/main/java/org/microbean/construct/element/SyntheticAnnotationMirror.java
index a9ae819..84b628d 100644
--- a/src/main/java/org/microbean/construct/element/SyntheticAnnotationMirror.java
+++ b/src/main/java/org/microbean/construct/element/SyntheticAnnotationMirror.java
@@ -52,7 +52,7 @@
* An experimental {@link AnnotationMirror} implementation that is partially or wholly synthetic.
*
* It is possible to create {@link SyntheticAnnotationMirror} instances representing annotations that a Java compiler
- * will not produce. For example, annotations cannot refer to each
* other, directly or indirectly, but two {@link SyntheticAnnotationMirror}s may do so.
*
diff --git a/src/main/java/org/microbean/construct/element/SyntheticAnnotationTypeElement.java b/src/main/java/org/microbean/construct/element/SyntheticAnnotationTypeElement.java
index f135ace..2a51a15 100644
--- a/src/main/java/org/microbean/construct/element/SyntheticAnnotationTypeElement.java
+++ b/src/main/java/org/microbean/construct/element/SyntheticAnnotationTypeElement.java
@@ -632,29 +632,32 @@ final List extends AnnotationMirror> 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); // TODO: hmm
- 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(); // TODO: hmm
- hashCode = 17 * hashCode + c;
- return hashCode;
- }
+ // It's not clear that equals and hashCode should be overridden. You can compare annotation members for "sameness"
+ // indirectly via AnnotationMirrors#sameAnnotation if needed.
+
+ // @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); // TODO: hmm
+ // 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(); // TODO: hmm
+ // hashCode = 17 * hashCode + c;
+ // return hashCode;
+ // }
final SyntheticName name() {
return this.name;
diff --git a/src/main/java/org/microbean/construct/element/UniversalElement.java b/src/main/java/org/microbean/construct/element/UniversalElement.java
index 8366e6b..1ec4ece 100644
--- a/src/main/java/org/microbean/construct/element/UniversalElement.java
+++ b/src/main/java/org/microbean/construct/element/UniversalElement.java
@@ -65,9 +65,35 @@ public final class UniversalElement
TypeParameterElement,
VariableElement {
+
+ /*
+ * Instance fields.
+ */
+
+
// volatile not needed
private Supplier extends List extends UniversalElement>> enclosedElementsSupplier;
+
+ /*
+ * Constructors.
+ */
+
+
+ /**
+ * Creates a new {@link UniversalElement} that is a copy of the supplied {@link UniversalElement}.
+ *
+ * @param ue a non-{@code null} {@link UniversalElement}
+ *
+ * @exception NullPointerException if {@code uc} is {@code null}
+ *
+ * @see #clone()
+ */
+ public UniversalElement(final UniversalElement ue) {
+ super(ue);
+ this.enclosedElementsSupplier = ue.enclosedElementsSupplier;
+ }
+
/**
* Creates a new {@link UniversalElement}.
*
@@ -99,9 +125,9 @@ public UniversalElement(final Element delegate, final PrimordialDomain domain) {
* @see #delegate()
*/
@SuppressWarnings("try")
- private UniversalElement(final List extends AnnotationMirror> annotations,
- final Element delegate,
- final PrimordialDomain domain) {
+ public UniversalElement(final List extends AnnotationMirror> annotations,
+ final Element delegate,
+ final PrimordialDomain domain) {
super(annotations, delegate, domain);
this.enclosedElementsSupplier = () -> {
final List extends UniversalElement> ees;
@@ -113,6 +139,12 @@ private UniversalElement(final List extends AnnotationMirror> annotations,
};
}
+
+ /*
+ * Instance methods.
+ */
+
+
@Override // Element
public final R accept(final ElementVisitor v, final P p) {
return switch (this.getKind()) {
@@ -148,6 +180,18 @@ public final UniversalType asType() {
return UniversalType.of(this.delegate().asType(), this.domain());
}
+ /**
+ * Returns a non-{@code null}, determinate, shallow copy of this {@link UniversalElement}.
+ *
+ * @return a non-{@code null}, determinate, shallow copy of this {@link UniversalElement}
+ */
+ @Override // UniversalConstruct (Cloneable)
+ public final UniversalElement clone() {
+ final UniversalElement clone = (UniversalElement)super.clone();
+ assert clone.enclosedElementsSupplier != null;
+ return clone;
+ }
+
/**
* Returns {@code true} if and only if this {@link UniversalElement} is a generic class declaration.
*
diff --git a/src/main/java/org/microbean/construct/element/package-info.java b/src/main/java/org/microbean/construct/element/package-info.java
index dc00b3d..191f83b 100644
--- a/src/main/java/org/microbean/construct/element/package-info.java
+++ b/src/main/java/org/microbean/construct/element/package-info.java
@@ -1,6 +1,6 @@
/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
*
- * Copyright © 2024 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
@@ -13,8 +13,10 @@
*/
/**
- * Provides classes and interfaces related to Java language elements.
+ * Provides classes and interfaces related to valid Java language elements.
*
* @author Laird Nelson
+ *
+ * @see org.microbean.construct.element.UniversalElement
*/
package org.microbean.construct.element;
diff --git a/src/main/java/org/microbean/construct/package-info.java b/src/main/java/org/microbean/construct/package-info.java
index d4261c0..6228a94 100644
--- a/src/main/java/org/microbean/construct/package-info.java
+++ b/src/main/java/org/microbean/construct/package-info.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
@@ -13,8 +13,12 @@
*/
/**
- * Provides classes and interfaces related to Java language constructs.
+ * Provides classes and interfaces related to valid Java language constructs.
*
* @author Laird Nelson
+ *
+ * @see org.microbean.construct.Domain
+ *
+ * @see org.microbean.construct.UniversalConstruct
*/
package org.microbean.construct;
diff --git a/src/main/java/org/microbean/construct/type/UniversalType.java b/src/main/java/org/microbean/construct/type/UniversalType.java
index c3e78fd..4ef0542 100644
--- a/src/main/java/org/microbean/construct/type/UniversalType.java
+++ b/src/main/java/org/microbean/construct/type/UniversalType.java
@@ -66,6 +66,25 @@ public final class UniversalType
UnionType,
WildcardType {
+
+ /*
+ * Constructors.
+ */
+
+
+ /**
+ * Creates a new {@link UniversalType} that is a copy of the supplied {@link UniversalType}.
+ *
+ * @param ut a non-{@code null} {@link UniversalType}
+ *
+ * @exception NullPointerException if {@code ut} is {@code null}
+ *
+ * @see #clone()
+ */
+ public UniversalType(final UniversalType ut) {
+ super(ut);
+ }
+
/**
* Creates a new {@link UniversalType}.
*
@@ -78,7 +97,6 @@ public final class UniversalType
*
* @see #delegate()
*/
- @SuppressWarnings("try")
public UniversalType(final TypeMirror delegate, final PrimordialDomain domain) {
this(null, delegate, domain);
}
@@ -104,6 +122,12 @@ public UniversalType(final List extends AnnotationMirror> annotations,
final PrimordialDomain domain) {
super(annotations, delegate, domain);
}
+
+
+ /*
+ * Instance methods.
+ */
+
@Override // TypeMirror
public final R accept(final TypeVisitor v, final P p) {
@@ -144,6 +168,16 @@ public final UniversalElement asElement() {
};
}
+ /**
+ * Returns a non-{@code null}, determinate, shallow copy of this {@link UniversalType}.
+ *
+ * @return a non-{@code null}, determinate, shallow copy of this {@link UniversalType}
+ */
+ @Override // UniversalConstruct (Cloneable)
+ public final UniversalType clone() {
+ return (UniversalType)super.clone();
+ }
+
/**
* Returns the element type of this {@link UniversalType} if it is an array type, or simply this {@link
* UniversalType} if it is not.
diff --git a/src/main/java/org/microbean/construct/type/package-info.java b/src/main/java/org/microbean/construct/type/package-info.java
index 77c44df..8609a5a 100644
--- a/src/main/java/org/microbean/construct/type/package-info.java
+++ b/src/main/java/org/microbean/construct/type/package-info.java
@@ -1,6 +1,6 @@
/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
*
- * Copyright © 2024 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
@@ -13,8 +13,10 @@
*/
/**
- * Provides classes and interfaces related to Java language types.
+ * Provides classes and interfaces related to valid Java language types.
*
* @author Laird Nelson
+ *
+ * @see org.microbean.construct.type.UniversalType
*/
package org.microbean.construct.type;