{@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}.
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 extends AnnotationRecord> getAnnotationMirrors() { - return AnnotationRecord.of(this.delegate().getAnnotationMirrors(), this.domain()); + public final List extends UniversalAnnotation> 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 extends UniversalElement, ? extends AnnotationValueRecord> getElementValues() { - final MapPrototypical 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}.
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 extends UniversalType> wrap(final Collection extends TypeMirror> 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)); + + } + +}