defaultQualifiers() {
+ return DEFAULT_QUALIFIERS;
+ }
+
+ /**
+ * Returns an {@link Attributes} that is {@linkplain Attributes#equals(Object) equal to} the supplied {@link
+ * Attributes}.
+ *
+ * The returned {@link Attributes} may be the supplied {@link Attributes} or a different instance.
+ *
+ * @param a an {@link Attributes}; must not be {@code null}
+ *
+ * @return an {@link Attributes} that is {@linkplain Attributes#equals(Object) equal to} the supplied {@link
+ * Attributes}; never {@code null}
+ *
+ * @exception NullPointerException if {@code a} is {@code null}
+ */
+ public Attributes normalize(final Attributes a) {
+ return switch (a) {
+ case null -> throw new NullPointerException("a");
+ case Attributes q when this.defaultQualifier(q) -> this.defaultQualifier();
+ default -> super.normalize(a);
+ };
+ }
+
+ /**
+ * Returns an immutable {@link List} of {@link Attributes}s that is {@linkplain List#equals(Object) equal to} the
+ * supplied {@link List}.
+ *
+ * The returned {@link List} may be the supplied {@link List} or a different instance.
+ *
+ * @param list a {@link List} of {@link Attributes}s; must not be {@code null}
+ *
+ * @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 {@code list} is {@code null}
+ */
+ public List normalize(final List list) {
+ return switch (list.size()) {
+ case 0 -> List.of();
+ case 1 -> list.equals(this.defaultQualifiers()) ? this.defaultQualifiers() : List.copyOf(list);
+ default -> super.normalize(list);
+ };
+ }
+
+ /**
+ * Returns the primordial qualifier.
+ *
+ * @return the primordial qualifier; never {@code null}
+ *
+ * @see #primordialQualifiers()
+ */
+ public Attributes primordialQualifier() {
+ return PRIMORDIAL_QUALIFIER;
+ }
+
+ /**
+ * Returns {@code true} if and only if the supplied {@link Attributes} {@linkplain
+ * Attributes#equals(Object) is equal to} the {@linkplain #primordialQualifier() primordial qualifier}.
+ *
+ * @param a an {@link Attributes}; must not be {@code null}
+ *
+ * @return {@code true} if and only if the supplied {@link Attributes} {@linkplain
+ * Attributes#equals(Object) is equal to} the {@linkplain #primordialQualifier() primordial qualifier}
+ *
+ * @exception NullPointerException if {@code a} is {@code null}
+ */
+ public boolean primordialQualifier(final Attributes a) {
+ return this.primordialQualifier() == a || this.primordialQualifier().equals(a) && this.qualifier(a);
+ }
+
+ /**
+ * Returns an immutable {@link List} consisting solely of the primordial qualifier.
+ *
+ * @return an immutable {@link List}; never {@code null}
+ *
+ * @see #primordialQualifier()
+ */
+ public List primordialQualifiers() {
+ return PRIMORDIAL_QUALIFIERS;
+ }
+
+ /**
+ * Returns an unmodifiable {@link List} consisting only of those {@link Attributes} in the supplied {@link
+ * Collection} that {@linkplain #qualifier(Attributes) are qualifiers}.
+ *
+ * @param c a {@link Collection} of {@link Attributes}s; must not be {@code null}
+ *
+ * @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}
+ *
+ * @exception NullPointerException if {@code c} is {@code null}
+ */
+ public List qualifiers(final Collection extends Attributes> c) {
+ return switch (c) {
+ case Collection> c0 when c0.isEmpty() -> List.of();
+ case Collection> c0 when c0.equals(defaultQualifiers()) -> defaultQualifiers();
+ case Collection> c0 when c0.equals(anyAndDefaultQualifiers()) -> anyAndDefaultQualifiers();
+ default -> super.qualifiers(c);
+ };
+ }
+
+}
diff --git a/src/main/java/org/microbean/bean/Ranked.java b/src/main/java/org/microbean/bean/Ranked.java
index 8d76910..33d4f12 100644
--- a/src/main/java/org/microbean/bean/Ranked.java
+++ b/src/main/java/org/microbean/bean/Ranked.java
@@ -38,7 +38,7 @@
*
* @deprecated This class is deprecated for future removal.
*/
-@Deprecated
+@Deprecated(forRemoval = true)
public interface Ranked {
diff --git a/src/main/java/org/microbean/bean/Selectables.java b/src/main/java/org/microbean/bean/Selectables.java
index 9d17802..33fcced 100644
--- a/src/main/java/org/microbean/bean/Selectables.java
+++ b/src/main/java/org/microbean/bean/Selectables.java
@@ -66,8 +66,8 @@ private Selectables() {
* @exception NullPointerException if any argument is {@code null}
*/
public static final Selectable ambiguityReducing(final Selectable s,
- final Predicate super E> p,
- final ToIntFunction super E> ranker) {
+ final Predicate super E> p, // are you an alternate?
+ final ToIntFunction super E> ranker) { // rank?
requireNonNull(s, "s");
requireNonNull(p, "p");
requireNonNull(ranker, "ranker");
@@ -80,7 +80,7 @@ public static final Selectable ambiguityReducing(final Selectable {
- final List elements = s.select(c);
+ final List elements = s.select(c); // remember: c could be null; the result could be everything
final int size = elements.size();
switch (size) {
case 0:
diff --git a/src/main/java/org/microbean/bean/model/Model.java b/src/main/java/org/microbean/bean/model/Model.java
new file mode 100644
index 0000000..6d2457c
--- /dev/null
+++ b/src/main/java/org/microbean/bean/model/Model.java
@@ -0,0 +1,147 @@
+/* -*- 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.bean.model;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+import org.microbean.assign.Aggregate;
+import org.microbean.assign.AttributedElement;
+import org.microbean.assign.AttributedType;
+import org.microbean.assign.Selectable;
+
+import org.microbean.bean.Bean;
+
+import static java.util.HashMap.newHashMap;
+
+import static java.util.HashSet.newHashSet;
+
+import static java.util.Objects.requireNonNull;
+
+import static java.util.stream.Collectors.toUnmodifiableSet;
+
+public final class Model {
+
+ private final Set dependencyResolutions;
+
+ private volatile boolean valid;
+
+ private Model() {
+ this(Set.of());
+ }
+
+ private Model(Set extends DependencyResolution> dependencyResolutions) {
+ super();
+ if (dependencyResolutions == null || dependencyResolutions.isEmpty()) {
+ this.dependencyResolutions = Set.of();
+ this.valid = true;
+ } else {
+ this.dependencyResolutions = Set.copyOf(dependencyResolutions);
+ }
+ }
+
+ @Override // Object
+ public final boolean equals(final Object other) {
+ return switch (other) {
+ case null -> false;
+ case Model m when this.getClass() == m.getClass() -> this.dependencyResolutions.equals(m.dependencyResolutions);
+ default -> false;
+ };
+ }
+
+ @Override // Object
+ public final int hashCode() {
+ return this.dependencyResolutions.hashCode();
+ }
+
+ public final boolean valid() {
+ if (this.valid) { // volatile read
+ return true;
+ }
+ for (final DependencyResolution dependencyResolution : this.dependencyResolutions) {
+ if (dependencyResolution.beans().size() != 1) {
+ return false;
+ }
+ }
+ this.valid = true; // volatile write
+ return true;
+ }
+
+ public final Set ambiguousDependencyResolutions() {
+ return this.dependencyResolutions.stream().filter(DependencyResolution::ambiguous).collect(toUnmodifiableSet());
+ }
+
+ public final Set unsatisfiedDependencyResolutions() {
+ return this.dependencyResolutions.stream().filter(DependencyResolution::unsatisfied).collect(toUnmodifiableSet());
+ }
+
+ // So you can do:
+ // org.microbean.assign.Selectables.caching(selectable, model.toSelectionCache());
+ public final BiFunction super AttributedType, Function super AttributedType, ? extends List>>, ? extends List>> toSelectionCache() {
+ if (!this.valid()) {
+ throw new IllegalStateException("not valid");
+ }
+ final Map>> m = newHashMap(this.dependencyResolutions.size()); // too big but whatever
+ for (final DependencyResolution r : this.dependencyResolutions) {
+ m.putIfAbsent(r.attributedType(), r.beans());
+ }
+ return m::computeIfAbsent; // m is mutable on purpose; we can revisit if we decide that a Model is the absolute source of truth
+ }
+
+ // s would normally be typesafeFiltering and ambiguityReducing but not necessarily cached
+ @SuppressWarnings("unchecked")
+ public static Model of(final Selectable super AttributedType, ? extends Bean>> s) {
+ final Collection extends Aggregate> allBeans = s.select(null);
+ if (allBeans.isEmpty()) {
+ return new Model(Set.of());
+ }
+ final Map>> m2 = newHashMap(allBeans.size() * 5); // estimate;
+ final Set dependencyResolutions = newHashSet(allBeans.size() * 5);
+ for (final Aggregate bean : allBeans) {
+ for (final AttributedElement dependency : bean.dependencies()) {
+ dependencyResolutions.add(new DependencyResolution(dependency,
+ m2.computeIfAbsent(dependency.attributedType(),
+ at -> (List>)s.select(at))));
+ }
+ }
+ return new Model(dependencyResolutions);
+ }
+
+ public static final record DependencyResolution(AttributedElement attributedElement, List> beans) {
+
+ public DependencyResolution {
+ requireNonNull(attributedElement, "attributedElement");
+ beans = beans == null ? List.of() : List.copyOf(beans);
+ }
+
+ public final AttributedType attributedType() {
+ return this.attributedElement.attributedType();
+ }
+
+ public final boolean ambiguous() {
+ return this.beans().size() > 1;
+ }
+
+ public final boolean unsatisfied() {
+ return this.beans().isEmpty();
+ }
+
+ }
+
+}
diff --git a/src/main/java/org/microbean/bean/model/package-info.java b/src/main/java/org/microbean/bean/model/package-info.java
index dfcb9ad..e4e3475 100644
--- a/src/main/java/org/microbean/bean/model/package-info.java
+++ b/src/main/java/org/microbean/bean/model/package-info.java
@@ -1,6 +1,6 @@
/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
*
- * Copyright © 2024 microBean™.
+ * Copyright © 2024–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
@@ -17,6 +17,26 @@
*
* This package is experimental, incomplete, and subject to change at any moment.
*
+ * The general idea is:
+ *
+ *
+ *
+ * - You start with a list of {@link org.microbean.bean.Bean}s ({@link org.microbean.assign.Aggregate}s, really). Each
+ * element will have {@linkplain org.microbean.bean.Bean#dependencies() dependencies}. A dependency is represented by an
+ * {@link org.microbean.assign.AttributedElement}. (For any dependency, it will not represent the bean that supplies
+ * it.)
+ *
+ * - From this list, you can extract a set of {@link org.microbean.assign.AttributedType}s. These represent
+ * all the demand in the system.
+ *
+ * - For each such {@link org.microbean.assign.AttributedType}, you can perform typesafe resolution and find a {@link
+ * org.microbean.bean.Bean} that satisfies the demand.
+ *
+ * - The resulting model should not have any "holes". That is, every {@link
+ * org.microbean.assign.AttributedType} should be matched up with exactly one {@link org.microbean.bean.Bean}.
+ *
+ *
+ *
* @author Laird Nelson
*/
package org.microbean.bean.model;
diff --git a/src/test/java/org/microbean/bean/TestBean.java b/src/test/java/org/microbean/bean/TestBean.java
index 45d7742..9507138 100644
--- a/src/test/java/org/microbean/bean/TestBean.java
+++ b/src/test/java/org/microbean/bean/TestBean.java
@@ -3,12 +3,12 @@
* Copyright © 2023–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
+ * 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.bean;
@@ -17,7 +17,6 @@
import java.util.List;
-import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.microbean.constant.Constables;
@@ -28,34 +27,33 @@
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
-import static org.microbean.assign.Qualifiers.anyAndDefaultQualifiers;
-
final class TestBean {
private static final Domain domain = new DefaultDomain();
private static final BeanTypes beanTypes = new BeanTypes(domain);
-
+
+ private static final Qualifiers qualifiers = new Qualifiers();
+
private TestBean() {
super();
}
- @Disabled // types are not currently Constable
@Test
final void testConstableStuff() {
final Factory f = new Constant<>("Hello");
final Id id =
new Id(beanTypes.beanTypes(List.of(domain.declaredType("java.lang.String"),
domain.javaLangObject().asType())),
- anyAndDefaultQualifiers());
+ qualifiers.anyAndDefaultQualifiers());
assertTrue(id instanceof Constable);
- assertFalse(id.types().describeConstable().isEmpty()); // Constables.describeConstable(id.types()).isEmpty());
+ assertFalse(id.types().describeConstable().isEmpty());
assertFalse(id.describeConstable().isEmpty());
}
@Test
final void testAnyAndDefaultQualifiersCanBeConstable() {
- assertFalse(Constables.describeConstable(anyAndDefaultQualifiers()).isEmpty());
+ assertFalse(Constables.describeConstable(qualifiers.anyAndDefaultQualifiers()).isEmpty());
}
-
+
}
diff --git a/src/test/java/org/microbean/bean/TestBeanSelection.java b/src/test/java/org/microbean/bean/TestBeanSelection.java
index a836059..0db75e5 100644
--- a/src/test/java/org/microbean/bean/TestBeanSelection.java
+++ b/src/test/java/org/microbean/bean/TestBeanSelection.java
@@ -37,8 +37,10 @@ final class TestBeanSelection {
private static final BeanTypes beanTypes = new BeanTypes(domain);
+ private static final Qualifiers qualifiers = new Qualifiers();
+
private static final Matcher matcher =
- new IdMatcher(new BeanTypeMatcher(domain), new BeanQualifiersMatcher());
+ new IdMatcher(new BeanTypeMatcher(domain), new BeanQualifiersMatcher(qualifiers));
private TestBeanSelection() {
super();
diff --git a/src/test/java/org/microbean/bean/TestConstableSemantics.java b/src/test/java/org/microbean/bean/TestConstableSemantics.java
index 980c66e..2d150c6 100644
--- a/src/test/java/org/microbean/bean/TestConstableSemantics.java
+++ b/src/test/java/org/microbean/bean/TestConstableSemantics.java
@@ -3,36 +3,26 @@
* Copyright © 2023–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
+ * 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.bean;
import java.lang.constant.Constable;
-import java.lang.constant.DynamicConstantDesc;
-import java.lang.constant.MethodHandleDesc;
-import java.lang.constant.MethodTypeDesc;
import java.lang.invoke.MethodHandles;
-import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.Set;
-import java.util.SortedSet;
-import java.util.TreeSet;
import javax.lang.model.type.TypeMirror;
import org.junit.jupiter.api.BeforeAll;
-import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.microbean.constant.Constables;
@@ -47,21 +37,19 @@
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNotSame;
-import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
-import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static java.lang.invoke.MethodHandles.lookup;
-import static org.microbean.assign.Qualifiers.anyAndDefaultQualifiers;
-
final class TestConstableSemantics {
private static Domain domain;
private static BeanTypes beanTypes;
+ private static Qualifiers qualifiers;
+
private TestConstableSemantics() {
super();
}
@@ -70,11 +58,12 @@ private TestConstableSemantics() {
static final void initializeDomain() {
domain = new DefaultDomain();
beanTypes = new BeanTypes(domain);
+ qualifiers = new Qualifiers();
}
@Test
final void testAnyAndDefaultQualifiersList() throws ReflectiveOperationException {
- final List> list = anyAndDefaultQualifiers();
+ final List> list = qualifiers.anyAndDefaultQualifiers();
assertEquals(list, Constables.describeConstable(list).orElseThrow().resolveConstantDesc(MethodHandles.lookup()));
}
@@ -128,7 +117,7 @@ final void testId() throws ReflectiveOperationException {
final Id id =
new Id(beanTypes.beanTypes(List.of(domain.typeElement("java.lang.String").asType(),
domain.javaLangObject().asType())),
- anyAndDefaultQualifiers());
+ qualifiers.anyAndDefaultQualifiers());
final Id id2 = (Id)Constables.describeConstable(id)
.orElseThrow()
.resolveConstantDesc(lookup());
diff --git a/src/test/java/org/microbean/bean/TestQualifiers.java b/src/test/java/org/microbean/bean/TestQualifiers.java
index 6ce559b..c1d3cd7 100644
--- a/src/test/java/org/microbean/bean/TestQualifiers.java
+++ b/src/test/java/org/microbean/bean/TestQualifiers.java
@@ -3,12 +3,12 @@
* Copyright © 2023–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
+ * 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.bean;
@@ -23,26 +23,25 @@
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
-import static org.microbean.assign.Qualifiers.defaultQualifier;
-import static org.microbean.assign.Qualifiers.qualifier;
-
final class TestQualifiers {
+ private static final Qualifiers qualifiers = new Qualifiers();
+
private TestQualifiers() {
super();
}
@Test
final void testDefaultQualifierStaticMethod() {
- final Attributes dq = defaultQualifier();
+ final Attributes dq = qualifiers.defaultQualifier();
assertEquals("Default", dq.name());
final Collection extends Attributes> md = dq.attributes(dq.name());
assertEquals(1, md.size());
final Attributes q = md.iterator().next();
assertEquals("Qualifier", q.name());
assertTrue(q.values().isEmpty());
- assertTrue(qualifier(dq));
- assertFalse(qualifier(q));
+ assertTrue(qualifiers.qualifier(dq));
+ assertFalse(qualifiers.qualifier(q));
}
}
diff --git a/src/test/java/org/microbean/bean/TestSelectables.java b/src/test/java/org/microbean/bean/TestSelectables.java
index 726fe84..f398a28 100644
--- a/src/test/java/org/microbean/bean/TestSelectables.java
+++ b/src/test/java/org/microbean/bean/TestSelectables.java
@@ -17,13 +17,8 @@
import org.junit.jupiter.api.Test;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
-import static org.microbean.assign.Qualifiers.anyAndDefaultQualifiers;
-
final class TestSelectables {
private TestSelectables() {
@@ -40,5 +35,5 @@ final void testMethodReferenceAndNull() {
}
}
-
+
}
diff --git a/src/test/java/org/microbean/bean/model/TestElements.java b/src/test/java/org/microbean/bean/model/TestElements.java
index b6046ba..6aa80a0 100644
--- a/src/test/java/org/microbean/bean/model/TestElements.java
+++ b/src/test/java/org/microbean/bean/model/TestElements.java
@@ -1,6 +1,6 @@
/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
*
- * Copyright © 2024 microBean™.
+ * Copyright © 2024–2025 microBean™.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
@@ -13,15 +13,7 @@
*/
package org.microbean.bean.model;
-import java.io.StringWriter;
-
-import java.util.ArrayList;
-import java.util.List;
-
import javax.lang.model.element.Element;
-import javax.lang.model.element.ExecutableElement;
-
-import javax.lang.model.util.ElementFilter;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
@@ -37,14 +29,14 @@
final class TestElements {
private static final Domain domain = new DefaultDomain();
-
+
private TestElements() {
super();
}
@Test
final void testStreamDepthFirst() {
- final Element self = domain.typeElement(this.getClass().getName());
+ final Element self = domain.typeElement(this.getClass().getCanonicalName());
assertTrue(self instanceof UniversalElement);
Trees.streamDepthFirst(self, Elements::parametersAndEnclosedElements)
.forEachOrdered(e -> System.out.println(e.getSimpleName() + " (" + e.getKind() + ")"));
@@ -62,5 +54,5 @@ final void testBigHorkingStream() {
private static final void frob(int a, int b) {
class goop {};
}
-
+
}