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 ConsumerLaird 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 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 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 ConsumerMost 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 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 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 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 c, + final AnnotationMirror a, + final Predicate 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 c0, + final Collection c1, + final Predicate 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 Mapexperimental 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 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 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 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 c0, + final Collection c1, + final Predicate 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 CollectionCycles 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 p) { + if (am0 == null) { + return 0; + } + if (p == null) { + p = ee -> true; + } + int hashCode = 0; + final Map 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 l0, final Predicate 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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> 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 annotations, - final Element delegate, - final PrimordialDomain domain) { + public UniversalElement(final List annotations, + final Element delegate, + final PrimordialDomain domain) { super(annotations, delegate, domain); this.enclosedElementsSupplier = () -> { final List ees; @@ -113,6 +139,12 @@ private UniversalElement(final List 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 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;