From 2d58f68f52ad347acca31ee309f6ecc940a0c2d6 Mon Sep 17 00:00:00 2001 From: Laird Nelson Date: Fri, 21 Nov 2025 17:23:36 -0800 Subject: [PATCH] Fixes #31; upgrades dependencies and workflows Signed-off-by: Laird Nelson --- .../mvn-release-prepare-perform.yaml | 8 +- .github/workflows/mvn-verify.yaml | 8 +- README.md | 2 +- pom.xml | 28 +- .../java/org/microbean/construct/Domain.java | 39 ++- .../org/microbean/construct/Processor.java | 1 + .../construct/UniversalConstruct.java | 79 ++++-- .../construct/constant/Constables.java | 2 +- .../construct/element/AnnotationRecord.java | 163 ----------- .../element/AnnotationValueRecord.java | 200 ------------- .../construct/element/StringName.java | 16 +- .../element/UniversalAnnotation.java | 230 +++++++++++++++ .../element/UniversalAnnotationValue.java | 264 ++++++++++++++++++ .../construct/element/UniversalDirective.java | 5 +- .../construct/element/UniversalElement.java | 24 +- .../construct/type/UniversalType.java | 58 +++- .../construct/TestDefaultDomain.java | 2 +- .../type/TestTypeSamenessAndEquality.java | 71 +++++ 18 files changed, 753 insertions(+), 447 deletions(-) delete mode 100644 src/main/java/org/microbean/construct/element/AnnotationRecord.java delete mode 100644 src/main/java/org/microbean/construct/element/AnnotationValueRecord.java create mode 100644 src/main/java/org/microbean/construct/element/UniversalAnnotation.java create mode 100644 src/main/java/org/microbean/construct/element/UniversalAnnotationValue.java create mode 100644 src/test/java/org/microbean/construct/type/TestTypeSamenessAndEquality.java diff --git a/.github/workflows/mvn-release-prepare-perform.yaml b/.github/workflows/mvn-release-prepare-perform.yaml index d12a45b..3a93fc1 100644 --- a/.github/workflows/mvn-release-prepare-perform.yaml +++ b/.github/workflows/mvn-release-prepare-perform.yaml @@ -24,20 +24,20 @@ jobs: steps: - id: 'checkout' name: 'Step: Check Out Project' - uses: 'actions/checkout@v4' + uses: 'actions/checkout@v6' with: fetch-depth: 1 persist-credentials: false - id: 'setup-java' name: 'Step: Set Up Java and Maven' - uses: 'actions/setup-java@v4' + uses: 'actions/setup-java@v5' with: cache: 'maven' distribution: 'temurin' gpg-passphrase: 'GPG_PASSPHRASE' gpg-private-key: '${{ secrets.GPG_PRIVATE_KEY }}' - java-version: '24' - mvn-toolchain-id: 'Temurin 24' + java-version: '25' + mvn-toolchain-id: 'Temurin 25' mvn-toolchain-vendor: 'openjdk' # see ../../pom.xml server-id: 'central.sonatype.com' server-password: 'CENTRAL_SONATYPE_COM_PASSWORD' diff --git a/.github/workflows/mvn-verify.yaml b/.github/workflows/mvn-verify.yaml index a9a759e..faefc80 100644 --- a/.github/workflows/mvn-verify.yaml +++ b/.github/workflows/mvn-verify.yaml @@ -12,18 +12,18 @@ jobs: steps: - id: 'checkout' name: 'Step: Checkout' - uses: 'actions/checkout@v4' + uses: 'actions/checkout@v6' with: fetch-depth: 1 persist-credentials: false - id: 'setup-java' name: 'Step: Set Up Java and Maven' - uses: 'actions/setup-java@v4' + uses: 'actions/setup-java@v5' with: cache: 'maven' distribution: 'temurin' - java-version: '24' - mvn-toolchain-id: 'Temurin 24' + java-version: '25' + mvn-toolchain-id: 'Temurin 25' mvn-toolchain-vendor: 'openjdk' # see ../../pom.xml - id: 'mvn-verify' name: 'Step: Maven Verify' diff --git a/README.md b/README.md index a7d2262..14a6fda 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ as a Maven dependency: Always check https://search.maven.org/artifact/org.microbean/microbean-construct for up-to-date available versions. --> - 0.0.16 + 0.0.17 ``` diff --git a/pom.xml b/pom.xml index caf4bc9..de7cf1c 100644 --- a/pom.xml +++ b/pom.xml @@ -108,7 +108,7 @@ org.junit junit-bom - 5.13.2 + 6.0.1 pom import @@ -139,7 +139,7 @@ maven-antrun-plugin - 3.1.0 + 3.2.0 maven-assembly-plugin @@ -166,7 +166,7 @@ maven-compiler-plugin - 3.14.0 + 3.14.1 -Xlint:all @@ -176,7 +176,7 @@ maven-dependency-plugin - 3.8.1 + 3.9.0 maven-deploy-plugin @@ -184,7 +184,7 @@ maven-enforcer-plugin - 3.6.1 + 3.6.2 maven-gpg-plugin @@ -196,11 +196,11 @@ maven-jar-plugin - 3.4.2 + 3.5.0 maven-javadoc-plugin - 3.11.3 + 3.12.0 true @@ -215,7 +215,7 @@ maven-release-plugin - 3.1.1 + 3.2.0 maven-resources-plugin @@ -223,7 +223,7 @@ maven-scm-plugin - 2.1.0 + 2.2.1 maven-scm-publish-plugin @@ -247,7 +247,7 @@ maven-surefire-plugin - 3.5.3 + 3.5.4 maven-toolchains-plugin @@ -256,22 +256,22 @@ com.github.spotbugs spotbugs-maven-plugin - 4.9.4.0 + 4.9.8.1 org.codehaus.mojo versions-maven-plugin - 2.18.0 + 2.19.1 io.smallrye jandex-maven-plugin - 3.4.0 + 3.5.2 org.sonatype.central central-publishing-maven-plugin - 0.8.0 + 0.9.0 true central.sonatype.com diff --git a/src/main/java/org/microbean/construct/Domain.java b/src/main/java/org/microbean/construct/Domain.java index 1b0352d..299e5d0 100644 --- a/src/main/java/org/microbean/construct/Domain.java +++ b/src/main/java/org/microbean/construct/Domain.java @@ -59,6 +59,8 @@ import static javax.lang.model.element.ElementKind.CONSTRUCTOR; import static javax.lang.model.element.ElementKind.METHOD; +import static javax.lang.model.type.TypeKind.DECLARED; + /** * A representation of a domain of valid Java constructs. * @@ -82,6 +84,8 @@ *

