From 30c1f57c181db64273bb83e93a083bdcfd51f45b Mon Sep 17 00:00:00 2001 From: Laird Nelson Date: Fri, 20 Feb 2026 21:42:02 -0800 Subject: [PATCH 1/2] Road grading; uptakes microbean-construct 0.0.24 Signed-off-by: Laird Nelson --- README.md | 4 +- pom.xml | 26 +- src/main/java/module-info.java | 3 +- .../microbean/assign/AbstractTypeMatcher.java | 34 +- .../java/org/microbean/assign/Aggregate.java | 57 +-- .../java/org/microbean/assign/Annotated.java | 99 +++++ .../java/org/microbean/assign/Assignment.java | 18 +- .../microbean/assign/AttributedElement.java | 104 ------ .../org/microbean/assign/AttributedType.java | 144 -------- .../org/microbean/assign/AttributedTyped.java | 40 -- .../assign/CacheableAnnotatedConstruct.java | 164 +++++++++ .../java/org/microbean/assign/Matcher.java | 18 +- .../java/org/microbean/assign/Normalizer.java | 132 +++++++ .../java/org/microbean/assign/Qualifiers.java | 341 ++++++++++++++---- .../java/org/microbean/assign/Selectable.java | 17 +- .../org/microbean/assign/Selectables.java | 117 +++++- .../assign/SpecializationComparator.java | 10 +- src/main/java/org/microbean/assign/Types.java | 63 ++-- .../org/microbean/assign/TestComposition.java | 50 +++ .../org/microbean/assign/TestQualifiers.java | 51 +++ .../TestSupertypesWithTypeUseAnnotations.java | 132 +++++++ 21 files changed, 1123 insertions(+), 501 deletions(-) create mode 100644 src/main/java/org/microbean/assign/Annotated.java delete mode 100644 src/main/java/org/microbean/assign/AttributedElement.java delete mode 100644 src/main/java/org/microbean/assign/AttributedType.java delete mode 100644 src/main/java/org/microbean/assign/AttributedTyped.java create mode 100644 src/main/java/org/microbean/assign/CacheableAnnotatedConstruct.java create mode 100644 src/main/java/org/microbean/assign/Normalizer.java create mode 100644 src/test/java/org/microbean/assign/TestComposition.java create mode 100644 src/test/java/org/microbean/assign/TestQualifiers.java create mode 100644 src/test/java/org/microbean/assign/TestSupertypesWithTypeUseAnnotations.java diff --git a/README.md b/README.md index 331d600..f1bd04f 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ [![Maven Central](https://img.shields.io/maven-central/v/org.microbean/microbean-assign.svg?label=Maven%20Central)](https://search.maven.org/artifact/org.microbean/microbean-assign) +![0% AI](https://img.shields.io/badge/%F0%9F%A4%96_AI-0%25_%F0%9F%8C%BC-brightgreen) + The microBean™ Assign project provides classes and interfaces assisting with implementing Java type assignment. # Status @@ -27,7 +29,7 @@ dependency: org.microbean microbean-assign - 0.0.11 + 0.0.12 ``` diff --git a/pom.xml b/pom.xml index 6e21d3c..c02b432 100644 --- a/pom.xml +++ b/pom.xml @@ -71,7 +71,7 @@ ${project.organization.name}. All rights reserved.]]> <a href="${project.url}" target="_top"><span style="font-family:Lobster, cursive;">µb</span> ${project.artifactId}</a> ${project.version} - https://microbean.github.io/microbean-attributes/apidocs/,https://microbean.github.io/microbean-construct/apidocs/ + ,https://microbean.github.io/microbean-construct/apidocs/ 2 @@ -118,12 +118,6 @@ - - org.microbean - microbean-attributes - 0.0.5 - - org.microbean microbean-constant @@ -133,7 +127,7 @@ org.microbean microbean-construct - 0.0.18 + 0.0.24 @@ -141,12 +135,6 @@ - - org.microbean - microbean-attributes - compile - - org.microbean microbean-constant @@ -300,7 +288,7 @@ com.puppycrawl.tools checkstyle - 12.3.0 + 13.2.0 @@ -321,7 +309,7 @@ maven-compiler-plugin - 3.14.1 + 3.15.0 -Xlint:all @@ -331,7 +319,7 @@ maven-dependency-plugin - 3.9.0 + 3.10.0 maven-deploy-plugin @@ -433,7 +421,7 @@ org.codehaus.mojo versions-maven-plugin - 2.20.1 + 2.21.0 io.smallrye @@ -443,7 +431,7 @@ org.sonatype.central central-publishing-maven-plugin - 0.9.0 + 0.10.0 true central.sonatype.com diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index f3c90cb..82bad09 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 © 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 @@ -22,7 +22,6 @@ exports org.microbean.assign; requires transitive java.compiler; - requires transitive org.microbean.attributes; requires org.microbean.constant; requires transitive org.microbean.construct; diff --git a/src/main/java/org/microbean/assign/AbstractTypeMatcher.java b/src/main/java/org/microbean/assign/AbstractTypeMatcher.java index e9e8125..d231254 100644 --- a/src/main/java/org/microbean/assign/AbstractTypeMatcher.java +++ b/src/main/java/org/microbean/assign/AbstractTypeMatcher.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 @@ -19,7 +19,6 @@ import java.lang.constant.DynamicConstantDesc; import java.lang.constant.MethodHandleDesc; -import java.util.Objects; import java.util.Optional; import java.util.function.Predicate; @@ -35,6 +34,10 @@ import static java.lang.constant.ConstantDescs.BSM_INVOKE; +import static java.lang.constant.MethodHandleDesc.ofConstructor; + +import static java.util.Objects.requireNonNull; + /** * A partial implementation of a {@link Matcher} that tests if one {@link TypeMirror} matches another. * @@ -49,14 +52,6 @@ public abstract class AbstractTypeMatcher implements Constable, Matcher { - /* - * Static fields. - */ - - - private static final ClassDesc CD_Domain = ClassDesc.of("org.microbean.construct.Domain"); - - /* * Instance fields. */ @@ -77,7 +72,7 @@ public abstract class AbstractTypeMatcher implements Constable, Matcher describeConstable() { + public Optional> describeConstable() { return (this.domain() instanceof Constable c ? c.describeConstable() : Optional.empty()) .map(domainDesc -> DynamicConstantDesc.of(BSM_INVOKE, - MethodHandleDesc.ofConstructor(ClassDesc.of(this.getClass().getName()), - CD_Domain), + ofConstructor(this.getClass().describeConstable().orElseThrow(), + Domain.class.describeConstable().orElseThrow()), domainDesc)); } @@ -371,7 +371,7 @@ protected boolean identical(final TypeMirror receiver, final TypeMirror payload) // CDI has an undefined notion of "identical to". This method attempts to divine and implement the intent. Recall // that javax.lang.model.* compares types with "sameType" semantics. return - Objects.requireNonNull(receiver, "receiver") == Objects.requireNonNull(payload, "payload") || + requireNonNull(receiver, "receiver") == requireNonNull(payload, "payload") || this.domain().sameType(receiver, payload); } diff --git a/src/main/java/org/microbean/assign/Aggregate.java b/src/main/java/org/microbean/assign/Aggregate.java index a97b3f4..167485a 100644 --- a/src/main/java/org/microbean/assign/Aggregate.java +++ b/src/main/java/org/microbean/assign/Aggregate.java @@ -1,6 +1,6 @@ /* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*- * - * Copyright © 2025 microBean™. + * 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 @@ -16,6 +16,10 @@ import java.util.Collection; import java.util.SequencedSet; +import javax.lang.model.element.Element; + +import javax.lang.model.AnnotatedConstruct; + import java.util.function.Function; import static java.util.Collections.unmodifiableSequencedSet; @@ -23,7 +27,7 @@ import static java.util.LinkedHashSet.newLinkedHashSet; /** - * An object with {@linkplain AttributedElement dependencies}. + * An object with {@linkplain #dependencies() dependencies}. * *

By default, {@link Aggregate}s have {@linkplain #EMPTY_DEPENDENCIES no dependencies}.

* @@ -45,9 +49,9 @@ public interface Aggregate { public static final SequencedSet> EMPTY_ASSIGNMENTS = unmodifiableSequencedSet(newLinkedHashSet(0)); /** - * An immutable, empty {@link SequencedSet} of {@link AttributedElement}s. + * An immutable, empty {@link SequencedSet} of {@link Element}s. */ - public static final SequencedSet EMPTY_DEPENDENCIES = unmodifiableSequencedSet(newLinkedHashSet(0)); + public static final SequencedSet> EMPTY_DEPENDENCIES = unmodifiableSequencedSet(newLinkedHashSet(0)); /* @@ -56,53 +60,58 @@ public interface Aggregate { /** - * Returns an immutable, determinate, {@link SequencedSet} of {@link AttributedElement} instances. + * Returns an immutable, determinate, {@link SequencedSet} of {@link Annotated Annotated<? extends Element>} + * instances. * - *

If an {@link AttributedElement} in the set returns a {@link javax.lang.model.element.TypeElement} from its - * {@link AttributedElement#element()} method that represents this {@link Aggregate} implementation, undefined - * behavior, including the possibility of infinite loops, may result (an {@link Aggregate} may not have itself as a - * dependency).

+ *

If an {@link Annotated Annotated<? extends Element>} in the set represents this very {@link Aggregate} + * implementation, undefined behavior, including the possibility of infinite loops, may result (an {@link Aggregate} + * may not have itself as a dependency).

* - *

Note that it is permissible for an {@link AttributedElement} in the set to refer to another type by returning a - * {@link javax.lang.model.element.TypeElement} from its {@link AttributedElement#element()} method.

+ *

Note that it is permissible for an {@link Annotated Annotated<? extends Element>} in the set to represent + * another type.

* *

The default implementation of this method returns the value of the {@link #EMPTY_DEPENDENCIES} field.

* - * @return an immutable, determinate, {@link SequencedSet} of {@link AttributedElement} instances; never {@code null} + * @return an immutable, determinate, {@link SequencedSet} of {@link Annotated Annotated<? extends Element>} + * instances; never {@code null} + * + * @see Annotated * - * @see AttributedElement + * @see Annotated#of(AnnotatedConstruct) */ - public default SequencedSet dependencies() { + public default SequencedSet> dependencies() { return EMPTY_DEPENDENCIES; } /** - * A convenience method that assigns a contextual reference to each of this {@link Aggregate}'s {@link - * AttributedElement} instances and returns the resulting {@link SequencedSet} of {@link Assignment}s. + * A convenience method that assigns a contextual reference to each of this {@link Aggregate}'s {@link Annotated + * Annotated<? extends Element>}-typed {@linkplain #dependencies() dependencies} and returns the resulting + * {@link SequencedSet} of {@link Assignment}s. * - *

Note: Undefined behavior may result if an {@link AttributedElement} in the {@linkplain - * #dependencies() dependencies} represents this {@link Aggregate} implementation (an {@link Aggregate} may not have - * itself as a dependency).

+ *

Note: Undefined behavior may result if an {@link Annotated Annotated<? extends Element>} + * in the {@linkplain #dependencies() dependencies} represents this {@link Aggregate} implementation (an {@link + * Aggregate} may not have itself as a dependency).

* *

Typically there is no need to override this method.

* *

Usage of this method is not required.

* - * @param r a {@link Function} that retrieves a contextual reference suitable for an {@link AttributedType}; if {@link - * #dependencies()} returns a non-empty {@link SequencedSet} then this argument must not be {@code null} + * @param r a {@link Function} that retrieves a contextual reference suitable for an {@link Annotated Annotated<? + * extends AnnotatedConstruct>}; if {@link #dependencies()} returns a non-empty {@link SequencedSet} then this + * argument must not be {@code null} * * @return an immutable {@link SequencedSet} of {@link Assignment} instances; never {@code null} * * @exception NullPointerException if {@code r} is {@code null} */ // (Convenience.) - public default SequencedSet> assign(final Function r) { - final Collection ds = this.dependencies(); + public default SequencedSet> assign(final Function, ?> r) { + final Collection> ds = this.dependencies(); if (ds == null || ds.isEmpty()) { return EMPTY_ASSIGNMENTS; } final SequencedSet> assignments = newLinkedHashSet(ds.size()); - ds.forEach(d -> assignments.add(new Assignment<>(d, r.apply(d.attributedType())))); + ds.forEach(d -> assignments.add(new Assignment<>(d, r.apply(d)))); return unmodifiableSequencedSet(assignments); } diff --git a/src/main/java/org/microbean/assign/Annotated.java b/src/main/java/org/microbean/assign/Annotated.java new file mode 100644 index 0000000..5a05204 --- /dev/null +++ b/src/main/java/org/microbean/assign/Annotated.java @@ -0,0 +1,99 @@ +/* -*- 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.assign; + +import java.util.List; + +import java.util.function.Predicate; + +import javax.lang.model.AnnotatedConstruct; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; + +/** + * An interface whose implementations bear semantically significant annotations. + * + *

Semantically significant annotations are included in hashcode and equality computations.

+ * + * @param the type of the annotated object + * + * @author Laird Nelson + * + * @see org.microbean.construct.element.AnnotationMirrors#sameAnnotation(AnnotationMirror, AnnotationMirror, + * java.util.function.Predicate) + * + * @see org.microbean.construct.element.AnnotationMirrors#containsAll(java.util.Collection, java.util.Collection, + * java.util.function.Predicate) + * + * @see org.microbean.construct.element.AnnotationMirrors#hashCode(AnnotationMirror, java.util.function.Predicate) + */ +public interface Annotated { + + /** + * Returns a non-{@code null}, immutable, determinate {@link List} of semantically significant {@link + * AnnotationMirror}s. + * + *

These annotations supersede any annotations that might otherwise be available from objects returned by the + * {@link #annotated()} method.

+ * + * @return a non-{@code null}, immutable, determinate {@link List} of semantically significant {@link + * AnnotationMirror}s. + */ + public List annotations(); + + /** + * Returns the non-{@code null}, determinate, annotated object. + * + * @return the non-{@code null}, determinate, annotated object. + */ + public T annotated(); + + /** + * A convenience method that returns a new {@link Annotated} implementation. + * + * @param the type of {@link AnnotatedConstruct} + * + * @param ac a non-{@code null} {@link AnnotatedConstruct} + * + * @return a new, non-{@code null} {@link Annotated} implementation + * + * @exception NullPointerException if {@code ac} is {@code null} + * + * @see #of(AnnotatedConstruct, Predicate) + */ + public static Annotated of(final T ac) { + return of(ac, null); + } + + /** + * A convenience method that returns a new {@link Annotated} implementation. + * + * @param the type of {@link AnnotatedConstruct} + * + * @param ac a non-{@code null} {@link AnnotatedConstruct} + * + * @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 a new, non-{@code null} {@link Annotated} implementation + * + * @exception NullPointerException if {@code ac} is {@code null} + */ + public static Annotated of(final T ac, final Predicate p) { + return new CacheableAnnotatedConstruct(ac, p); + } + +} diff --git a/src/main/java/org/microbean/assign/Assignment.java b/src/main/java/org/microbean/assign/Assignment.java index d0a42ce..a95c839 100644 --- a/src/main/java/org/microbean/assign/Assignment.java +++ b/src/main/java/org/microbean/assign/Assignment.java @@ -1,6 +1,6 @@ /* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*- * - * Copyright © 2025 microBean™. + * 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 @@ -13,14 +13,16 @@ */ package org.microbean.assign; -import java.util.Objects; +import javax.lang.model.element.Element; + +import static java.util.Objects.requireNonNull; /** - * An assignment of a value to an {@link AttributedElement}. + * An assignment of a value to an {@link Annotated Annotated<? extends Element>}. * - * @param the value type + * @param the value type; must be logically assignable to the {@linkplain Element#asType() type of the assignee} * - * @param assignee the {@link AttributedElement}; must not be {@code null} + * @param assignee the {@link Annotated Annotated<? extends Element>}; must not be {@code null} * * @param value the value; may be {@code null} * @@ -29,17 +31,17 @@ // You're going to be tempted to replace the value component with a Supplier component. Don't do it. An assignment is a // value that belongs to, e.g., a field, so even if the value "came from" none/dependent/prototype scope, it was already // sourced and "belongs to" the field. -public final record Assignment(AttributedElement assignee, R value) { +public final record Assignment(Annotated assignee, R value) { /** * Creates a new {@link Assignment}. * - * @param assignee the {@link AttributedElement}; must not be {@code null} + * @param assignee the {@link Annotated Annotated<? extends Element>}; must not be {@code null} * * @param value the contextual reference; may be {@code null} */ public Assignment { - Objects.requireNonNull(assignee, "assignee"); + requireNonNull(assignee, "assignee"); } } diff --git a/src/main/java/org/microbean/assign/AttributedElement.java b/src/main/java/org/microbean/assign/AttributedElement.java deleted file mode 100644 index 7e78bcb..0000000 --- a/src/main/java/org/microbean/assign/AttributedElement.java +++ /dev/null @@ -1,104 +0,0 @@ -/* -*- 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.assign; - -import java.lang.constant.ClassDesc; -import java.lang.constant.Constable; -import java.lang.constant.ConstantDesc; -import java.lang.constant.DynamicConstantDesc; -import java.lang.constant.MethodHandleDesc; - -import java.util.List; -import java.util.Optional; - -import javax.lang.model.element.Element; - -import javax.lang.model.type.TypeMirror; - -import org.microbean.attributes.Attributed; -import org.microbean.attributes.Attributes; - -import org.microbean.constant.Constables; - -import static java.lang.constant.ConstantDescs.BSM_INVOKE; -import static java.lang.constant.ConstantDescs.CD_List; - -import static java.util.Objects.requireNonNull; - -/** - * A pairing of an {@link Element} with a {@link List} of {@link Attributes}s. - * - * @param element an {@link Element} - * - * @param attributes a {@link List} of {@link Attributes}s - * - * @author Laird Nelson - */ -public final record AttributedElement(Element element, List attributes) implements Attributed, AttributedTyped, Constable { - - /** - * Creates a new {@link AttributedElement}. - * - * @param element a {@link Element}; must not be {@code null} - * - * @param attributes a {@link List} of {@link Attributes}; must not be {@code null} - * - * @exception NullPointerException if either argument is {@code null} - */ - public AttributedElement { - requireNonNull(element, "element"); - attributes = List.copyOf(attributes); - } - - /** - * Returns this {@link AttributedElement}'s {@linkplain Element#asType() type}. - * - * @return this {@link AttributedElement}'s {@linkplain Element#asType() type}; never {@code null} - */ - public final TypeMirror type() { - return this.element().asType(); - } - - /** - * Returns this {@link AttributedElement}'s {@link AttributedType}. - * - * @return this {@link AttributedElement}'s {@link AttributedType}; never {@code null} - * - * @see AttributedType - */ - @Override // AttributedTyped - public final AttributedType attributedType() { - return new AttributedType(this.type(), this.attributes()); - } - - /** - * Returns an {@link Optional} containing a {@link ConstantDesc} describing this {@link AttributedType}, or an - * {@linkplain Optional#isEmpty() empty Optional} if it could not be described. - * - * @return an {@link Optional} containing a {@link ConstantDesc} describing this {@link AttributedType}, or an - * {@linkplain Optional#isEmpty() empty Optional} if it could not be describe; never {@code null} - */ - @Override // Constable - public Optional describeConstable() { - return this.element() instanceof Constable e ? e.describeConstable() : Optional.empty() - .flatMap(elementDesc -> Constables.describeConstable(this.attributes()) - .map(attributesDesc -> DynamicConstantDesc.of(BSM_INVOKE, - MethodHandleDesc.ofConstructor(ClassDesc.of(this.getClass().getName()), - ClassDesc.of("javax.lang.model.element.Element"), - CD_List), - elementDesc, - attributesDesc))); - } - -} diff --git a/src/main/java/org/microbean/assign/AttributedType.java b/src/main/java/org/microbean/assign/AttributedType.java deleted file mode 100644 index bf4847d..0000000 --- a/src/main/java/org/microbean/assign/AttributedType.java +++ /dev/null @@ -1,144 +0,0 @@ -/* -*- 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.assign; - -import java.lang.constant.ClassDesc; -import java.lang.constant.Constable; -import java.lang.constant.ConstantDesc; -import java.lang.constant.DynamicConstantDesc; -import java.lang.constant.MethodHandleDesc; - -import java.util.List; -import java.util.Optional; - -import javax.lang.model.type.TypeMirror; - -import org.microbean.attributes.Attributed; -import org.microbean.attributes.Attributes; - -import org.microbean.constant.Constables; - -import static java.lang.constant.ConstantDescs.BSM_INVOKE; -import static java.lang.constant.ConstantDescs.CD_List; - -import static java.util.Arrays.asList; - -/** - * A pairing of a {@link TypeMirror} with a {@link List} of {@link Attributes}s. - * - * @param type a {@link TypeMirror} - * - * @param attributes a {@link List} of {@link Attributes}s - * - * @author Laird Nelson - */ -public final record AttributedType(TypeMirror type, List attributes) implements Attributed, Constable { - - /** - * Creates a new {@link AttributedType}. - * - * @param type a {@link TypeMirror}; must not be {@code null}; must be a {@linkplain - * javax.lang.model.type.TypeKind#isPrimitive() primitive} type, an {@linkplain javax.lang.model.type.TypeKind#ARRAY - * array} type, a {@linkplain javax.lang.model.type.TypeKind#DECLARED declared} type, or a {@linkplain - * javax.lang.model.type.TypeKind#TYPEVAR type variable} - * - * @param attributes an array of {@link Attributes}; may be {@code null} - * - * @exception NullPointerException if {@code type} is {@code null} - * - * @exception IllegalArgumentException if {@code type} is the wrong kind of type - */ - public AttributedType(final TypeMirror type, final Attributes... attributes) { - this(type, attributes == null || attributes.length <= 0 ? List.of() : asList(attributes)); - } - - /** - * Creates a new {@link AttributedType}. - * - * @param type a {@link TypeMirror}; must not be {@code null}; must be a {@linkplain - * javax.lang.model.type.TypeKind#isPrimitive() primitive} type, an {@linkplain javax.lang.model.type.TypeKind#ARRAY - * array} type, a {@linkplain javax.lang.model.type.TypeKind#DECLARED declared} type, or a {@linkplain - * javax.lang.model.type.TypeKind#TYPEVAR type variable} - * - * @param attributes a {@link List} of {@link Attributes}; must not be {@code null} - * - * @exception NullPointerException if either argument is {@code null} - * - * @exception IllegalArgumentException if {@code type} is the wrong kind of type - */ - public AttributedType { - switch (type.getKind()) { - case ARRAY, BOOLEAN, BYTE, CHAR, DECLARED, DOUBLE, FLOAT, INT, LONG, SHORT, TYPEVAR: - break; - default: - throw new IllegalArgumentException("type: " + type); - } - attributes = List.copyOf(attributes); - } - - /** - * Creates a new {@link AttributedType}. - * - * @param type a {@link TypeMirror}; must not be {@code null}; must be a {@linkplain - * javax.lang.model.type.TypeKind#isPrimitive() primitive}, {@linkplain javax.lang.model.type.TypeKind#ARRAY array} or - * {@linkplain javax.lang.model.type.TypeKind#DECLARED declared} type - * - * @exception NullPointerException if {@code type} is {@code null} - * - * @exception IllegalArgumentException if {@code type} is the wrong kind of type - */ - public AttributedType(final TypeMirror type) { - this(type, List.of()); - } - - /** - * Returns an {@link Optional} containing a {@link ConstantDesc} describing this {@link AttributedType}, or an - * {@linkplain Optional#isEmpty() empty Optional} if it could not be described. - * - * @return an {@link Optional} containing a {@link ConstantDesc} describing this {@link AttributedType}, or an - * {@linkplain Optional#isEmpty() empty Optional} if it could not be describe; never {@code null} - */ - @Override // Constable - public Optional describeConstable() { - return this.type() instanceof Constable t ? t.describeConstable() : Optional.empty() - .flatMap(typeDesc -> Constables.describeConstable(this.attributes()) - .map(attributesDesc -> DynamicConstantDesc.of(BSM_INVOKE, - MethodHandleDesc.ofConstructor(ClassDesc.of(this.getClass().getName()), - ClassDesc.of(TypeMirror.class.getName()), - CD_List), - typeDesc, - attributesDesc))); - } - - /** - * Returns an {@link AttributedType} comprising the supplied arguments. - * - * @param type a {@link TypeMirror}; must not be {@code null}; must be a {@linkplain - * javax.lang.model.type.TypeKind#isPrimitive() primitive} type, an {@linkplain javax.lang.model.type.TypeKind#ARRAY - * array} type, a {@linkplain javax.lang.model.type.TypeKind#DECLARED declared} type, or a {@linkplain - * javax.lang.model.type.TypeKind#TYPEVAR type variable} - * - * @param attributes an array of {@link Attributes}; may be {@code null} - * - * @return a non-{@code null} {@link AttributedType} - * - * @exception NullPointerException if {@code type} is {@code null} - * - * @exception IllegalArgumentException if {@code type} is the wrong kind of type - */ - public static final AttributedType of(final TypeMirror type, Attributes... attributes) { - return new AttributedType(type, attributes == null || attributes.length <= 0 ? List.of() : asList(attributes)); - } - -} diff --git a/src/main/java/org/microbean/assign/AttributedTyped.java b/src/main/java/org/microbean/assign/AttributedTyped.java deleted file mode 100644 index f5b49ba..0000000 --- a/src/main/java/org/microbean/assign/AttributedTyped.java +++ /dev/null @@ -1,40 +0,0 @@ -/* -*- 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.assign; - -/** - * An interface whose implementations are affiliated with an {@link AttributedType}. - * - * @author Laird Nelson - * - * @see #attributedType() - */ -public interface AttributedTyped { - - /** - * Returns the {@link AttributedType} with which this {@link AttributedTyped} instance is affiliated. - * - *

Implementations of this method must not return {@code null}.

- * - *

Implementations of this method must return a determinate value.

- * - *

Implementations of this method must be safe for concurrent use by multiple threads.

- * - * @return a non-{@code null} {@link AttributedType} - * - * @see AttributedType - */ - public AttributedType attributedType(); - -} diff --git a/src/main/java/org/microbean/assign/CacheableAnnotatedConstruct.java b/src/main/java/org/microbean/assign/CacheableAnnotatedConstruct.java new file mode 100644 index 0000000..bb2b452 --- /dev/null +++ b/src/main/java/org/microbean/assign/CacheableAnnotatedConstruct.java @@ -0,0 +1,164 @@ +/* -*- 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.assign; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import java.util.function.Predicate; + +import javax.lang.model.AnnotatedConstruct; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; + +import org.microbean.construct.element.AnnotationMirrors; + +import static org.microbean.construct.element.AnnotationMirrors.containsAll; +import static org.microbean.construct.element.AnnotationMirrors.sameAnnotation; + +/** + * An {@link Annotated} implementation wrapping an {@link AnnotatedConstruct}. + * + * @author Laird Nelson + */ +// This deliberately doesn't implement TypeMirror or Element and delegate operations since it is used only for caching. +// It would be nice to ensure this class does not have to be public. +// This class may become an inner or nested class of something else. +final class CacheableAnnotatedConstruct implements Annotated { + + private final List annotations; + + private final T annotated; + + private final Predicate p; + + private int hashCode; + + /** + * Creates a new {@link CacheableAnnotatedConstruct}. + * + * @param annotated a non-{@code null} {@link AnnotatedConstruct} + * + * @param p a {@link Predicate} that returns {@code true} if a given {@link ExecutableElement}, representing an + * annotation interface element, is to be included in equality and hashcode computations; may be {@code null} in which + * case it is as if {@code e -> true} were supplied instead + * + * @exception NullPointerException if {@code annotated} is {@code null} + * + * @see AnnotationMirrors#sameAnnotation(AnnotationMirror, AnnotationMirror, Predicate) + * + * @see AnnotationMirrors#hashCode(AnnotationMirror, Predicate) + */ + CacheableAnnotatedConstruct(final T annotated, final Predicate p) { + super(); + this.p = p == null ? ee -> true : p; + if (annotated instanceof Element e) { + final List as = new ArrayList<>(e.asType().getAnnotationMirrors()); + as.addAll(e.getAnnotationMirrors()); + this.annotations = List.copyOf(as); + } else { + this.annotations = List.copyOf(annotated.getAnnotationMirrors()); + } + this.annotated = annotated; + } + + /** + * Returns a non-{@code null}, immutable, determinate {@link List} of the {@link AnnotationMirror}s supplied at + * {@linkplain #CacheableAnnotatedConstruct(AnnotatedConstruct, Predicate) construction time}. + * + * @return a non-{@code null}, immutable, determinate {@link List} of the {@link AnnotationMirror}s supplied at + * {@linkplain #CacheableAnnotatedConstruct(AnnotatedConstruct, Predicate) construction time} + * + * @see #CacheableAnnotatedConstruct(AnnotatedConstruct, Predicate) + */ + @Override // Annotated + public final List annotations() { + return this.annotations; + } + + /** + * Returns the determinate annotated object supplied at {@linkplain #CacheableAnnotatedConstruct(AnnotatedConstruct, + * Predicate) construction time}. + * + * @return the determinate annotated object supplied at {@linkplain #CacheableAnnotatedConstruct(AnnotatedConstruct, + * Predicate) construction time} + * + * @see #CacheableAnnotatedConstruct(AnnotatedConstruct, Predicate) + */ + @Override // Annotated + public final T annotated() { + return this.annotated; + } + + /** + * Returns {@code true} if and only if the supplied {@link Object} is equal to this {@link CacheableAnnotatedConstruct}. + * + *

Two {@link CacheableAnnotatedConstruct}s are considered equal when their {@linkplain #annotated() annotated object}s are + * {@linkplain Object#equals(Object) equal} and when each {@link AnnotationMirror} is the {@linkplain + * AnnotationMirrors#sameAnnotation(AnnotationMirror, AnnotationMirror, Predicate) same as} every other {@link + * AnnotationMirror}.

+ * + * @param other the {@link Object} to test; may be {@code null} in which case {@code false} will be returned + * + * @return {@code true} if and only if the supplied {@link Object} is equal to this {@link CacheableAnnotatedConstruct} + * + * @see AnnotationMirrors#sameAnnotation(AnnotationMirror, AnnotationMirror, Predicate) + */ + @Override // Object + public final boolean equals(final Object other) { + return this == other || switch (other) { + case null -> false; + case CacheableAnnotatedConstruct her -> + this.getClass() == her.getClass() && + Objects.equals(this.annotated(), her.annotated()) && + containsAll(this.annotations(), her.annotations(), this.p) && + containsAll(her.annotations(), this.annotations(), her.p); + default -> false; + }; + } + + /** + * Returns a non-zero hashcode for this {@link CacheableAnnotatedConstruct} based on both its {@linkplain + * #annotations() annotations} and its {@linkplain #annotated() annotated object}. + * + * @return a non-zero hashcode for this {@link CacheableAnnotatedConstruct} based on both its {@linkplain + * #annotations() annotations} and its {@linkplain #annotated() annotated object} + * + * @see #annotations() + * + * @see #annotated() + * + * @see AnnotationMirrors#hashCode(AnnotationMirror, Predicate) + */ + @Override // Object + public final int hashCode() { + if (this.hashCode == 0) { // volatile not needed, computation is determinate and deterministic + int hashCode = 17 * 31 + (this.annotated == null ? 0 : this.annotated().hashCode()); + for (final AnnotationMirror a : this.annotations()) { + hashCode = 17 * hashCode + AnnotationMirrors.hashCode(a, this.p); + } + this.hashCode = hashCode; + } + return hashCode; + } + + @Override // Object + public final String toString() { + return this.annotations() + " " + this.annotated(); + } + +} diff --git a/src/main/java/org/microbean/assign/Matcher.java b/src/main/java/org/microbean/assign/Matcher.java index 30166a3..4d85101 100644 --- a/src/main/java/org/microbean/assign/Matcher.java +++ b/src/main/java/org/microbean/assign/Matcher.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 @@ -19,16 +19,16 @@ * A {@link BiPredicate} with particular semantics associated with its {@link #test(Object, Object) test(Object, * Object)} method. * - * @param the criteria object + * @param the criteria object * - * @param the object being tested + * @param the object being tested * * @author Laird Nelson * * @see #test(Object, Object) */ @FunctionalInterface -public interface Matcher extends BiPredicate { +public interface Matcher extends BiPredicate { /** * Returns {@code true} if and only if the second argument matches the first argument. @@ -36,18 +36,18 @@ public interface Matcher extends BiPredicate { *

The order of arguments may therefore be significant for {@link Matcher} implementations that do not represent * equality tests.

* - * @param a an object serving as a kind of criteria; must not be {@code null} + * @param c an object serving as a kind of criteria; must not be {@code null} * - * @param b an object to test against the criteria; must not be {@code null} + * @param t an object to test against the criteria; must not be {@code null} * * @return {@code true} if and only if the second argument matches the first argument; {@code false} * otherwise * - * @exception NullPointerException if either {@code a} or {@code b} is {@code null} + * @exception NullPointerException if either {@code c} or {@code t} is {@code null} * * @exception IllegalArgumentException if either non-{@code null} argument is unsuitable for any reason */ - @Override // BiPredicate - public boolean test(final A a, final B b); + @Override // BiPredicate + public boolean test(final C c, final T t); } diff --git a/src/main/java/org/microbean/assign/Normalizer.java b/src/main/java/org/microbean/assign/Normalizer.java new file mode 100644 index 0000000..e214489 --- /dev/null +++ b/src/main/java/org/microbean/assign/Normalizer.java @@ -0,0 +1,132 @@ +/* -*- 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.assign; + +import java.util.Arrays; +import java.util.Map; +import java.util.Objects; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * An experimental, simple, mutable, concurrent cache of objects. + * + * @param the type of object to be normalized + * + * @author Laird Nelson + * + * @see #normalize(Object, Object) + */ +public final class Normalizer { + + private final Map cache; + + /** + * Creates a new {@link Normalizer}. + * + * @see #Normalizer(ConcurrentMap) + */ + public Normalizer() { + this(null); + } + + /** + * Creates a new {@link Normalizer}. + * + * @param cache a {@link ConcurrentMap} that will be used as the internal cache; may be {@code null} in which case a + * default, unbounded, initially empty implementation will be used instead + */ + public Normalizer(final ConcurrentMap cache) { + super(); + this.cache = cache == null ? new ConcurrentHashMap<>() : cache; + } + + /** + * Normalizes the supplied {@code element} such that if this {@link Normalizer} already has an {@linkplain + * Object#equals(Object) equivalent} cached element, the cached element is returned, and, if it does not, the supplied + * {@code element} is cached indefinitely and returned. + * + * @param element the element to normalize; may be {@code null} in which case {@code null} is returned + * + * @return the supplied {@code element} or a previously cached {@linkplain Object#equals(Object) equivalent} element + * + * @see #normalize(Object, Object) + */ + public final T normalize(final T element) { + return this.normalize(element, null); + } + + /** + * Normalizes the supplied {@code element} such that if this {@link Normalizer} already has an {@linkplain + * Object#equals(Object) equivalent} cached element, the cached element is returned, and, if it does not, the supplied + * {@code element} is cached indefinitely and returned. + * + * @param element the element to normalize; may be {@code null} in which case {@code null} is returned + * + * @param extra additional data to include in equality comparisons; may be {@code null}; ignored if {@code element} is + * {@code null} + * + * @return the supplied {@code element} or a previously cached {@linkplain Object#equals(Object) equivalent} element + */ + @SuppressWarnings("unchecked") + public final T normalize(final T element, final Object extra) { + if (element == null) { + return null; + } else if (extra == null) { + return this.cache.computeIfAbsent(element, k -> (T)k); // don't bother to create a Key + } + return this.cache.computeIfAbsent(new Key<>(element, extra), k -> ((Key)k).element()); + } + + + /* + * Inner and nested classes. + */ + + + private static final record Key(T element, Object extra) { + + @Override // Record + public final int hashCode() { + if (this.element == null) { + if (this.extra == null) { + return 0; + } + return this.extra instanceof Object[] a ? Arrays.deepHashCode(a) : this.extra.hashCode(); + } else if (this.extra == null) { + return this.element instanceof Object[] a ? Arrays.deepHashCode(a) : this.element.hashCode(); + } + int hashCode = 17; + int c = this.element instanceof Object[] a ? Arrays.deepHashCode(a) : this.element.hashCode(); + hashCode = 31 * hashCode + c; + c = this.extra instanceof Object[] a ? Arrays.deepHashCode(a) : this.extra.hashCode(); + return 31 * hashCode + c; + } + + @Override // Record + public final boolean equals(final Object other) { + if (other == this) { + return true; + } else if (other != null && this.getClass() == other.getClass()) { + final Key her = (Key)other; + return Objects.deepEquals(this.element, her.element) && Objects.deepEquals(this.extra, her.extra); + } else { + return false; + } + } + + } + +} diff --git a/src/main/java/org/microbean/assign/Qualifiers.java b/src/main/java/org/microbean/assign/Qualifiers.java index f767bb0..04d7828 100644 --- a/src/main/java/org/microbean/assign/Qualifiers.java +++ b/src/main/java/org/microbean/assign/Qualifiers.java @@ -1,6 +1,6 @@ /* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*- * - * Copyright © 2025 microBean™. + * 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 @@ -13,12 +13,41 @@ */ package org.microbean.assign; +import java.lang.constant.Constable; +import java.lang.constant.ConstantDesc; +import java.lang.constant.DynamicConstantDesc; + import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.List; +import java.util.Optional; + +import java.util.function.Predicate; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.QualifiedNameable; +import javax.lang.model.element.TypeElement; + +import javax.lang.model.type.ArrayType; + +import org.microbean.construct.Domain; + +import org.microbean.construct.element.AnnotationMirrors; +import org.microbean.construct.element.SyntheticAnnotationMirror; +import org.microbean.construct.element.SyntheticAnnotationTypeElement; +import org.microbean.construct.element.UniversalElement; -import org.microbean.attributes.Attributes; +import static java.lang.constant.ConstantDescs.BSM_INVOKE; +import static java.lang.constant.ConstantDescs.NULL; + +import static java.lang.constant.MethodHandleDesc.ofConstructor; + +import static java.util.Collections.unmodifiableList; + +import static java.util.Objects.requireNonNull; + +import static javax.lang.model.element.ElementKind.ANNOTATION_TYPE; /** * A utility class for working with qualifiers. @@ -27,20 +56,20 @@ * injection systems.

* * @author Laird Nelson - * - * @see Attributes */ -public class Qualifiers { +public class Qualifiers implements Constable { /* - * Static fields. + * Instance fields. */ - private static final Attributes QUALIFIER = Attributes.of("Qualifier"); + private final Predicate annotationElementInclusionPredicate; - private static final List QUALIFIERS = List.of(QUALIFIER); + private final AnnotationMirror metaQualifier; + + private final List metaQualifiers; /* @@ -50,9 +79,80 @@ public class Qualifiers { /** * Creates a new {@link Qualifiers}. + * + * @param domain a non-{@code null} {@link Domain} + * + * @exception NullPointerException if {@code domain} is {@code null} + * + * @see #Qualifiers(Domain, AnnotationMirror, Predicate) + */ + public Qualifiers(final Domain domain) { + this(domain, null, null); + } + + /** + * Creates a new {@link Qualifiers}. + * + * @param metaQualifier a non-{@code null} {@link AnnotationMirror} to serve as the {@linkplain #metaQualifier() meta-qualifier} + * + * @exception NullPointerException if {@code metaQualifier} is {@code null} + * + * @see #Qualifiers(Domain, AnnotationMirror, Predicate) + */ + public Qualifiers(final AnnotationMirror metaQualifier) { + this(null, requireNonNull(metaQualifier, "metaQualifier"), null); + } + + /** + * Creates a new {@link Qualifiers}. + * + * @param metaQualifier a non-{@code null} {@link AnnotationMirror} to serve as the {@linkplain #metaQualifier() meta-qualifier} + * + * @param annotationElementInclusionPredicate a {@link Predicate} that returns {@code true} if a given {@link + * ExecutableElement}, representing an annotation element, is to be included in the computation; may be {@code null} + * in which case it is as if {@code e -> true} were supplied instead + * + * @exception NullPointerException if {@code metaQualifier} is {@code null} + * + * @see #Qualifiers(Domain, AnnotationMirror, Predicate) + */ + public Qualifiers(final AnnotationMirror metaQualifier, + final Predicate annotationElementInclusionPredicate) { + this(null, requireNonNull(metaQualifier, "metaQualifier"), annotationElementInclusionPredicate); + } + + /** + * Creates a new {@link Qualifiers}. + * + * @param domain a {@link Domain}; if {@code null}, then {@code metaQualifier} must not be {@code null} + * + * @param metaQualifier an {@link AnnotationMirror} to serve as the {@linkplain #metaQualifier() meta-qualifier}; may + * (commonly) be {@code null} in which case a synthetic meta-qualifier will be used instead; must not be {@code null} + * if {@code domain} is {@code null} + * + * @param annotationElementInclusionPredicate a {@link Predicate} that returns {@code true} if a given {@link + * ExecutableElement}, representing an annotation element, is to be included in the computation; may be {@code null} + * in which case it is as if {@code e -> true} were supplied instead + * + * @exception NullPointerException if {@code domain} is {@code null} in certain situations */ - public Qualifiers() { + public Qualifiers(final Domain domain, + final AnnotationMirror metaQualifier, + final Predicate annotationElementInclusionPredicate) { super(); + if (metaQualifier == null) { + final List as = domain.typeElement("java.lang.annotation.Documented").getAnnotationMirrors(); + assert as.size() == 3; // @Documented, @Retention, @Target, in that order, all annotated in turn with each other + this.metaQualifier = + new SyntheticAnnotationMirror(new SyntheticAnnotationTypeElement(List.of(as.get(0), // @Documented + as.get(1), // @Retention(RUNTIME) (happens fortuitously to be RUNTIME) + as.get(2)), // @Target(ANNOTATION_TYPE) (happens fortuitously to be ANNOTATION_TYPE) + "Qualifier")); + } else { + this.metaQualifier = metaQualifier; + } + this.annotationElementInclusionPredicate = annotationElementInclusionPredicate == null ? Qualifiers::returnTrue : annotationElementInclusionPredicate; + this.metaQualifiers = List.of(this.metaQualifier); } @@ -60,115 +160,202 @@ public Qualifiers() { * Instance methods. */ + // The contains* methods below do not apply only to qualifiers. That makes them smell a little funky here but it's not + // really worth breaking out, I don't think. The whole annotationElementInclusionPredicate thing may belong in + // microbean-bean, but it really doesn't *have* to be bean-qualifiers-specific. /** - * Returns an {@link Attributes} that is {@linkplain Attributes#equals(Object) equal to} the supplied {@link - * Attributes}. + * Returns {@code true} if and only if the supplied {@link Collection} of {@link AnnotationMirror}s contains an {@link + * AnnotationMirror} that is {@linkplain AnnotationMirrors#sameAnnotation(AnnotationMirror, AnnotationMirror, + * Predicate) the same} as the supplied {@link AnnotationMirror}. * - *

The returned {@link Attributes} may be the supplied {@link Attributes} or a different instance.

+ * @param c a non-{@code null} {@link Collection} of {@link AnnotationMirror}s * - * @param a an {@link Attributes}; must not be {@code null} + * @param a a non-{@code null} {@link AnnotationMirror} * - * @return an {@link Attributes} that is {@linkplain Attributes#equals(Object) equal to} the supplied {@link - * Attributes}; never {@code null} + * @return {@code true} if and only if the supplied {@link Collection} of {@link AnnotationMirror}s contains an {@link + * AnnotationMirror} that is {@linkplain AnnotationMirrors#sameAnnotation(AnnotationMirror, AnnotationMirror, + * Predicate) the same} as the supplied {@link AnnotationMirror} * - * @exception NullPointerException if {@code a} is {@code null} + * @exception NullPointerException if {@code c} or {@code a} is {@code null} + * + * @see AnnotationMirrors#sameAnnotation(AnnotationMirror, AnnotationMirror, Predicate) + * + * @see AnnotationMirrors#contains(Collection, AnnotationMirror, Predicate) + * + * @see #Qualifiers(Domain, AnnotationMirror, Predicate) */ - public Attributes normalize(final Attributes a) { - return switch (a) { - case null -> throw new NullPointerException("a"); - case Attributes q when this.qualifier().equals(q) -> this.qualifier(); - default -> a; - }; + public final boolean contains(final Collection c, final AnnotationMirror a) { + return AnnotationMirrors.contains(c, a, this.annotationElementInclusionPredicate); } /** - * Returns an immutable {@link List} of {@link Attributes}s that is {@linkplain List#equals(Object) equal to} the - * supplied {@link List}. + * Returns {@code true} if and only if {@code c0} contains all {@linkplain + * AnnotationMirrors#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 * - *

The returned {@link List} may be the supplied {@link List} or a different instance.

+ * @param c1 a non-{@code null} {@link Collection} of {@link AnnotationMirror}s * - * @param list a {@link List} of {@link Attributes}s; must not be {@code null} + * @return {@code true} if and only if {@code c0} contains all {@linkplain + * AnnotationMirrors#sameAnnotation(AnnotationMirror, AnnotationMirror, Predicate) the same} {@link AnnotationMirror}s + * as are found in {@code c1} * - * @return an immutable {@link List} of {@link Attributes}s that is {@linkplain List#equals(Object) equal to} the - * supplied {@link List}; never {@code null} + * @exception NullPointerException if either {@code c0} or {@code c1} is {@code null} * - * @exception NullPointerException if {@code list} is {@code null} + * @see AnnotationMirrors#sameAnnotation(AnnotationMirror, AnnotationMirror, Predicate) + * + * @see AnnotationMirrors#containsAll(Collection, Collection, Predicate) + * + * @see #Qualifiers(Domain, AnnotationMirror, Predicate) */ - public List normalize(final List list) { - return switch (list.size()) { - case 0 -> List.of(); - case 1 -> list.equals(this.qualifiers()) ? this.qualifiers() : List.copyOf(list); - default -> { - final List l = new ArrayList<>(list.size()); - for (final Attributes a : list) { - l.add(this.normalize(a)); - } - yield Collections.unmodifiableList(l); - } - }; + public final boolean containsAll(final Collection c0, + final Collection c1) { + return AnnotationMirrors.containsAll(c0, c1, this.annotationElementInclusionPredicate); } /** - * Returns the qualifier (meta-) qualifier. + * Returns a non-{@code null}, determinate {@link Optional} housing a {@link ConstantDesc} describing this {@link + * Qualifiers}, or an {@linkplain Optional#isEmpty() empty} {@link Optional} if it cannot be described. * - * @return the qualifier (meta-) qualifier; never {@code null} + * @return a non-{@code null}, determinate {@link Optional} housing a {@link ConstantDesc} describing this {@link + * Qualifiers}, or an {@linkplain Optional#isEmpty() empty} {@link Optional} if it cannot be described */ - public Attributes qualifier() { - return QUALIFIER; + @Override // Constable + public Optional describeConstable() { + return (this.metaQualifier instanceof Constable c ? c.describeConstable() : Optional.empty()) + .flatMap(mqDesc -> (this.annotationElementInclusionPredicate == null ? + Optional.of(NULL) : + this.annotationElementInclusionPredicate instanceof Constable c ? + c.describeConstable() : + Optional.empty()) + .map(pDesc -> DynamicConstantDesc.of(BSM_INVOKE, + ofConstructor(this.getClass().describeConstable().orElseThrow(), + Domain.class.describeConstable().orElseThrow(), + AnnotationMirror.class.describeConstable().orElseThrow(), + Predicate.class.describeConstable().orElseThrow()), + NULL, + mqDesc, + pDesc))); } /** - * Returns {@code true} if and only if the supplied {@link Attributes} is an {@link Attributes} that can be used to - * designate other {@link Attributes} as qualifiers. + * Returns a non-{@code null}, determinate {@link AnnotationMirror} that can be used to designate (meta-annotate) + * other annotations as qualifiers. * - * @param q an {@link Attributes}; must not be {@code null} + * @return a non-{@code null}, determinate {@link AnnotationMirror} that can be used to designate (meta-annotate) + * other annotations as qualifiers + */ + public final AnnotationMirror metaQualifier() { + return this.metaQualifier; + } + + /** + * Returns {@code true} if and only if the supplied {@link AnnotationMirror} {@linkplain + * #sameAnnotation(AnnotationMirror, AnnotationMirror) is the same annotation as} the supplied {@link + * AnnotationMirror}. + * + * @param a a non-{@code null} {@link AnnotationMirror} + * + * @return {@code true} if and only if if and only if the supplied {@link AnnotationMirror} {@linkplain + * #sameAnnotation(AnnotationMirror, AnnotationMirror) is the same annotation as} the supplied {@link + * AnnotationMirror} * - * @return {@code true} if and only if the supplied {@link Attributes} is an {@link Attributes} that can be used to - * designate other {@link Attributes} as qualifiers + * @exception NullPointerException if {@code a} is {@code null} * - * @exception NullPointerException if {@code q} is {@code null} + * @see #sameAnnotation(AnnotationMirror, AnnotationMirror) */ - public boolean qualifier(final Attributes q) { - return q.attributes().contains(this.qualifier()); + public final boolean metaQualifier(final AnnotationMirror a) { + return this.sameAnnotation(this.metaQualifier(), a); } /** - * Returns an immutable {@link List} consisting solely of the qualifier (meta-) qualifier. - * - * @return an immutable {@link List}; never {@code null} + * Returns a non-{@code null}, determinate, immutable {@link List} whose sole element is the {@linkplain + * #metaQualifier() meta-qualifier} annotation. * - * @see #qualifier() + * @return a non-{@code null}, determinate, immutable {@link List} whose sole element is the {@linkplain + * #metaQualifier() meta-qualifier} annotation */ - public List qualifiers() { - return QUALIFIERS; + public final List metaQualifiers() { + return this.metaQualifiers; } /** - * Returns an unmodifiable {@link List} consisting only of those {@link Attributes} in the supplied {@link - * Collection} that {@linkplain #qualifier(Attributes) are qualifiers}. + * Returns {@code true} if and only if the supplied {@link AnnotationMirror} has an {@linkplain + * AnnotationMirror#getAnnotationType() annotation type} declared by a {@link TypeElement} that is {@linkplain + * javax.lang.model.AnnotatedConstruct#getAnnotationMirrors() annotated with} at least one annotation {@linkplain + * #metaQualifier(AnnotationMirror) deemed to be the meta-qualifier}. * - * @param c a {@link Collection} of {@link Attributes}s; must not be {@code null} + * @param a a non-{@code null} {@link AnnotationMirror} * - * @return an unmodifiable {@link List} consisting only of those {@link Attributes}s in the supplied {@link - * Collection} that {@linkplain #qualifier(Attributes) are qualifiers}; never {@code null} + * @return {@code true} if and only if the supplied {@link AnnotationMirror} has an {@linkplain + * AnnotationMirror#getAnnotationType() annotation type} declared by a {@link TypeElement} that is {@linkplain + * javax.lang.model.AnnotatedConstruct#getAnnotationMirrors() annotated with} at least one annotation {@linkplain + * #metaQualifier(AnnotationMirror) deemed to be the meta-qualifier} * - * @exception NullPointerException if {@code c} is {@code null} + * @exception NullPointerException if {@code a} is {@code null} */ - public List qualifiers(final Collection c) { - return switch (c) { - case Collection c0 when c0.isEmpty() -> List.of(); - default -> { - final ArrayList list = new ArrayList<>(c.size()); - for (final Attributes a : c) { - if (this.qualifier(a)) { - list.add(this.normalize(a)); + public final boolean qualifier(final AnnotationMirror a) { + if (!this.metaQualifier(a)) { + final TypeElement annotationInterface = (TypeElement)a.getAnnotationType().asElement(); + if (annotationInterface.getKind() == ANNOTATION_TYPE) { + for (final AnnotationMirror ma : annotationInterface.getAnnotationMirrors()) { + if (this.metaQualifier(ma)) { + return true; + } } } - list.trimToSize(); - yield Collections.unmodifiableList(list); } - }; + return false; + } + + /** + * Returns a non-{@code null}, determinate, immutable {@link List} of {@link AnnotationMirror} instances drawn from + * the supplied {@link Collection} that were {@linkplain #qualifier(AnnotationMirror) deemed to be qualifiers}. + * + * @param as a non-{@code null} {@link Collection} of {@link AnnotationMirror}s + * + * @return a non-{@code null}, determinate, immutable {@link List} of {@link AnnotationMirror} instances drawn from + * the supplied {@link Collection} that were {@linkplain #qualifier(AnnotationMirror) deemed to be qualifiers} + * + * @exception NullPointerException if {@code as} is {@code null} + * + * @see #qualifier(AnnotationMirror) + */ + public List qualifiers(final Collection as) { + if (as.isEmpty()) { + return List.of(); + } + final List l = new ArrayList<>(as.size()); + for (final AnnotationMirror a : as) { + if (this.qualifier(a)) { + l.add(a); + } + } + return l.isEmpty() ? List.of() : unmodifiableList(l); + } + + /** + * Determines whether the two {@link AnnotationMirror}s represent the same (underlying, otherwise opaque) annotation. + * + * @param am0 an {@link AnnotationMirror}; may be {@code null} + * + * @param am1 an {@link AnnotationMirror}; may be {@code null} + * + * @return {@code true} if the supplied {@link AnnotationMirror}s represent the same (underlying, otherwise opaque) + * annotation; {@code false} otherwise + * + * @see AnnotationMirrors#sameAnnotation(AnnotationMirror, AnnotatonMirror, Predicate) + * + * @see #Qualifiers(Domain, AnnotationMirror, Predicate) + */ + public final boolean sameAnnotation(final AnnotationMirror am0, final AnnotationMirror am1) { + return AnnotationMirrors.sameAnnotation(am0, am1, this.annotationElementInclusionPredicate); + } + + private static final boolean returnTrue(final X ignored) { + return true; } } diff --git a/src/main/java/org/microbean/assign/Selectable.java b/src/main/java/org/microbean/assign/Selectable.java index dacfaab..7eea889 100644 --- a/src/main/java/org/microbean/assign/Selectable.java +++ b/src/main/java/org/microbean/assign/Selectable.java @@ -1,14 +1,14 @@ /* -*- 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 + * 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 + * 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.assign; @@ -16,8 +16,8 @@ import java.util.List; /** - * A notional list of elements from which immutable sublists may be selected according to some - * criteria. + * A notional, opaque, stable collection of elements from which immutable sublists may be selected according + * to some criteria. * * @param the criteria type * @@ -32,15 +32,16 @@ public interface Selectable { * Selects and returns an immutable {@link List} representing a sublist of this {@link Selectable}'s * elements, as mediated by the supplied criteria. * - *

Implementations of this method must be idempotent and must return a determinate value.

+ *

Implementations of this method must be idempotent and must be stable, i. e. return a + * determinate value.

* *

Implementations of this method must not return {@code null}.

* * @param criteria the criteria to use; may be {@code null} to indicate no particular criteria should be used during * selection * - * @return an immutable sublist of this {@link Selectable}'s elements effectively selected by the supplied {@code - * criteria}; never {@code null} + * @return a non-{@code null}, immutable, determinate sublist of this {@link Selectable}'s elements effectively selected by the + * supplied {@code criteria} */ // Filters this thing according to the supplied criteria, producing a List. // List not Stream to permit caching diff --git a/src/main/java/org/microbean/assign/Selectables.java b/src/main/java/org/microbean/assign/Selectables.java index 040ca6a..1edcf12 100644 --- a/src/main/java/org/microbean/assign/Selectables.java +++ b/src/main/java/org/microbean/assign/Selectables.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 @@ -13,6 +13,7 @@ */ package org.microbean.assign; +import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; @@ -23,6 +24,15 @@ import java.util.function.BiFunction; import java.util.function.BiPredicate; import java.util.function.Function; +import java.util.function.Predicate; + +import javax.lang.model.AnnotatedConstruct; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; + +import static java.util.Objects.requireNonNull; /** * Utility methods for working with {@link Selectable}s. @@ -36,7 +46,7 @@ public final class Selectables { private Selectables() { super(); } - + /** * Returns a {@link Selectable} that caches its results. * @@ -54,7 +64,7 @@ private Selectables() { * * @see #caching(Selectable, BiFunction) */ - public static Selectable caching(final Selectable selectable) { + public static Selectable caching(final Selectable selectable) { final Map> selectionCache = new ConcurrentHashMap<>(); return Selectables.caching(selectable, selectionCache::computeIfAbsent); } @@ -78,10 +88,69 @@ public static Selectable caching(final Selectable selectable) * * @see ConcurrentHashMap#computeIfAbsent(Object, Function) */ - public static Selectable caching(final Selectable selectable, + public static Selectable caching(final Selectable selectable, final BiFunction>, ? extends List> f) { return c -> f.apply(c, selectable::select); } + + /** + * An experimental method that converts a {@link Selectable} accepting {@link Annotated + * Annotated<AnnotatedConstruct>} instances into a {@link Selectable} accepting {@link AnnotatedConstruct} + * instances. + * + * @param the criteria type + * + * @param the element type + * + * @param s a non-{@code null} {@link Selectable} accepting {@link Annotated Annotated<AnnotatedConstruct>} + * instances + * + * @return a non-{@code null}, determinate {@link Selectable} accepting {@link AnnotatedConstruct} instances + * + * @exception NullPointerException if {@code s} is {@code null} + * + * @see #convert(Selectable, Predicate) + * + * @see Annotated + * + * @see AnnotatedConstruct + */ + @Deprecated(forRemoval = true) // Annotated.of(AnnotatedConstruct) exists + public static Selectable convert(final Selectable, E> s) { + return convert(s, null); + } + + /** + * An experimental method that converts a {@link Selectable} accepting {@link Annotated + * Annotated<AnnotatedConstruct>} instances into a {@link Selectable} accepting {@link AnnotatedConstruct} + * instances. + * + * @param the criteria type + * + * @param the element type + * + * @param s a non-{@code null} {@link Selectable} accepting {@link Annotated Annotated<AnnotatedConstruct>} + * instances + * + * @param annotationElementInclusionPredicate 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 a non-{@code null}, determinate {@link Selectable} accepting {@link AnnotatedConstruct} instances + * + * @exception NullPointerException if {@code s} is {@code null} + * + * @see Annotated + * + * @see Annotated#of(AnnotatedConstruct, Predicate) + * + * @see AnnotatedConstruct + */ + @Deprecated(forRemoval = true) // Annotated.of(AnnotatedConstruct) exists + public static Selectable convert(final Selectable, E> s, + final Predicate annotationElementInclusionPredicate) { + return ac -> s.select(Annotated.of(ac, annotationElementInclusionPredicate)); + } /** * Returns a {@link Selectable} whose {@link Selectable#select(Object)} method always returns an {@linkplain List#of() @@ -111,9 +180,10 @@ private static final List empty(final C ignored) { * *

The {@link Selectable} instances returned by this method may or may not cache their selections.

* - *

The selector must (indirectly) designate a sublist from the supplied {@link Collection} as mediated by the - * supplied criteria. The selector must additionally be idempotent and must produce a determinate value when given the - * same arguments.

+ *

The selector tests its first argument to see if it is selected by its second argument. The selector + * is invoked repeatedly. If, for any given invocation, the first argument is selected, the selected element is added + * to the selection that is eventually returned as a sublist of the supplied {@link Collection}. The selector must + * additionally be idempotent and must produce a determinate value when given the same arguments.

* *

No validation of these semantics of the selector is performed.

* @@ -132,9 +202,38 @@ private static final List empty(final C ignored) { @SuppressWarnings("unchecked") public static Selectable filtering(final Collection collection, final BiPredicate p) { - Objects.requireNonNull(p, "p"); + requireNonNull(p, "p"); return collection.isEmpty() ? empty() : c -> (List)collection.stream().filter(e -> p.test(e, c)).toList(); } - + /* + * An experimental convenience method that returns a non-{@code null}, determinate {@link Selectable} + * representing the composition of the supplied {@link Selectable} with the supplied {@code argumentTransformer} {@link + * Function}. + * + * @param the (criteria) type of the sole parameter of the returned {@link Selectable} + * + * @param the (criteria) type of the sole parameter of the supplied {@link Selectable} + * + * @param the element type of both {@link Selectable}s + * + * @param selectable a non-{@code null} {@link Selectable} + * + * @param argumentTransformer a non-{@code null} {@link Function} that maps its sole parameter (of type {@code B}) to + * a value of type {@code C} suitable for supplying as criteria to the supplied {@link Selectable}; must be idempotent + * and return a determinate value + * + * @return a non-{@code null}, determinate, composed {@link Selectable} + * + * @exception NullPointerException if any argument is {@code null} + * + * @see Function#compose(Function) + */ + /* + public static Selectable compose(final Selectable selectable, + final Function argumentTransformer) { + return ((Function>)selectable::select).compose(argumentTransformer)::apply; + } + */ + } diff --git a/src/main/java/org/microbean/assign/SpecializationComparator.java b/src/main/java/org/microbean/assign/SpecializationComparator.java index e52568a..a9eeb69 100644 --- a/src/main/java/org/microbean/assign/SpecializationComparator.java +++ b/src/main/java/org/microbean/assign/SpecializationComparator.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 @@ -38,7 +38,7 @@ public final class SpecializationComparator implements Comparator { /** * Creates a new {@link SpecializationComparator}. * - * @param domain a {@link Domain}; must not be {@code null} + * @param domain a non-{@code null} {@link Domain} * * @exception NullPointerException if {@code domain} is {@code null} */ @@ -85,12 +85,12 @@ public final int compare(final TypeMirror t, final TypeMirror s) { return 1; // nulls right } else if (s == null) { return -1; // nulls right - } else if (domain.sameType(t, s)) { + } else if (this.domain.sameType(t, s)) { return 0; - } else if (domain.subtype(t, s)) { + } else if (this.domain.subtype(t, s)) { // t is a subtype of s; s is a proper supertype of t return -1; - } else if (domain.subtype(s, t)) { + } else if (this.domain.subtype(s, t)) { // s is a subtype of t; t is a proper supertype of s return 1; } else { diff --git a/src/main/java/org/microbean/assign/Types.java b/src/main/java/org/microbean/assign/Types.java index b3478eb..faf82ac 100644 --- a/src/main/java/org/microbean/assign/Types.java +++ b/src/main/java/org/microbean/assign/Types.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 @@ -43,10 +43,14 @@ import static java.lang.constant.ConstantDescs.BSM_INVOKE; +import static java.lang.constant.MethodHandleDesc.ofConstructor; + import static java.util.Comparator.comparing; import static java.util.HashSet.newHashSet; +import static java.util.Objects.requireNonNull; + /** * A utility class that assists with working with a {@link Domain} instance. * @@ -59,14 +63,6 @@ public class Types implements Constable { - /* - * Static fields. - */ - - - private static final ClassDesc CD_Domain = ClassDesc.of("org.microbean.construct.Domain"); - - /* * Instance fields. */ @@ -85,16 +81,16 @@ public class Types implements Constable { /** * Creates a new {@link Types}. * - * @param domain a {@link Domain}; must not be {@code null} + * @param domain a non-{@code null} {@link Domain} * * @exception NullPointerException if {@code domain} is {@code null} */ public Types(final Domain domain) { super(); - this.domain = Objects.requireNonNull(domain, "domain"); this.c = comparing(TypeMirror::getKind, PrimitiveAndReferenceTypeKindComparator.INSTANCE) .thenComparing(new SpecializationComparator(domain)) .thenComparing(Types::erasedName); + this.domain = domain; } @@ -122,8 +118,8 @@ public Types(final Domain domain) { public Optional describeConstable() { return (this.domain instanceof Constable c ? c.describeConstable() : Optional.empty()) .map(domainDesc -> DynamicConstantDesc.of(BSM_INVOKE, - MethodHandleDesc.ofConstructor(ClassDesc.of(this.getClass().getName()), - CD_Domain), + ofConstructor(this.getClass().describeConstable().orElseThrow(), + Domain.class.describeConstable().orElseThrow()), domainDesc)); } @@ -132,7 +128,7 @@ public Optional describeConstable() { * * @return the {@link Domain} affiliated with this {@link Types} instance; never {@code null} */ - public final Domain domain() { + protected final Domain domain() { return this.domain; } @@ -180,7 +176,7 @@ private final boolean isInterface(final TypeMirror t) { } /** - * Returns a non-{@code null} {@link SupertypeList} of the supertypes of the supplied {@link TypeMirror}. + * Returns a non-{@code null}, determinate {@link SupertypeList} of the supertypes of the supplied {@link TypeMirror}. * *

No element in the returned {@link List} will be {@code null}.

* @@ -204,9 +200,9 @@ private final boolean isInterface(final TypeMirror t) { * * * - * @param t a {@link TypeMirror}; must not be {@code null} + * @param t a non-{@code null} {@link TypeMirror} * - * @return a non-{@code null} {@link SupertypeList} of the supertypes of the supplied {@link TypeMirror} + * @return a non-{@code null}, determinate {@link SupertypeList} of the supertypes of the supplied {@link TypeMirror} * * @exception NullPointerException if {@code t} is {@code null} * @@ -225,8 +221,8 @@ public final SupertypeList supertypes(final TypeMirror t) { } /** - * Returns a non-{@code null} {@link SupertypeList} of the supertypes of the supplied {@link TypeMirror}, filtered - * using the supplied {@link Predicate}. + * Returns a non-{@code null}, determinate {@link SupertypeList} of the supertypes of the supplied {@link TypeMirror}, + * filtered using the supplied {@link Predicate}. * *

No element in the returned {@link SupertypeList} will be {@code null}.

* @@ -236,8 +232,6 @@ public final SupertypeList supertypes(final TypeMirror t) { *

No two elements in the list will be {@linkplain Objects#equals(Object) equal} or {@linkplain * Domain#sameType(TypeMirror, TypeMirror) the same type}. (The {@link SupertypeList} is an ordered set.)

* - *

This method returns determinate values.

- * *

The elements of the returned {@link SupertypeList} will be in the following (partial) order:

* *
    @@ -248,14 +242,14 @@ public final SupertypeList supertypes(final TypeMirror t) { * *
* - * @param t a {@link TypeMirror}; must not be {@code null} + * @param t a non-{@code null} {@link TypeMirror} * - * @param p a {@link Predicate}; must not be {@code null} + * @param p a non-{@code null} {@link Predicate} * - * @return a non-{@code null} {@link SupertypeList} of the supertypes of the supplied {@link TypeMirror}, filtered - * using the supplied {@link Predicate} + * @return a non-{@code null}, determinate {@link SupertypeList} of the supertypes of the supplied {@link TypeMirror}, + * filtered using the supplied {@link Predicate} * - * @exception NullPointerException if either {@code t} or {@code p} is {@code null} + * @exception NullPointerException if any argument is {@code null} * * @see Domain#directSupertypes(TypeMirror) * @@ -279,12 +273,13 @@ public final SupertypeList supertypes(final TypeMirror t, final Predicate 1) { - // interface types need to be sorted because they can show up in multiple implements clauses and you want the set - // to be determinate. + // interface types need to be sorted because they can show up in multiple implements clauses and you want the + // set to be determinate. // // (Non-interface supertypes are already sorted from most-specific to least and will not contain primitive - // types. By extension, array types will precede declared types (java.lang.Object). If t is a type variable, then - // either its supertype will be another type variable or a declared type, so type variables precede everything.) + // types. By extension, array types will precede declared types (java.lang.Object). If t is a type variable, + // then either its supertype will be another type variable or a declared type, so type variables precede + // everything.) interfaceTypes.sort(this.c); } if (types.isEmpty()) { @@ -337,7 +332,7 @@ private static final String erasedName(final QualifiedNameable qn) { } /** - * Returns the erased name of the supplied {@link TypeMirror}. + * Returns the non-{@code null}, determinate erased name of the supplied {@link TypeMirror}. * *

Erased name is not a term defined or used by the Java Language Specification. Its definition and * contract follow.

@@ -382,9 +377,9 @@ private static final String erasedName(final QualifiedNameable qn) { *

The erased name of any other {@link TypeMirror} is the return value of an invocation of its {@link * Object#toString() toString()} method.

* - * @param t a {@link TypeMirror}; must not be {@code null} + * @param t a non-{@code null} {@link TypeMirror} * - * @return the erased name of the supplied {@link TypeMirror}; never {@code null} + * @return the non-{@code null}, determinate erased name of the supplied {@link TypeMirror} * * @exception NullPointerException if {@code t} is {@code null} */ @@ -413,7 +408,7 @@ public static final String erasedName(final TypeMirror t) { }; } - private static final boolean returnTrue(final T ignored) { + private static final boolean returnTrue(final X ignored) { return true; } diff --git a/src/test/java/org/microbean/assign/TestComposition.java b/src/test/java/org/microbean/assign/TestComposition.java new file mode 100644 index 0000000..3fc74ff --- /dev/null +++ b/src/test/java/org/microbean/assign/TestComposition.java @@ -0,0 +1,50 @@ +/* -*- 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.assign; + +import java.util.List; + +import java.util.function.Function; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import static org.microbean.assign.Types.erasedName; + +final class TestComposition { + + private TestComposition() { + super(); + } + + @Test + final void test() { + final Selectable s0 = TestComposition::select; + final Function> f0 = s0::select; + final Function> f1 = f0.compose(TestComposition::stringToBoolean); + } + + private static List select(final Boolean ignored) { + return List.of(Integer.valueOf(42)); + } + + private static Boolean stringToBoolean(final String s) { + return Boolean.valueOf(s); + } + +} diff --git a/src/test/java/org/microbean/assign/TestQualifiers.java b/src/test/java/org/microbean/assign/TestQualifiers.java new file mode 100644 index 0000000..e054d67 --- /dev/null +++ b/src/test/java/org/microbean/assign/TestQualifiers.java @@ -0,0 +1,51 @@ +/* -*- 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.assign; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.TypeElement; + +import javax.lang.model.type.DeclaredType; + +import org.junit.jupiter.api.Test; + +import org.microbean.construct.DefaultDomain; +import org.microbean.construct.Domain; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import static org.microbean.construct.element.AnnotationMirrors.retentionPolicy; + +final class TestQualifiers { + + private static final Domain domain = new DefaultDomain(); + + private TestQualifiers() { + super(); + } + + @Test + void test() { + final Qualifiers q = new Qualifiers(domain); + final AnnotationMirror metaQualifier = q.metaQualifier(); + final DeclaredType metaQualifierAnnotationType = metaQualifier.getAnnotationType(); + final TypeElement metaQualifierTypeElement = (TypeElement)metaQualifierAnnotationType.asElement(); + assertTrue(metaQualifierTypeElement.getQualifiedName().toString().endsWith("Qualifier")); + assertSame(RUNTIME, retentionPolicy(metaQualifier)); + } + +} diff --git a/src/test/java/org/microbean/assign/TestSupertypesWithTypeUseAnnotations.java b/src/test/java/org/microbean/assign/TestSupertypesWithTypeUseAnnotations.java new file mode 100644 index 0000000..fcc6686 --- /dev/null +++ b/src/test/java/org/microbean/assign/TestSupertypesWithTypeUseAnnotations.java @@ -0,0 +1,132 @@ +/* -*- 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.assign; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import java.util.List; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.TypeElement; + +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeMirror; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.microbean.construct.DefaultDomain; +import org.microbean.construct.Domain; + +import org.microbean.construct.type.UniversalType; + +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE_USE; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import static org.microbean.assign.Types.erasedName; + +final class TestSupertypesWithTypeUseAnnotations { + + private Domain domain; + + private Types types; + + private TestSupertypesWithTypeUseAnnotations() { + super(); + } + + @BeforeEach + final void setup() { + this.domain = new DefaultDomain(); + this.types = new Types(this.domain); + } + + @Test + final void test() { + final TypeElement te = this.domain.typeElement(B.class.getCanonicalName()); + // Surprising, but see https://mail.openjdk.org/pipermail/type-annotations-spec-experts/2013-November/000174.html: + // + // "JSR 308 introduced ElementType.TYPE_USE as pertaining not only to uses of types but also to declarations of + // types; logically, it's a 'supertype' [superset?] of ElementType.TYPE." + // + assertEquals(1, te.getAnnotationMirrors().size()); + + final DeclaredType dt = (DeclaredType)te.asType(); + + // The TYPE_USE annotation is also not "propagated" to the TypeMirror "underlying" the TypeElement representing the + // declaration. Is this surprising? I guess not? + assertEquals(0, dt.getAnnotationMirrors().size()); + + final DeclaredType superclassType = (DeclaredType)te.getSuperclass(); + final TypeElement superclassElement = (TypeElement)superclassType.asElement(); + assertEquals(this.domain.typeElement(A.class.getCanonicalName()), superclassElement); + + // Makes sense; A has no annotations on it + assertTrue(superclassElement.getAnnotationMirrors().isEmpty()); + + // Makes sense; the-usage-of-A-in-B's-extends-clause has an annotation on it + assertEquals(1, superclassType.getAnnotationMirrors().size()); + + // OK, we're using Domain. That means we can do evil awful things with annotations. For example, we could propagate + // declaration annotations to type usage ones: + ((UniversalType)dt).getAnnotationMirrors().addAll(te.getAnnotationMirrors()); + + // Now dt has declaration annotations: + assertEquals(1, dt.getAnnotationMirrors().size()); + + // This shouldn't change anything: + assertTrue(domain.sameType(domain.typeElement(B.class.getCanonicalName()).asType(), // fresh copy + dt)); + + } + + @Test + final void test2() { + final TypeElement te = this.domain.typeElement(C.class.getCanonicalName()); + List directSupertypes = this.domain.directSupertypes(te.asType()); + assertEquals(1, directSupertypes.size()); + final DeclaredType bType = (DeclaredType)directSupertypes.get(0); + assertTrue(bType.getAnnotationMirrors().isEmpty()); + directSupertypes = this.domain.directSupertypes(bType); + assertEquals(1, directSupertypes.size()); + final DeclaredType aType = (DeclaredType)directSupertypes.get(0); + assertEquals(1, aType.getAnnotationMirrors().size()); // type use annotations are preserved + } + + @Target(TYPE_USE) + @Retention(RUNTIME) + private static @interface Gorp {} + + private static sealed class A permits B { + + } + + @Gorp + private static sealed class B extends @Gorp A permits C { + + } + + private static final class C extends B { + + } + +} From df102ff6f61f6595995a35600b8932be4b4d6789 Mon Sep 17 00:00:00 2001 From: Laird Nelson Date: Fri, 20 Feb 2026 21:55:55 -0800 Subject: [PATCH 2/2] Fixing broken release Signed-off-by: Laird Nelson --- README.md | 2 +- pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f1bd04f..7747642 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ dependency: org.microbean microbean-assign - 0.0.12 + 0.0.13
``` diff --git a/pom.xml b/pom.xml index c02b432..8eb8827 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ org.microbean microbean-assign - 0.0.12-SNAPSHOT + 0.0.13-SNAPSHOT microBean™ Assign microBean™ Assign: Utility classes for implementing Java type assignment.