diff --git a/README.md b/README.md
index 331d600..7747642 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,8 @@
[](https://search.maven.org/artifact/org.microbean/microbean-assign)
+
+
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.13
```
diff --git a/pom.xml b/pom.xml
index 6e21d3c..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.
@@ -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 extends Annotated extends Element>> 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 extends Assignment>> assign(final Function super AttributedType, ?> r) {
- final Collection extends AttributedElement> ds = this.dependencies();
+ public default SequencedSet extends Assignment>> assign(final Function super Annotated extends AnnotatedConstruct>, ?> r) {
+ final Collection extends Annotated extends Element>> 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 super ExecutableElement> 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 extends Element> 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 extends ConstantDesc> 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 extends ConstantDesc> 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 super ExecutableElement> 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 super ExecutableElement> 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 extends AnnotatedConstruct> 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 super ExecutableElement> 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 super ExecutableElement> 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 super ExecutableElement> annotationElementInclusionPredicate) {
super();
+ if (metaQualifier == null) {
+ final List extends AnnotationMirror> 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 extends AnnotationMirror> 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 extends AnnotationMirror> c0,
+ final Collection extends AnnotationMirror> 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 extends ConstantDesc> 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 extends Attributes> 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 extends AnnotationMirror> 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 super C, E> 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 super C, E> selectable,
final BiFunction super C, Function super C, ? extends List>, ? 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 super Annotated, 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 super Annotated, E> s,
+ final Predicate super ExecutableElement> 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 extends E> collection,
final BiPredicate super E, ? super C> 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 super C, E> selectable,
+ final Function super B, ? extends C> 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 extends ConstantDesc> 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 extends ConstantDesc> 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 supe
}
} else {
if (interfaceTypes.size() > 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 extends TypeMirror> 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 {
+
+ }
+
+}