{@link Domain} implementations must be thread-safe.

* * @author Laird Nelson + * + * @see JDK-8055219 */ @SuppressWarnings("try") public interface Domain { @@ -646,7 +650,7 @@ public default boolean javaLangObject(final TypeMirror t) { default -> { try (var lock = this.lock()) { yield - t.getKind() == TypeKind.DECLARED && + t.getKind() == DECLARED && javaLangObject(((DeclaredType)t).asElement()); } } @@ -839,7 +843,7 @@ public default boolean parameterized(final TypeMirror t) { case UniversalType ut -> ut.parameterized(); default -> { try (var lock = this.lock()) { - yield t.getKind() == TypeKind.DECLARED && !((DeclaredType)t).getTypeArguments().isEmpty(); + yield t.getKind() == DECLARED && !((DeclaredType)t).getTypeArguments().isEmpty(); } } }; @@ -957,6 +961,35 @@ public default PrimitiveType primitiveType(final TypeElement e) { // (Unboxing.) public PrimitiveType primitiveType(final TypeMirror t); + /** + * A convenience method that returns {@code true} if and only if the supplied {@link TypeMirror} represents a + * prototypical type. + * + *

Prototypical types are not defined by the Java Language Specification. They are partially defined by the + * {@linkplain TypeElement#asType() specification of the TypeElement#asType() + * method}.

+ * + * @param t a {@link TypeMirror}; must not be {@code null} + * + * @return {@code true} if and only if this {@link UniversalType} represents a prototypical type + * + * @exception NullPointerException if {@code t} is {@code null} + * + * @see TypeElement#asType() + */ + // (Convenience.) + public default boolean prototypical(final TypeMirror t) { + return switch (t) { + case null -> throw new NullPointerException("t"); + case UniversalType ut -> ut.prototypical(); + default -> { + try (var lock = this.lock()) { + yield t.getKind() == DECLARED && t.equals(((DeclaredType)t).asElement().asType()); + } + } + }; + } + /** * A convenience method that returns {@code true} if and only if the supplied {@link TypeMirror} is a raw * type according to the rules @@ -1064,6 +1097,8 @@ yield switch (t.getKind()) { * * @see javax.lang.model.util.Types#isSameType(TypeMirror, TypeMirror) * + * @see JDK-8055219 + * * @spec https://docs.oracle.com/javase/specs/jls/se23/html/jls-4.html#jls-4.3.4 Java Language Specification, section * 4.3.4 * diff --git a/src/main/java/org/microbean/construct/Processor.java b/src/main/java/org/microbean/construct/Processor.java index 29a32d7..a9f7a9b 100644 --- a/src/main/java/org/microbean/construct/Processor.java +++ b/src/main/java/org/microbean/construct/Processor.java @@ -46,6 +46,7 @@ final class Processor implements AutoCloseable, javax.annotation.processing.Proc private final Consumer cpe; + // run() method invoked under lock private final Runnable r; private final Lock lock; diff --git a/src/main/java/org/microbean/construct/UniversalConstruct.java b/src/main/java/org/microbean/construct/UniversalConstruct.java index 2c67268..8bb95ff 100644 --- a/src/main/java/org/microbean/construct/UniversalConstruct.java +++ b/src/main/java/org/microbean/construct/UniversalConstruct.java @@ -1,6 +1,6 @@ /* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*- * - * Copyright © 2024 microBean™. + * Copyright © 2024–2025 microBean™. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -35,7 +35,7 @@ import org.microbean.construct.constant.Constables; -import org.microbean.construct.element.AnnotationRecord; +import org.microbean.construct.element.UniversalAnnotation; import org.microbean.construct.element.UniversalElement; import org.microbean.construct.type.UniversalType; @@ -67,9 +67,11 @@ public abstract sealed class UniversalConstruct im private final Domain domain; + // Eventually this should become a lazy constant/stable value // volatile not needed private Supplier delegateSupplier; + // Eventually this should become a lazy constant/stable value private volatile String s; @@ -93,19 +95,25 @@ protected UniversalConstruct(final T delegate, final Domain domain) { super(); this.domain = Objects.requireNonNull(domain, "domain"); final T unwrappedDelegate = unwrap(Objects.requireNonNull(delegate, "delegate")); - final Runnable symbolCompleter = switch (unwrappedDelegate) { - case null -> throw new IllegalArgumentException("delegate: " + delegate); - case Element e -> e::getModifiers; - case TypeMirror t -> t::getKind; - default -> UniversalConstruct::doNothing; - }; - this.delegateSupplier = () -> { - try (var lock = domain.lock()) { - symbolCompleter.run(); - this.delegateSupplier = () -> unwrappedDelegate; - } - return unwrappedDelegate; - }; + if (unwrappedDelegate == delegate) { + // No unwrapping happened so do symbol completion early; most common case. + final Runnable symbolCompleter = switch (unwrappedDelegate) { + case Element e -> e::getModifiers; + case TypeMirror t -> t::getKind; + default -> UniversalConstruct::doNothing; + }; + this.delegateSupplier = () -> { + try (var lock = domain.lock()) { + symbolCompleter.run(); + this.delegateSupplier = () -> unwrappedDelegate; + } + return unwrappedDelegate; + }; + } else { + assert delegate instanceof UniversalConstruct; + // Symbol completion already happened + this.delegateSupplier = () -> unwrappedDelegate; + } } @@ -117,6 +125,8 @@ protected UniversalConstruct(final T delegate, final Domain domain) { /** * Returns the delegate to which operations are delegated. * + *

The delegate is guaranteed not to be an instance of {@link UniversalConstruct}.

+ * * @return a non-{@code null} delegate * * @see Element @@ -124,7 +134,9 @@ protected UniversalConstruct(final T delegate, final Domain domain) { * @see TypeMirror */ public final T delegate() { - return this.delegateSupplier.get(); + final T delegate = this.delegateSupplier.get(); + assert !(delegate instanceof UniversalConstruct); + return delegate; } @Override // Constable @@ -149,14 +161,32 @@ public final Domain domain() { return this.domain; } + @Override // Object + public final boolean equals(final Object other) { + // Interesting; equality does not cause symbol completion. See: + // + // https://github.com/openjdk/jdk/blob/jdk-26%2B25/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Type.java#L553-L559 + // (the only type that overrides this is ArrayType; see + // https://github.com/openjdk/jdk/blob/jdk-26%2B25/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Type.java#L1402-L1406) + // + // https://github.com/openjdk/jdk/blob/jdk-26%2B25/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symbol.java + // (Symbol (Element) doesn't override it at all.) + return this == other || switch (other) { + case null -> false; + case UniversalConstruct uc when this.getClass() == uc.getClass() -> this.delegate().equals(uc.delegate()); + default -> false; + }; + } + @Override // AnnotatedConstruct - public final List getAnnotationMirrors() { - return AnnotationRecord.of(this.delegate().getAnnotationMirrors(), this.domain()); + public final List getAnnotationMirrors() { + return UniversalAnnotation.of(this.delegate().getAnnotationMirrors(), this.domain()); } @Override // AnnotatedConstruct @SuppressWarnings("try") public final A getAnnotation(final Class annotationType) { + // TODO: is this lock actually needed, given how delegateSupplier works? try (var lock = this.domain().lock()) { return this.delegate().getAnnotation(annotationType); } @@ -165,11 +195,24 @@ public final A getAnnotation(final Class annotationTyp @Override // AnnotatedConstruct @SuppressWarnings("try") public final A[] getAnnotationsByType(final Class annotationType) { + // TODO: is this lock actually needed, given how delegateSupplier works? try (var lock = this.domain().lock()) { return this.delegate().getAnnotationsByType(annotationType); } } + @Override // Object + public final int hashCode() { + // Interesting; hashCode doesn't cause symbol completion. See: + // + // https://github.com/openjdk/jdk/blob/jdk-26%2B25/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Type.java#L565-L568 + // (AnnoConstruct doesn't define it so super.hashCode() is Object.hashCode()) + // + // https://github.com/openjdk/jdk/blob/jdk-26%2B25/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symbol.java + // (Symbol (Element) doesn't override it at all.) + return this.delegate().hashCode(); + } + @Override // Object @SuppressWarnings("try") public final String toString() { diff --git a/src/main/java/org/microbean/construct/constant/Constables.java b/src/main/java/org/microbean/construct/constant/Constables.java index 205b7b6..a3de239 100644 --- a/src/main/java/org/microbean/construct/constant/Constables.java +++ b/src/main/java/org/microbean/construct/constant/Constables.java @@ -545,7 +545,7 @@ yield switch (t.getKind()) { yield Optional.empty(); } for (int i = 0; i < typeArgumentCount; i++) { - final int index = i + 3; + final int index = i + 4; args[index] = describe(typeArguments.get(i), d).orElse(null); if (args[index] == null) { yield Optional.empty(); diff --git a/src/main/java/org/microbean/construct/element/AnnotationRecord.java b/src/main/java/org/microbean/construct/element/AnnotationRecord.java deleted file mode 100644 index ded3bf3..0000000 --- a/src/main/java/org/microbean/construct/element/AnnotationRecord.java +++ /dev/null @@ -1,163 +0,0 @@ -/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*- - * - * Copyright © 2024 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.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Objects; -import java.util.Set; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.AnnotationValue; -import javax.lang.model.element.ExecutableElement; - -import org.microbean.construct.Domain; - -import org.microbean.construct.type.UniversalType; - -import static java.util.HashMap.newHashMap; - -/** - * An {@link AnnotationMirror} implementation. - * - * @param delegate an {@link AnnotationMirror} to which operations will be delegated; must not be {@code null} - * - * @param domain a {@link Domain}; must not be {@code null} - * - * @author Laird Nelson - * - * @see AnnotationMirror - */ -public final record AnnotationRecord(AnnotationMirror delegate, Domain domain) implements AnnotationMirror { - - - /* - * Constructors. - */ - - - /** - * Creates a new {@link AnnotationRecord}. - * - * @param delegate an {@link AnnotationMirror} to which operations will be delegated; must not be {@code null} - * - * @param domain a {@link Domain}; must not be {@code null} - * - * @exception NullPointerException if either argument is {@code null} - */ - public AnnotationRecord { - Objects.requireNonNull(delegate, "delegate"); - Objects.requireNonNull(domain, "domain"); - } - - - /* - * Instance methods. - */ - - - @Override // Object - @SuppressWarnings("try") - public final boolean equals(final Object other) { - return this == other || switch (other) { - case null -> false; - case AnnotationMirror a -> { - try (var lock = this.domain().lock()) { - yield this.delegate().equals(a instanceof AnnotationRecord ar ? ar.delegate() : a); - } - } - default -> false; - }; - } - - @Override // AnnotationMirror - @SuppressWarnings("try") - public final UniversalType getAnnotationType() { - final Domain d = this.domain(); - try (var lock = d.lock()) { - return UniversalType.of(this.delegate().getAnnotationType(), d); - } - } - - @Override // AnnotationMirror - @SuppressWarnings("try") - public final Map getElementValues() { - final Map map = newHashMap(17); - final Domain d = this.domain(); - try (var lock = d.lock()) { - for (final Entry e : this.delegate().getElementValues().entrySet()) { - map.put(UniversalElement.of(e.getKey(), d), AnnotationValueRecord.of(e.getValue(), d)); - } - } - return Collections.unmodifiableMap(map); - } - - @Override // AnnotationMirror - public final int hashCode() { - int hashCode = 37 * 17 + this.getAnnotationType().hashCode(); - return 37 * hashCode + this.getElementValues().hashCode(); - } - - - /* - * Static methods. - */ - - - /** - * Returns a non-{@code null} {@link AnnotationRecord} that is either the supplied {@link AnnotationMirror} (if it - * itself is an {@link AnnotationRecord}) or one that wraps it. - * - * @param a an {@link AnnotationMirror}; must not be {@code null} - * - * @param d a {@link Domain}; must not be {@code null} - * - * @return a non-{@code null} {@link AnnotationRecord} - * - * @exception NullPointerException if either argument is {@code null} - * - * @see #AnnotationRecord(AnnotationMirror, Domain) - */ - public static final AnnotationRecord of(final AnnotationMirror a, final Domain d) { - return a instanceof AnnotationRecord ar ? ar : new AnnotationRecord(a, d); - } - - /** - * Returns a non-{@code null}, immutable {@link List} of {@link AnnotationRecord}s whose elements wrap the supplied - * {@link List}'s elements. - * - * @param as a {@link Collection} of {@link AnnotationMirror}s; must not be {@code null} - * - * @param domain a {@link Domain}; must not be {@code null} - * - * @return a non-{@code null}, immutable {@link List} of {@link AnnotationRecord}s - * - * @exception NullPointerException if either argument is {@code null} - */ - public static final List of(final Collection as, - final Domain domain) { - if (as.isEmpty()) { - return List.of(); - } - final List newAs = new ArrayList<>(as.size()); - for (final AnnotationMirror a : as) { - newAs.add(AnnotationRecord.of(a, domain)); - } - return Collections.unmodifiableList(newAs); - } -} diff --git a/src/main/java/org/microbean/construct/element/AnnotationValueRecord.java b/src/main/java/org/microbean/construct/element/AnnotationValueRecord.java deleted file mode 100644 index 79f34dd..0000000 --- a/src/main/java/org/microbean/construct/element/AnnotationValueRecord.java +++ /dev/null @@ -1,200 +0,0 @@ -/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*- - * - * Copyright © 2024 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.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Objects; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.AnnotationValue; -import javax.lang.model.element.AnnotationValueVisitor; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.VariableElement; - -import javax.lang.model.type.TypeMirror; - -import org.microbean.construct.Domain; - -import org.microbean.construct.type.UniversalType; - -/** - * An {@link AnnotationValue} implementation. - * - * @param delegate an {@link AnnotationValue} to which operations will be delegated; must not be {@code null} - * - * @param domain a {@link Domain}; must not be {@code null} - * - * @author Laird Nelson - * - * @see AnnotationValue - */ -public final record AnnotationValueRecord(AnnotationValue delegate, Domain domain) implements AnnotationValue { - - - /* - * Constructors. - */ - - - /** - * Creates a new {@link AnnotationValueRecord}. - * - * @param delegate an {@link AnnotationValue} to which operations will be delegated; must not be {@code null} - * - * @param domain a {@link Domain}; must not be {@code null} - * - * @exception NullPointerException if either argument is {@code null} - */ - public AnnotationValueRecord { - Objects.requireNonNull(delegate, "delegate"); - Objects.requireNonNull(domain, "domain"); - } - - - /* - * Instance methods. - */ - - - @Override // AnnotationValue - @SuppressWarnings({ "try", "unchecked" }) - public final R accept(final AnnotationValueVisitor v, final P p) { - try (var lock = this.domain().lock()) { - return switch (this.getValue()) { - case null -> v.visitUnknown(this, p); // ...or AssertionError? - case AnnotationMirror a -> v.visitAnnotation(a, p); - case List l -> v.visitArray((List)l, p); - case TypeMirror t -> v.visitType(t, p); - case VariableElement e -> v.visitEnumConstant(e, p); - case Boolean b -> v.visitBoolean(b, p); - case Byte b -> v.visitByte(b, p); - case Character c -> v.visitChar(c, p); - case Double d -> v.visitDouble(d, p); - case Float f -> v.visitFloat(f, p); - case Integer i -> v.visitInt(i, p); - case Long l -> v.visitLong(l, p); - case Short s -> v.visitShort(s, p); - case String s -> v.visitString(s, p); - default -> v.visitUnknown(this, p); - }; - } - } - - @Override // Object - @SuppressWarnings("try") - public final boolean equals(final Object other) { - return this == other || switch (other) { - case null -> false; - case AnnotationValue av -> { - try (var lock = this.domain().lock()) { - // The mere act of getting a value (even of type String) can trigger symbol completion: - // https://github.com/openjdk/jdk/blob/jdk-25%2B3/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Constants.java#L49 - yield this.delegate().equals(av instanceof AnnotationValueRecord avr ? avr.delegate() : av); - } - } - default -> false; - }; - } - - @Override // AnnotationValue - @SuppressWarnings({ "try", "unchecked" }) - public final Object getValue() { - final Domain domain = this.domain(); - final Object value; - try (var lock = domain.lock()) { - // The mere act of getting a value (even of type String) can trigger symbol completion: - // https://github.com/openjdk/jdk/blob/jdk-25%2B3/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Constants.java#L49 - value = this.delegate().getValue(); - } - return switch (value) { - case null -> throw new AssertionError(); - case AnnotationMirror a -> AnnotationRecord.of(a, domain); - case List l -> of((List)l, domain); - case TypeMirror t -> UniversalType.of(t, domain); - case VariableElement e -> UniversalElement.of(e, domain); - default -> value; - }; - } - - @Override // Object - public final int hashCode() { - return this.getValue().hashCode(); - } - - @Override // AnnotationValue - @SuppressWarnings("try") - public final String toString() { - try (var lock = this.domain().lock()) { - // Can cause symbol completion and/or Name#toString() calls - return this.delegate().toString(); - } - } - - - /* - * Static methods. - */ - - - /** - * Returns a non-{@code null} {@link AnnotationValueRecord} that is either the supplied {@link AnnotationValue} (if it - * itself is {@code null} or is an {@link AnnotationValueRecord}) or one that wraps it. - * - * @param av an {@link AnnotationValue}; may be {@code null} - * - * @param domain a {@link Domain}; must not be {@code null} - * - * @return an {@link AnnotationValueRecord}, or {@code null} (if {@code av} is {@code null}) - * - * @exception NullPointerException if {@code domain} is {@code null} - * - * @see #AnnotationValueRecord(AnnotationValue, Domain) - */ - public static final AnnotationValueRecord of(final AnnotationValue av, final Domain domain) { - return switch (av) { - case null -> null; - case AnnotationValueRecord avr -> avr; - default -> new AnnotationValueRecord(av, domain); - }; - } - - /** - * Returns a non-{@code null}, immutable {@link List} of {@link AnnotationValueRecord}s whose elements wrap the - * supplied {@link List}'s elements. - * - * @param avs a {@link Collection} of {@link AnnotationValue}s; must not be {@code null} - * - * @param domain a {@link Domain}; must not be {@code null} - * - * @return a non-{@code null}, immutable {@link List} of {@link AnnotationValueRecord}s - * - * @exception NullPointerException if either argument is {@code null} - */ - public static final List of(final Collection avs, - final Domain domain) { - if (avs.isEmpty()) { - return List.of(); - } - final List newAvs = new ArrayList<>(avs.size()); - for (final AnnotationValue av : avs) { - newAvs.add(AnnotationValueRecord.of(av, domain)); - } - return Collections.unmodifiableList(newAvs); - } - - -} diff --git a/src/main/java/org/microbean/construct/element/StringName.java b/src/main/java/org/microbean/construct/element/StringName.java index 7ef7feb..12da20e 100644 --- a/src/main/java/org/microbean/construct/element/StringName.java +++ b/src/main/java/org/microbean/construct/element/StringName.java @@ -1,6 +1,6 @@ /* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*- * - * Copyright © 2024 microBean™. + * Copyright © 2024–2025 microBean™. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -115,6 +115,20 @@ public final Optional describeConstable() { domainDesc)); } + @Override // Record + public final boolean equals(final Object other) { + return this == other || switch (other) { + case null -> false; + case StringName sn when this.getClass() == sn.getClass() -> Objects.equals(this.value(), sn.value()); + default -> false; + }; + } + + @Override // Record + public final int hashCode() { + return this.value().hashCode(); + } + @Override // Name (CharSequence) public final boolean isEmpty() { return this.value().isEmpty(); diff --git a/src/main/java/org/microbean/construct/element/UniversalAnnotation.java b/src/main/java/org/microbean/construct/element/UniversalAnnotation.java new file mode 100644 index 0000000..a874152 --- /dev/null +++ b/src/main/java/org/microbean/construct/element/UniversalAnnotation.java @@ -0,0 +1,230 @@ +/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*- + * + * Copyright © 2025 microBean™. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package org.microbean.construct.element; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.Set; + +import java.util.function.Supplier; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.ExecutableElement; + +import org.microbean.construct.Domain; + +import org.microbean.construct.type.UniversalType; + +import static java.util.Objects.requireNonNull; + +/** + * An {@link AnnotationMirror} implementation. + * + * @author Laird Nelson + * + * @see AnnotationMirror + */ +public final class UniversalAnnotation implements AnnotationMirror { + + + /* + * Instance fields. + */ + + + // Eventually this should become a lazy constant/stable value + // volatile not needed + private Supplier delegateSupplier; + + private final Domain domain; + + + /* + * Constructors. + */ + + + /** + * Creates a new {@link UniversalAnnotation}. + * + * @param delegate an {@link AnnotationMirror} to which operations will be delegated; must not be {@code null} + * + * @param domain a {@link Domain}; must not be {@code null} + * + * @exception NullPointerException if either argument is {@code null} + */ + @SuppressWarnings("try") + public UniversalAnnotation(final AnnotationMirror delegate, final Domain domain) { + super(); + this.domain = requireNonNull(domain, "domain"); + final AnnotationMirror unwrappedDelegate = unwrap(requireNonNull(delegate, "delegate")); + if (unwrappedDelegate == delegate) { + // No unwrapping happened so do symbol completion early; most common case + this.delegateSupplier = () -> { + try (var lock = domain.lock()) { + unwrappedDelegate.getElementValues(); // should take care of any symbol completion + this.delegateSupplier = () -> unwrappedDelegate; + } + return unwrappedDelegate; + }; + } else { + assert delegate instanceof UniversalAnnotation; + // Symbol completion already happened + this.delegateSupplier = () -> unwrappedDelegate; + } + } + + + /* + * Instance methods. + */ + + + /** + * Returns the delegate to which operations are delegated. + * + * @return a non-{@code null} delegate + * + * @see AnnotationMirror + */ + public final AnnotationMirror delegate() { + final AnnotationMirror delegate = this.delegateSupplier.get(); + assert !(delegate instanceof UniversalAnnotation); + return delegate; + } + + /** + * Returns the {@link Domain} supplied at construction time. + * + * @return the non-{@code null} {@link Domain} supplied at construction time + */ + public final Domain domain() { + return this.domain; + } + + @Override // Object + @SuppressWarnings("try") + public final boolean equals(final Object other) { + return this == other || switch (other) { + case null -> false; + // No lock needed; see + // https://github.com/openjdk/jdk/blob/jdk-26%2B25/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Attribute.java#L45 + case UniversalAnnotation ar when this.getClass() == ar.getClass() -> this.delegate().equals(ar.delegate()); + default -> false; + }; + } + + @Override // AnnotationMirror + @SuppressWarnings("try") + public final UniversalType getAnnotationType() { + // No lock needed; see + // https://github.com/openjdk/jdk/blob/jdk-26%2B25/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Attribute.java#L285-L288 + return UniversalType.of(this.delegate().getAnnotationType(), this.domain()); + } + + @Override // AnnotationMirror + @SuppressWarnings("try") + public final Map getElementValues() { + final Map map = new LinkedHashMap<>(17); + final Domain d = this.domain(); + // TODO: is this lock actually needed, given how delegateSupplier works? + // try (var lock = d.lock()) { + for (final Entry e : this.delegate().getElementValues().entrySet()) { + map.put(UniversalElement.of(e.getKey(), d), UniversalAnnotationValue.of(e.getValue(), d)); + } + // } + return Collections.unmodifiableMap(map); + } + + @Override // AnnotationMirror + public final int hashCode() { + // No lock needed; see + // https://github.com/openjdk/jdk/blob/jdk-26%2B25/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Attribute.java#L45 + return this.delegate().hashCode(); + } + + + /* + * Static methods. + */ + + + /** + * Returns a non-{@code null} {@link UniversalAnnotation} that is either the supplied {@link AnnotationMirror} (if it + * itself is an {@link UniversalAnnotation}) or one that wraps it. + * + * @param a an {@link AnnotationMirror}; must not be {@code null} + * + * @param d a {@link Domain}; must not be {@code null} + * + * @return a non-{@code null} {@link UniversalAnnotation} + * + * @exception NullPointerException if either argument is {@code null} + * + * @see #UniversalAnnotation(AnnotationMirror, Domain) + */ + public static final UniversalAnnotation of(final AnnotationMirror a, final Domain d) { + return a instanceof UniversalAnnotation ar ? ar : new UniversalAnnotation(a, d); + } + + /** + * Returns a non-{@code null}, immutable {@link List} of {@link UniversalAnnotation}s whose elements wrap the supplied + * {@link List}'s elements. + * + * @param as a {@link Collection} of {@link AnnotationMirror}s; must not be {@code null} + * + * @param domain a {@link Domain}; must not be {@code null} + * + * @return a non-{@code null}, immutable {@link List} of {@link UniversalAnnotation}s + * + * @exception NullPointerException if either argument is {@code null} + */ + public static final List of(final Collection as, + final Domain domain) { + if (as.isEmpty()) { + return List.of(); + } + final List newAs = new ArrayList<>(as.size()); + for (final AnnotationMirror a : as) { + newAs.add(UniversalAnnotation.of(a, domain)); + } + return Collections.unmodifiableList(newAs); + } + + /** + * Unwraps the supplied {@link AnnotationMirror} implementation such that the returned value is not an + * instance of {@link UniversalAnnotation}. + * + * @param a an {@link AnnotationMirror}; may be {@code null} + * + * @return an {@link AnnotationMirror} that is guaranteed not to be an instance of {@link UniversalAnnotation} + * + * @see #delegate() + */ + public static final AnnotationMirror unwrap(AnnotationMirror a) { + while (a instanceof UniversalAnnotation ua) { + a = ua.delegate(); + } + return a; + } + + +} diff --git a/src/main/java/org/microbean/construct/element/UniversalAnnotationValue.java b/src/main/java/org/microbean/construct/element/UniversalAnnotationValue.java new file mode 100644 index 0000000..47a1353 --- /dev/null +++ b/src/main/java/org/microbean/construct/element/UniversalAnnotationValue.java @@ -0,0 +1,264 @@ +/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*- + * + * Copyright © 2025 microBean™. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package org.microbean.construct.element; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import java.util.function.Supplier; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.AnnotationValueVisitor; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.VariableElement; + +import javax.lang.model.type.TypeMirror; + +import org.microbean.construct.Domain; + +import org.microbean.construct.type.UniversalType; + +import static java.util.Objects.requireNonNull; + +/** + * An {@link AnnotationValue} implementation. + * + * @author Laird Nelson + * + * @see AnnotationValue + */ +public final class UniversalAnnotationValue implements AnnotationValue { + + + // Eventually this should become a lazy constant/stable value + // volatile not needed + private Supplier delegateSupplier; + + private final Domain domain; + + private String s; + + private Object v; + + + /* + * Constructors. + */ + + + /** + * Creates a new {@link UniversalAnnotationValue}. + * + * @param delegate an {@link AnnotationValue} to which operations will be delegated; must not be {@code null} + * + * @param domain a {@link Domain}; must not be {@code null} + * + * @exception NullPointerException if either argument is {@code null} + */ + @SuppressWarnings("try") + public UniversalAnnotationValue(final AnnotationValue delegate, final Domain domain) { + super(); + this.domain = requireNonNull(domain, "domain"); + final AnnotationValue unwrappedDelegate = unwrap(requireNonNull(delegate, "delegate")); + if (unwrappedDelegate == delegate) { + // No unwrapping happened so do symbol completion early; most common case + this.delegateSupplier = () -> { + try (var lock = domain.lock()) { + // Should trigger symbol completion; see + // https://github.com/openjdk/jdk/blob/jdk-25%2B3/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Constants.java#L49; + // any invocation of getTag() will do it. + // + // We bother to eagerly cache the value and the string representation because honestly you're going to call + // those methods anyway, probably repeatedly. + this.v = unwrappedDelegate.getValue(); + this.s = unwrappedDelegate.toString(); // names will do it too + this.delegateSupplier = () -> unwrappedDelegate; + } + return unwrappedDelegate; + }; + } else { + assert delegate instanceof UniversalAnnotationValue; + // Symbol completion already happened; no lock needed + final UniversalAnnotationValue uav = (UniversalAnnotationValue)delegate; + this.v = uav.getValue(); // already cached/computed + this.s = uav.toString(); // already cached/computed + this.delegateSupplier = () -> unwrappedDelegate; + } + } + + + /* + * Instance methods. + */ + + + @Override // AnnotationValue + @SuppressWarnings("unchecked") + public final R accept(final AnnotationValueVisitor v, final P p) { + return switch (this.getValue()) { + case null -> v.visitUnknown(this, p); // ...or AssertionError? + case AnnotationMirror a -> v.visitAnnotation(a, p); + case List l -> v.visitArray((List)l, p); + case TypeMirror t -> v.visitType(t, p); + case VariableElement e -> v.visitEnumConstant(e, p); + case Boolean b -> v.visitBoolean(b, p); + case Byte b -> v.visitByte(b, p); + case Character c -> v.visitChar(c, p); + case Double d -> v.visitDouble(d, p); + case Float f -> v.visitFloat(f, p); + case Integer i -> v.visitInt(i, p); + case Long l -> v.visitLong(l, p); + case Short s -> v.visitShort(s, p); + case String s -> v.visitString(s, p); + default -> v.visitUnknown(this, p); + }; + } + + /** + * Returns the delegate to which operations are delegated. + * + * @return a non-{@code null} delegate + * + * @see AnnotationValue + */ + public final AnnotationValue delegate() { + final AnnotationValue delegate = this.delegateSupplier.get(); + assert !(delegate instanceof UniversalAnnotationValue); + return delegate; + } + + /** + * Returns the {@link Domain} supplied at construction time. + * + * @return the non-{@code null} {@link Domain} supplied at construction time + */ + public final Domain domain() { + return this.domain; + } + + @Override // Object + @SuppressWarnings("try") + public final boolean equals(final Object other) { + return this == other || switch (other) { + case null -> false; + // No lock needed; see + // https://github.com/openjdk/jdk/blob/jdk-26%2B25/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Attribute.java#L45 + case UniversalAnnotationValue uav when this.getClass() == uav.getClass() -> this.delegate().equals(uav.delegate()); + default -> false; + }; + } + + @Override // AnnotationValue + @SuppressWarnings("unchecked") + public final Object getValue() { + final Domain domain = this.domain(); + final Object value = this.v; + return switch (value) { + case null -> throw new AssertionError(); + case AnnotationMirror a -> UniversalAnnotation.of(a, domain); + case List l -> of((List)l, domain); + case TypeMirror t -> UniversalType.of(t, domain); + case VariableElement e -> UniversalElement.of(e, domain); + default -> value; + }; + } + + @Override // Object + @SuppressWarnings({ "try", "unchecked" }) + public final int hashCode() { + // No lock needed; see + // https://github.com/openjdk/jdk/blob/jdk-26%2B25/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Attribute.java#L45 + return this.delegate().hashCode(); + } + + @Override // AnnotationValue + @SuppressWarnings("try") + public final String toString() { + return this.s; + } + + + /* + * Static methods. + */ + + + /** + * Returns a non-{@code null} {@link UniversalAnnotationValue} that is either the supplied {@link AnnotationValue} (if it + * itself is {@code null} or is an {@link UniversalAnnotationValue}) or one that wraps it. + * + * @param av an {@link AnnotationValue}; may be {@code null} + * + * @param domain a {@link Domain}; must not be {@code null} + * + * @return an {@link UniversalAnnotationValue}, or {@code null} (if {@code av} is {@code null}) + * + * @exception NullPointerException if {@code domain} is {@code null} + * + * @see #UniversalAnnotationValue(AnnotationValue, Domain) + */ + public static final UniversalAnnotationValue of(final AnnotationValue av, final Domain domain) { + return switch (av) { + case null -> null; + case UniversalAnnotationValue uav -> uav; + default -> new UniversalAnnotationValue(av, domain); + }; + } + + /** + * Returns a non-{@code null}, immutable {@link List} of {@link UniversalAnnotationValue}s whose elements wrap the + * supplied {@link List}'s elements. + * + * @param avs a {@link Collection} of {@link AnnotationValue}s; must not be {@code null} + * + * @param domain a {@link Domain}; must not be {@code null} + * + * @return a non-{@code null}, immutable {@link List} of {@link UniversalAnnotationValue}s + * + * @exception NullPointerException if either argument is {@code null} + */ + public static final List of(final Collection avs, + final Domain domain) { + if (avs.isEmpty()) { + return List.of(); + } + final List newAvs = new ArrayList<>(avs.size()); + for (final AnnotationValue av : avs) { + newAvs.add(UniversalAnnotationValue.of(av, domain)); + } + return Collections.unmodifiableList(newAvs); + } + + /** + * Unwraps the supplied {@link AnnotationValue} implementation such that the returned value is not an + * instance of {@link UniversalAnnotationValue}. + * + * @param a an {@link AnnotationValue}; may be {@code null} + * + * @return an {@link AnnotationValue} that is guaranteed not to be an instance of {@link UniversalAnnotationValue} + * + * @see #delegate() + */ + public static final AnnotationValue unwrap(AnnotationValue a) { + while (a instanceof UniversalAnnotationValue uav) { + a = uav.delegate(); + } + return a; + } + + +} diff --git a/src/main/java/org/microbean/construct/element/UniversalDirective.java b/src/main/java/org/microbean/construct/element/UniversalDirective.java index 87c6989..7474e82 100644 --- a/src/main/java/org/microbean/construct/element/UniversalDirective.java +++ b/src/main/java/org/microbean/construct/element/UniversalDirective.java @@ -1,6 +1,6 @@ /* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*- * - * Copyright © 2024 microBean™. + * Copyright © 2024–2025 microBean™. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -98,8 +98,7 @@ public final Directive delegate() { public final boolean equals(final Object other) { return other == this || switch (other) { case null -> false; - case UniversalDirective her -> this.delegate().equals(her.delegate()); - case Directive her -> this.delegate().equals(her); + case UniversalDirective her when this.getClass() == her.getClass() -> this.delegate().equals(her.delegate()); default -> false; }; } diff --git a/src/main/java/org/microbean/construct/element/UniversalElement.java b/src/main/java/org/microbean/construct/element/UniversalElement.java index 8367509..8840957 100644 --- a/src/main/java/org/microbean/construct/element/UniversalElement.java +++ b/src/main/java/org/microbean/construct/element/UniversalElement.java @@ -127,17 +127,6 @@ public final UniversalType asType() { return UniversalType.of(this.delegate().asType(), this.domain()); } - @Override // Element - @SuppressWarnings("try") - public final boolean equals(final Object other) { - return this == other || switch (other) { - case null -> false; - case UniversalElement her -> this.delegate().equals(her.delegate()); - case Element her -> this.delegate().equals(her); - default -> false; - }; - } - /** * Returns {@code true} if and only if this {@link UniversalElement} is a generic class declaration. * @@ -186,12 +175,12 @@ public final Object getConstantValue() { } @Override // ExecutableElement - public final AnnotationValueRecord getDefaultValue() { + public final UniversalAnnotationValue getDefaultValue() { return switch (this.getKind()) { // See // https://github.com/openjdk/jdk/blob/jdk-25%2B3/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symbol.java#L2287-L2290; // no concurrent Name access so no lock needed - case METHOD -> AnnotationValueRecord.of(((ExecutableElement)this.delegate()).getDefaultValue(), this.domain()); + case METHOD -> UniversalAnnotationValue.of(((ExecutableElement)this.delegate()).getDefaultValue(), this.domain()); default -> null; }; } @@ -339,11 +328,6 @@ public final List getTypeParameters() { }; } - @Override // Element - public final int hashCode() { - return this.delegate().hashCode(); - } - @Override // ExecutableElement public final boolean isDefault() { return switch (this.getKind()) { @@ -378,8 +362,8 @@ public final boolean isVarArgs() { } /** - * A convenience method that returns {@code true} if this {@link UniversalElement} is the class declaration for {@code - * java.lang.Object}. + * A convenience method that returns {@code true} if this {@link UniversalElement} represents the class declaration + * for {@code java.lang.Object}. * * @return {@code true} if this {@link UniversalElement} is the class declaration for {@code java.lang.Object} */ diff --git a/src/main/java/org/microbean/construct/type/UniversalType.java b/src/main/java/org/microbean/construct/type/UniversalType.java index 549bcbc..7f587fe 100644 --- a/src/main/java/org/microbean/construct/type/UniversalType.java +++ b/src/main/java/org/microbean/construct/type/UniversalType.java @@ -39,6 +39,7 @@ import org.microbean.construct.element.UniversalElement; +import static javax.lang.model.type.TypeKind.DECLARED; import static javax.lang.model.type.TypeKind.NONE; import static javax.lang.model.type.TypeKind.VOID; @@ -64,6 +65,7 @@ public final class UniversalType UnionType, WildcardType { + // Eventually this should become a lazy constant/stable value private volatile UniversalType erasure; /** @@ -293,13 +295,8 @@ public final List getTypeVariables() { }; } - @Override // TypeMirror - public final int hashCode() { - return this.delegate().hashCode(); - } - /** - * A convenience method that returns {@code true} if and only if this is the type declared by the {@code + * A convenience method that returns {@code true} if and only if this represents the type declared by the {@code * java.lang.Object} class. * * @return {@code true} if and only if this is the type declared by the {@code java.lang.Object} class @@ -307,7 +304,7 @@ public final int hashCode() { * @see UniversalElement#javaLangObject() */ public final boolean javaLangObject() { - return this.getKind() == TypeKind.DECLARED && this.asElement().javaLangObject(); + return this.getKind() == DECLARED && this.asElement().javaLangObject(); } /** @@ -319,7 +316,22 @@ public final boolean javaLangObject() { * 4.5 */ public final boolean parameterized() { - return this.getKind() == TypeKind.DECLARED && !this.getTypeArguments().isEmpty(); + return this.getKind() == DECLARED && !this.getTypeArguments().isEmpty(); + } + + /** + * Returns {@code true} if and only if this {@link UniversalType} represents a prototypical type. + * + *

Prototypical types are not defined by the Java Language Specification. They are partially defined by the + * {@linkplain javax.lang.model.element.TypeElement#asType() specification of the TypeElement#asType() + * method}.

+ * + * @return {@code true} if and only if this {@link UniversalType} represents a prototypical type + * + * @see javax.lang.model.element.TypeElement#asType() + */ + public final boolean prototypical() { + return this.getKind() == DECLARED && this.equals(this.asElement().asType()); } /** @@ -362,13 +374,29 @@ public final UniversalType rawType() { }; } - @Override // TypeMirror - public final boolean equals(final Object other) { - return this == other || switch (other) { - case null -> false; - case TypeMirror her -> this.domain().sameType(this, her); - default -> false; - }; + /** + * Returns {@code true} if and only if this {@link UniversalType} is the same type as the supplied {@link + * TypeMirror}. + * + *

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

+ * + * @param t a {@link TypeMirror}; may be {@code null} in which case {@code false} will be returned + * + * @return {@code true} if and only if this {@link UniversalType} is the same type as the supplied {@link + * TypeMirror} + * + * @see Domain#sameType(TypeMirror, TypeMirror) + * + * @see javax.lang.model.util.Types#isSameType(TypeMirror, TypeMirror) + * + * @see #equals(Object) + * + * @see TypeMirror#equals(Object) + */ + public final boolean sameType(final TypeMirror t) { + return this.domain().sameType(this, t); } private final List wrap(final Collection ts) { diff --git a/src/test/java/org/microbean/construct/TestDefaultDomain.java b/src/test/java/org/microbean/construct/TestDefaultDomain.java index 035b1fc..9670ce2 100644 --- a/src/test/java/org/microbean/construct/TestDefaultDomain.java +++ b/src/test/java/org/microbean/construct/TestDefaultDomain.java @@ -178,7 +178,7 @@ final void testSameTypes() { final UniversalType t0 = domain.declaredType("java.lang.String"); final UniversalType t1 = domain.declaredType("java.lang.String"); assertNotSame(t0, t1); - assertEquals(t0, t1); + assertEquals(t0, t1); // see https://github.com/microbean/microbean-construct/issues/31 assertTrue(domain.sameType(t0, t1)); } diff --git a/src/test/java/org/microbean/construct/type/TestTypeSamenessAndEquality.java b/src/test/java/org/microbean/construct/type/TestTypeSamenessAndEquality.java new file mode 100644 index 0000000..4ff040d --- /dev/null +++ b/src/test/java/org/microbean/construct/type/TestTypeSamenessAndEquality.java @@ -0,0 +1,71 @@ +/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*- + * + * Copyright © 2025 microBean™. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package org.microbean.construct.type; + +import javax.lang.model.element.TypeElement; + +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeMirror; + +import org.junit.jupiter.api.Test; + +import org.microbean.construct.DefaultDomain; +import org.microbean.construct.Domain; + +import org.microbean.construct.element.UniversalElement; + +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +final class TestTypeSamenessAndEquality { + + private static final DefaultDomain domain = new DefaultDomain(); + + private TestTypeSamenessAndEquality() { + super(); + } + + @Test + final void testObjectPrototypicalTypeIsEqualToTypeUsage() { + + final TypeElement object0 = (TypeElement)domain.typeElement("java.lang.Object").delegate(); + assertSame(object0, domain.typeElement("java.lang.Object").delegate()); + + final TypeMirror objectPrototypicalType0 = object0.asType(); + assertSame(objectPrototypicalType0, object0.asType()); + + // No matter how you get the prototypical type, it's the same (new instances aren't created; UniversalType's cache + // doesn't affect things) + assertSame(objectPrototypicalType0, domain.typeElement("java.lang.Object").asType().delegate()); + assertSame(objectPrototypicalType0, domain.typeElement("java.lang.Object").asType().delegate()); + + assertSame(object0, ((DeclaredType)objectPrototypicalType0).asElement()); + + final TypeMirror objectDeclaredType0 = domain.declaredType(object0).delegate(); + assertSame(objectDeclaredType0, domain.declaredType(object0).delegate()); + assertSame(object0, ((DeclaredType)objectDeclaredType0).asElement()); + + // No matter what, the declared type (usage) and the prototypical type are not equal. + assertNotSame(objectPrototypicalType0, objectDeclaredType0); + assertNotEquals(objectPrototypicalType0, objectDeclaredType0); + + // They are, however, "the same". + assertTrue(domain.sameType(objectPrototypicalType0, objectDeclaredType0)); + assertTrue(domain.sameType(objectDeclaredType0, objectPrototypicalType0)); + + } + +}