From 824a718676e0af9a5fc0676c34bcf5bc339561c1 Mon Sep 17 00:00:00 2001
From: Laird Nelson
Date: Fri, 20 Feb 2026 23:58:39 -0800
Subject: [PATCH] Road grading; refreshes dependencies
Signed-off-by: Laird Nelson
---
README.md | 18 +-
pom.xml | 34 +-
src/main/java/module-info.java | 3 +-
.../java/org/microbean/scopelet/Instance.java | 6 +-
.../microbean/scopelet/MapBackedScopelet.java | 6 +-
.../org/microbean/scopelet/NoneScopelet.java | 7 +-
.../org/microbean/scopelet/Qualifiers.java | 176 +++++++
.../microbean/scopelet/ScopedInstances.java | 334 ++++++-------
.../java/org/microbean/scopelet/Scopelet.java | 22 +-
.../java/org/microbean/scopelet/Scopes.java | 437 +++++++++++++-----
.../microbean/scopelet/SingletonScopelet.java | 5 +-
.../TooManyActiveScopeletsException.java | 2 +-
.../org/microbean/scopelet/TestScopes.java | 86 ----
13 files changed, 670 insertions(+), 466 deletions(-)
create mode 100644 src/main/java/org/microbean/scopelet/Qualifiers.java
delete mode 100644 src/test/java/org/microbean/scopelet/TestScopes.java
diff --git a/README.md b/README.md
index f2c5ce6..787d012 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,11 @@
# microBean™ Scopelet
-[](https://search.maven.org/artifact/org.microbean/microbean-scopelet)
+[](https://central.sonatype.com/artifact/org.microbean/microbean-scopelet)
-The microBean™ Scopelet project provides classes and interfaces assisting with implementing dependency injection scopes.
+
+
+The microBean™ Scopelet™ project provides classes and interfaces assisting with implementing dependency injection
+scopes.
# Status
@@ -15,19 +18,20 @@ changes, regardless of project version and without notice.**
# Requirements
-microBean™ Scopelet requires a Java runtime of version 16 or higher.
+microBean™ Scopelet™ requires a Java runtime of version 16 or higher.
# Installation
-microBean™ Scopelet is available on [Maven Central](https://search.maven.org/). Include microBean™ Scopelet as a Maven
-dependency:
+microBean™ Scopelet™ is available on [Maven
+Central](https://central.sonatype.com/artifact/org.microbean/microbean-scopelet). Include microBean™ Scopelet™ as a
+Maven dependency:
```xml
org.microbean
microbean-scopelet
-
- 0.0.11
+
+ 0.0.12
```
diff --git a/pom.xml b/pom.xml
index 6e19eb3..8cd6069 100644
--- a/pom.xml
+++ b/pom.xml
@@ -71,7 +71,7 @@
${project.organization.name}. All rights reserved.]]>
<a href="${project.url}" target="_top"><span style="font-family:Lobster, cursive;">µb</span> ${project.artifactId}</a> ${project.version}
- https://microbean.github.io/microbean-assign/apidocs/,https://microbean.github.io/microbean-attributes/apidocs/,https://microbean.github.io/microbean-bean/apidocs/,https://microbean.github.io/microbean-construct/apidocs/,https://microbean.github.io/microbean-reference/apidocs/
+ https://microbean.github.io/microbean-assign/apidocs/,https://microbean.github.io/microbean-bean/apidocs/,https://microbean.github.io/microbean-construct/apidocs/,https://microbean.github.io/microbean-reference/apidocs/
2
@@ -121,37 +121,31 @@
org.microbean
microbean-assign
- 0.0.11
-
-
-
- org.microbean
- microbean-attributes
- 0.0.5
+ 0.0.14
org.microbean
microbean-bean
- 0.0.22
+ 0.0.23
org.microbean
microbean-construct
- 0.0.18
+ 0.0.24
org.microbean
microbean-event
- 0.0.3
+ 0.0.4
org.microbean
microbean-reference
- 0.0.5
+ 0.0.6
@@ -165,12 +159,6 @@
compile
-
- org.microbean
- microbean-attributes
- compile
-
-
org.microbean
microbean-bean
@@ -330,7 +318,7 @@
com.puppycrawl.tools
checkstyle
- 12.3.0
+ 13.0.0
@@ -351,7 +339,7 @@
maven-compiler-plugin
- 3.14.1
+ 3.15.0
-Xlint:all
@@ -361,7 +349,7 @@
maven-dependency-plugin
- 3.9.0
+ 3.10.0
maven-deploy-plugin
@@ -463,7 +451,7 @@
org.codehaus.mojo
versions-maven-plugin
- 2.20.1
+ 2.21.0
io.smallrye
@@ -473,7 +461,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 b58b070..b71b881 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 © 2023–2025 microBean™.
+ * Copyright © 2023–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.scopelet;
requires transitive org.microbean.assign;
- requires org.microbean.attributes;
requires transitive org.microbean.bean;
requires org.microbean.constant;
requires org.microbean.construct;
diff --git a/src/main/java/org/microbean/scopelet/Instance.java b/src/main/java/org/microbean/scopelet/Instance.java
index 6eb031a..392ce81 100644
--- a/src/main/java/org/microbean/scopelet/Instance.java
+++ b/src/main/java/org/microbean/scopelet/Instance.java
@@ -1,6 +1,6 @@
/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
*
- * Copyright © 2023–2025 microBean™.
+ * Copyright © 2023–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
@@ -84,8 +84,8 @@ final class Instance implements AutoCloseable, Supplier {
* @param destruction a {@link Destruction}; may be {@code null}
*/
Instance(final I contextualInstance,
- final Destructor destructor,
- final Destruction destruction) {
+ final Destructor destructor,
+ final Destruction destruction) {
super();
this.destruction = destruction;
this.object = contextualInstance;
diff --git a/src/main/java/org/microbean/scopelet/MapBackedScopelet.java b/src/main/java/org/microbean/scopelet/MapBackedScopelet.java
index 088bb84..1b14676 100644
--- a/src/main/java/org/microbean/scopelet/MapBackedScopelet.java
+++ b/src/main/java/org/microbean/scopelet/MapBackedScopelet.java
@@ -1,6 +1,6 @@
/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
*
- * Copyright © 2023–2025 microBean™.
+ * Copyright © 2023–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
@@ -168,8 +168,8 @@ private final Supplier supplier(final Object id,
@SuppressWarnings("unchecked")
final Instance newInstance =
new Instance(factory == this ? (I)this : factory.create(creation),
- factory::destroy, // Destructor
- (Destruction)creation);
+ factory::destroy, // Destructor
+ (Destruction)creation);
// Put the created instance into our instance map. There will not be a pre-existing instance.
final Object previous = this.instances.put(id, newInstance);
diff --git a/src/main/java/org/microbean/scopelet/NoneScopelet.java b/src/main/java/org/microbean/scopelet/NoneScopelet.java
index 850b6ca..d447eda 100644
--- a/src/main/java/org/microbean/scopelet/NoneScopelet.java
+++ b/src/main/java/org/microbean/scopelet/NoneScopelet.java
@@ -1,6 +1,6 @@
/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
*
- * Copyright © 2023–2025 microBean™.
+ * Copyright © 2023–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
@@ -15,7 +15,6 @@
import java.lang.System.Logger;
-import java.lang.constant.ClassDesc;
import java.lang.constant.Constable;
import java.lang.constant.ConstantDesc;
import java.lang.constant.DynamicConstantDesc;
@@ -90,7 +89,7 @@ public NoneScopelet() {
* @exception InactiveScopeletException if this {@link Scopelet} {@linkplain #active() is not active}
*
* @exception ClassCastException if destruction is called for, {@code creation} is non-{@code null}, and {@code
- * creation} does not implement {@link org.microbean.bean.Destruction}, a requirement of its contract
+ * creation} does not implement {@link Destruction}, a requirement of its contract
*
* @see DestructorRegistry
*
@@ -122,7 +121,7 @@ public I instance(final Object ignoredBeanId, final Factory factory, fina
@Override // Constable
public Optional extends ConstantDesc> describeConstable() {
- return Optional.of(DynamicConstantDesc.of(BSM_INVOKE, ofConstructor(ClassDesc.of(this.getClass().getName()))));
+ return Optional.of(DynamicConstantDesc.of(BSM_INVOKE, ofConstructor(this.getClass().describeConstable().orElseThrow())));
}
}
diff --git a/src/main/java/org/microbean/scopelet/Qualifiers.java b/src/main/java/org/microbean/scopelet/Qualifiers.java
new file mode 100644
index 0000000..9a6ce09
--- /dev/null
+++ b/src/main/java/org/microbean/scopelet/Qualifiers.java
@@ -0,0 +1,176 @@
+/* -*- 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.scopelet;
+
+import java.lang.constant.Constable;
+import java.lang.constant.ConstantDesc;
+import java.lang.constant.DynamicConstantDesc;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.Name;
+import javax.lang.model.element.VariableElement;
+
+import org.microbean.construct.Domain;
+
+import org.microbean.construct.element.SyntheticAnnotationMirror;
+import org.microbean.construct.element.SyntheticAnnotationTypeElement;
+import org.microbean.construct.element.SyntheticAnnotationValue;
+
+import static java.lang.constant.ConstantDescs.BSM_INVOKE;
+
+import static java.lang.constant.MethodHandleDesc.ofConstructor;
+
+import static java.util.Objects.requireNonNull;
+
+import static javax.lang.model.element.ElementKind.ENUM_CONSTANT;
+
+import static org.microbean.construct.element.AnnotationMirrors.sameAnnotation;
+
+/**
+ * A utility class for working with qualifiers.
+ *
+ * @author Laird Nelson
+ */
+public class Qualifiers implements Constable {
+
+ private final org.microbean.assign.Qualifiers baseQualifiers;
+
+ private final AnnotationMirror primordialMetaQualifier;
+
+ private final List primordialMetaQualifiers;
+
+ /**
+ * Creates a new {@link Qualifiers}.
+ *
+ * @param domain a non-{@code null} {@link Domain}
+ *
+ * @param baseQualifiers a non-{@code null} {@link org.microbean.assign.Qualifiers}
+ *
+ * @see #Qualifiers(Domain, org.microbean.assign.Qualifiers, AnnotationMirror)
+ */
+ public Qualifiers(final Domain domain,
+ final org.microbean.assign.Qualifiers baseQualifiers) {
+ this(domain, baseQualifiers, null);
+ }
+
+ /**
+ * Creates a new {@link Qualifiers}.
+ *
+ * @param domain a {@link Domain}; may be {@code null} in which case {@code primordialMetaQualifier} must not be
+ * {@code null}
+ *
+ * @param baseQualifiers a non-{@code null} {@link org.microbean.assign.Qualifiers}
+ *
+ * @param primordialMetaQualifier an {@link AnnotationMirror} identifying the primordial meta-qualifier;
+ * may be {@code null} in which case {@code domain} must be non-{@code null}
+ *
+ * @exception NullPointerException if {@code baseQualifiers} is {@code null}, or if {@code domain} is {@code null} in
+ * certain circumstances, or if {@code primordialMetaQualifier} is {@code null} in certain circumstances
+ */
+ public Qualifiers(final Domain domain,
+ final org.microbean.assign.Qualifiers baseQualifiers,
+ final AnnotationMirror primordialMetaQualifier) {
+ super();
+ this.baseQualifiers = requireNonNull(baseQualifiers, "baseQualifiers");
+ if (primordialMetaQualifier == 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
+ final List savs = new ArrayList<>(4);
+ for (final Element e : domain.typeElement("java.lang.annotation.ElementType").getEnclosedElements()) {
+ if (e.getKind() == ENUM_CONSTANT && e instanceof VariableElement ve) {
+ final Name n = e.getSimpleName();
+ if (n.contentEquals("TYPE") || n.contentEquals("METHOD") || n.contentEquals("FIELD") || n.contentEquals("PARAMETER")) {
+ savs.add(new SyntheticAnnotationValue(ve));
+ }
+ }
+ }
+ final AnnotationMirror targetAnnotation =
+ new SyntheticAnnotationMirror(domain.typeElement("java.lang.annotation.Target"), Map.of("value", savs));
+ final List metaAnnotations =
+ List.of(baseQualifiers.metaQualifier(),
+ as.get(1), // @Retention
+ targetAnnotation, // @Target
+ as.get(0)); // @Documented
+ this.primordialMetaQualifier =
+ primordialMetaQualifier == null ?
+ new SyntheticAnnotationMirror(new SyntheticAnnotationTypeElement(metaAnnotations, "Primordial")) :
+ primordialMetaQualifier;
+ } else {
+ this.primordialMetaQualifier = primordialMetaQualifier;
+ }
+ this.primordialMetaQualifiers = List.of(this.primordialMetaQualifier);
+ }
+
+ @Override // Constable
+ public Optional extends ConstantDesc> describeConstable() {
+ return this.baseQualifiers instanceof Constable ? ((Constable)this.baseQualifiers).describeConstable() : Optional.empty()
+ .flatMap(baseQualifiersDesc -> this.primordialMetaQualifier instanceof Constable c ? c.describeConstable() : Optional.empty()
+ .map(primordialQualifierDesc -> DynamicConstantDesc.of(BSM_INVOKE,
+ ofConstructor(this.getClass().describeConstable().orElseThrow(),
+ org.microbean.assign.Qualifiers.class.describeConstable().orElseThrow(),
+ AnnotationMirror.class.describeConstable().orElseThrow()),
+ baseQualifiersDesc,
+ primordialQualifierDesc)));
+ }
+
+ /**
+ * Returns the non-{@code null}, determinate {@link AnnotationMirror} representing the primordial
+ * meta-qualifier.
+ *
+ * @return the non-{@code null}, determinate {@link AnnotationMirror} representing the primordial
+ * meta-qualifier
+ */
+ public final AnnotationMirror primordialMetaQualifier() {
+ return this.primordialMetaQualifier;
+ }
+
+ /**
+ * Returns {@code true} if and only if the supplied {@link AnnotationMirror} is {@linkplain
+ * org.microbean.assign.Qualifiers#sameAnnotation(AnnotationMirror, AnnotationMirror) the same} as the {@linkplain
+ * #primordialMetaQualifier() primordial meta-qualifier}.
+ *
+ * @param a a non-{@code null} {@link AnnotationMirror}
+ *
+ * @return {@code true} if and only if the supplied {@link AnnotationMirror} is {@linkplain
+ * org.microbean.assign.Qualifiers#sameAnnotation(AnnotationMirror, AnnotationMirror) the same} as the {@linkplain
+ * #primordialMetaQualifier() primordial meta-qualifier}
+ *
+ * @exception NullPointerException if {@code a} is {@code null}
+ *
+ * @see #primordialMetaQualifier()
+ *
+ * @see org.microbean.assign.Qualifiers#sameAnnotation(AnnotationMirror, AnnotationMirror)
+ */
+ public final boolean primordialMetaQualifier(final AnnotationMirror a) {
+ return this.baseQualifiers.sameAnnotation(this.primordialMetaQualifier, a);
+ }
+
+ /**
+ * Returns a non-{@code null}, determinate, immutable {@link List} housing only the {@linkplain
+ * #primordialMetaQualifier() primordial meta-qualifier}.
+ *
+ * @return a non-{@code null}, determinate, immutable {@link List} housing only the {@linkplain
+ * #primordialMetaQualifier() primordial meta-qualifier}
+ */
+ public final List primordialMetaQualifiers() {
+ return this.primordialMetaQualifiers;
+ }
+
+}
diff --git a/src/main/java/org/microbean/scopelet/ScopedInstances.java b/src/main/java/org/microbean/scopelet/ScopedInstances.java
index 05e2a04..43c5653 100644
--- a/src/main/java/org/microbean/scopelet/ScopedInstances.java
+++ b/src/main/java/org/microbean/scopelet/ScopedInstances.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
@@ -26,29 +26,36 @@
import java.util.function.BooleanSupplier;
import java.util.function.Supplier;
+import javax.lang.model.AnnotatedConstruct;
+
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+
import javax.lang.model.type.TypeMirror;
-import org.microbean.assign.AttributedType;
+import org.microbean.assign.Annotated;
import org.microbean.assign.Selectable;
-import org.microbean.attributes.Attributed;
-import org.microbean.attributes.Attributes;
-import org.microbean.attributes.BooleanValue;
-
import org.microbean.bean.AmbiguousResolutionException;
import org.microbean.bean.Bean;
import org.microbean.bean.Creation;
import org.microbean.bean.Factory;
import org.microbean.bean.Id;
-import org.microbean.bean.Qualifiers;
import org.microbean.bean.ReferencesSelector;
import org.microbean.construct.Domain;
+import org.microbean.construct.element.SyntheticAnnotationMirror;
+import org.microbean.construct.element.SyntheticAnnotationTypeElement;
+
+import org.microbean.construct.type.UniversalType;
+
import org.microbean.reference.Instances;
import static java.util.Objects.requireNonNull;
+import static org.microbean.construct.element.AnnotationMirrors.sameAnnotation;
+
/**
* An {@link Instances} implementation that is based on scopes.
*
@@ -61,26 +68,24 @@
public class ScopedInstances implements Instances {
- /*
- * Static fields.
- */
-
-
- // Note: deliberately not a scope or qualifier
- private static final Attributes CONSIDER_ACTIVENESS = Attributes.of("ConsiderActiveness");
-
-
/*
* Instance fields.
*/
+
+ private final Domain domain;
+
+ private final org.microbean.bean.Qualifiers bq;
- private final Qualifiers qualifiers;
+ private final Qualifiers sq;
private final Scopes scopes;
private final TypeMirror scopeletType;
+ // Deliberately not a scope or a qualifier
+ private final AnnotationMirror considerActiveness;
+
/*
* Constructors.
@@ -90,19 +95,35 @@ public class ScopedInstances implements Instances {
/**
* Creates a new {@link ScopedInstances}.
*
- * @param domain a {@link Domain}; must not be {@code null}
+ * @param domain a non-{@code null} {@link Domain}
+ *
+ * @param bq a non-{@code null} {@link org.microbean.bean.Qualifiers}
*
- * @param qualifiers a {@link Qualifiers}; must not be {@code null}
+ * @param sq a non-{@code null} {@link Qualifiers}
*
- * @param scopes a {@link Scopes}; must not be {@code null}
+ * @param scopes a non-{@code null} {@link Scopes}
+ *
+ * @param considerActiveness an {@link AnnotationMirror} used to signal that activeness should be taken
+ * into consideration during typesafe resolution; may be {@code null}
*
* @exception NullPointerException if any argument is {@code null}
*/
- public ScopedInstances(final Domain domain, final Qualifiers qualifiers, final Scopes scopes) {
+ public ScopedInstances(final Domain domain,
+ final org.microbean.bean.Qualifiers bq,
+ final Qualifiers sq,
+ final Scopes scopes,
+ final AnnotationMirror considerActiveness) {
super();
- this.qualifiers = requireNonNull(qualifiers, "qualifiers");
+ this.domain = domain;
+ this.scopeletType =
+ domain.declaredType(null, domain.typeElement(Scopelet.class.getCanonicalName()), domain.wildcardType());
this.scopes = requireNonNull(scopes, "scopes");
- this.scopeletType = scopeletType(domain);
+ this.bq = bq;
+ this.sq = requireNonNull(sq, "sq");
+ this.considerActiveness =
+ considerActiveness == null ?
+ new SyntheticAnnotationMirror(new SyntheticAnnotationTypeElement("ConsiderActiveness")) :
+ considerActiveness;
}
@@ -122,18 +143,58 @@ public ScopedInstances(final Domain domain, final Qualifiers qualifiers, final S
*/
@Override // Instances
public boolean proxiable(final Id id) {
- if (!id.types().proxiable()) {
- return false;
+ return id.types().proxiable() && this.findNormalScope(id) != null;
+ }
+
+ /**
+ * Returns a {@link Selectable Selectable<AnnotatedConstruct, Bean<?>>} that properly considers the fact
+ * that a {@link Scopelet} may be {@linkplain Scopelet#active() active or inactive} at any point for any reason.
+ *
+ * @param selectable a {@link Selectable} that will be used for all {@link AnnotatedConstruct}s other than {@link
+ * Scopelet} types being sought for the purpose of instantiating or acquiring contextual instances; must not be
+ * {@code null}
+ *
+ * @return a non-{@code null} {@link Selectable}
+ *
+ * @exception NullPointerException if any argument is {@code null}
+ */
+ public final Selectable, Bean>> selectableOf(final Selectable super Annotated extends AnnotatedConstruct>, Bean>> selectable) {
+ requireNonNull(selectable, "selectable");
+ final Selectable, Bean>> scopeletSelectable = aac -> {
+ Bean> activeScopeletBean = null;
+ for (final Bean> b : selectable.select(aac)) {
+ if (((Scopelet>)b.factory()).active()) {
+ if (activeScopeletBean == null) {
+ activeScopeletBean = b;
+ } else {
+ throw new TooManyActiveScopeletsException("scopelet1: " + activeScopeletBean + "; scopelet: " + b);
+ }
+ }
+ }
+ return activeScopeletBean == null ? List.of() : List.of(activeScopeletBean);
+ };
+ return aac ->
+ this.domain.sameType(this.scopeletType, type(aac)) && this.considerActiveness(aac.annotations()) ?
+ // A ScopedInstances is requesting a Scopelet for the purposes of instantiating something else. Use the
+ // scopeletSelectable.
+ scopeletSelectable.select(aac) :
+ // A ScopedInstances is requesting something "normal". Use the unadorned supplied Selectable.
+ selectable.select(aac);
+ }
+
+ private static final TypeMirror type(final Annotated extends AnnotatedConstruct> a) {
+ final AnnotatedConstruct ac = a.annotated();
+ if (ac instanceof TypeMirror t) {
+ return t;
}
- final Attributes scopeId = this.findScope(id);
- return scopeId != null && this.scopes.normal(scopeId);
+ return ((Element)ac).asType();
}
@Override // Instances
public final Supplier extends I> supplier(final Bean bean, final Creation request) {
final Id id = bean.id();
- final Attributes scopeId = this.findScope(id);
- // In this implementation, all Ids must have scopes.
+ final AnnotationMirror scopeId = this.findScope(id);
+ // In this implementation, all ids must have scopes.
if (scopeId == null) {
throw new IllegalStateException();
}
@@ -149,80 +210,64 @@ public final Supplier extends I> supplier(final Bean bean, final Creati
assert scopelet == factory : "scopelet != factory: " + scopelet + " != " + factory;
return factory::singleton;
}
- final AttributedType st = this.scopeletAttributedType(scopeId);
+ final Annotated ast = this.annotatedScopeletType(scopeId);
// Get the Scopelet and have it provide the instance
- return () -> request.>reference(st).instance(id, factory, request); // assumes Scopelet inactivity is handled
+ return () -> {
+ return request.>reference(ast)
+ .instance(id, factory, request); // assumes Scopelet inactivity is handled
+ };
}
- /*
- * Returns {@code true} if and only if the supplied {@link Attributes} is deemed to be an identifier of a
- * scope.
- *
- * @param a an {@link Attributes}; must not be {@code null}
- *
- * @return {@code true} if and only if the supplied {@link Attributes} is deemed to be an identifier of a scope
- *
- * @exception NullPointerException if {@code a} is {@code null}
- *
- * @see Scopes#scope(Attributes)
- *
- * @deprecated Use {@link Scopes#scope(Attributes)} instead.
- */
- // @Deprecated(forRemoval = true)
- // protected boolean isScopeId(final Attributes a) {
- // return this.scopes.scope(a);
- // }
-
/**
- * Returns {@code true} if and only if the supplied {@link Collection} of {@link Attributes} is deemed to designate
- * something as primordial.
+ * Returns {@code true} if and only if the supplied {@link Collection} of {@link AnnotationMirror}s is deemed to
+ * designate something as primordial.
*
* The default implementation of this method returns {@code true} if and only if the supplied {@link Collection}
- * {@linkplain Collection#contains(Object) contains} the {@linkplain
- * org.microbean.bean.Qualifiers#primordialQualifier() primordial qualifier}.
+ * contains an {@link AnnotationMirror} that is the {@linkplain
+ * org.microbean.construct.element.AnnotationMirrors#sameAnnotation(AnnotationMirror, AnnotationMirror) same
+ * annotation} as the {@link org.microbean.bean.Qualifiers#primordialQualifier() primordial qualifier}.
*
- * @param c a {@link Collection}; must not be {@code null}
+ * @param c a {@link Collection} of {@link AnnotationMirror}s; must not be {@code null}
*
- * @return {@code true} if and only if the supplied {@link Collection} of {@link Attributes} is deemed to designate
- * something as primordial
+ * @return {@code true} if and only if the supplied {@link Collection} of {@link AnnotationMirror}s is deemed to
+ * designate something as primordial
*
* @exception NullPointerException if {@code c} is {@code null}
*
* @see Qualifiers#primordialQualifier()
*/
- protected boolean primordial(final Collection extends Attributes> c) {
- return c.contains(this.qualifiers.primordialQualifier());
+ private final boolean primordial(final Collection extends AnnotationMirror> c) {
+ for (final AnnotationMirror a : c) {
+ if (this.sq.primordialMetaQualifier(a)) {
+ return true;
+ }
+ }
+ return false;
}
- /**
- * Finds and returns the nearest scope identifier in the forest represented by the supplied {@link
- * Attributes}.
- *
- * @param c a {@link Collection} of {@link Attributes}; must not be {@code null}
- *
- * @return the nearest scope identifier in the forest represented by the supplied {@link
- * Attributes}, or {@code null}
- *
- * @exception NullPointerException if {@code c} is {@code null}
- *
- * @see Scopes#findScope(Collection)
- *
- * @deprecated Please use {@link Scopes#findScope(Collection)} instead.
- */
- @Deprecated(forRemoval = true)
- final Attributes findScopeId(final Collection extends Attributes> c) {
- return this.scopes.findScope(c);
+ private final boolean primordial(final AnnotationMirror a) {
+ return this.primordial(a.getAnnotationType().asElement().getAnnotationMirrors());
}
- private final Attributes findScope(final Id id) {
+ private final AnnotationMirror findNormalScope(final Id id) {
+ AnnotationMirror scopeId = null;
+ for (final AnnotationMirror a : id.annotations()) {
+ if (this.bq.anyQualifier(a)) {
+ scopeId = this.scopes.findNormalScope(a.getAnnotationType().asElement().getAnnotationMirrors());
+ break;
+ }
+ }
+ return scopeId;
+ }
+
+ private final AnnotationMirror findScope(final Id id) {
// Looks for an Any qualifier, which every bean must possess, and then looks on *it* for the scope. This allows us
// to "tunnel" scopes (which are Qualifiers in this implementation) without disrupting typesafe resolution, since
- // meta-attributes are not part of an Attributes' equality computation.
- final Object anyQualifier = this.qualifiers.anyQualifier();
- Attributes scopeId = null;
- for (final Attributes a : id.attributes()) {
- if (a.equals(anyQualifier)) {
- scopeId = this.scopes.findScope(a.attributes());
+ // meta-annotations are not part of an AnnotationMirror's equality computation.
+ AnnotationMirror scopeId = null;
+ for (final AnnotationMirror a : id.annotations()) {
+ if (this.bq.anyQualifier(a)) {
+ scopeId = this.scopes.findScope(a.getAnnotationType().asElement().getAnnotationMirrors());
break;
}
}
@@ -232,122 +277,19 @@ private final Attributes findScope(final Id id) {
return scopeId;
}
- private final boolean primordial(final Attributed a) {
- return this.primordial(a.attributes());
+ private final Annotated annotatedScopeletType(final AnnotationMirror scopeId) {
+ return Annotated.of(new UniversalType(List.of(scopeId, this.considerActiveness),
+ this.scopeletType,
+ this.domain));
}
- private final AttributedType scopeletAttributedType(final Attributes scopeId) {
- return AttributedType.of(this.scopeletType, scopeId, CONSIDER_ACTIVENESS);
- }
-
-
- /*
- * Static methods.
- */
-
-
- /**
- * Returns a {@link Selectable Selectable<AttributedType, Bean<?>>} that properly considers the fact that
- * a {@link Scopelet} may be {@linkplain Scopelet#active() active or inactive} at any point for any reason.
- *
- * @param domain a {@link Domain}; must not be {@code null}
- *
- * @param selectable a {@link Selectable} that will be used for all {@link AttributedType}s other than {@link
- * Scopelet} types being sought for the purpose of instantiating or acquiring contextual instances; must not be {@code
- * null}
- *
- * @return a non-{@code null} {@link Selectable}
- *
- * @exception NullPointerException if any argument is {@code null}
- */
- public static final Selectable> selectableOf(final Domain domain,
- final Selectable> selectable) {
- Objects.requireNonNull(selectable, "selectable");
- final Selectable> scopeletSelectable = c -> {
- Bean> activeScopeletBean = null;
- for (final Bean> b : selectable.select(c)) {
- if (((Scopelet>)b.factory()).active()) {
- if (activeScopeletBean == null) {
- activeScopeletBean = b;
- } else {
- throw new TooManyActiveScopeletsException("scopelet1: " + activeScopeletBean + "; scopelet2: " + b);
- }
- }
- }
- return activeScopeletBean == null ? List.of() : List.of(activeScopeletBean);
- };
- final TypeMirror scopeletType = scopeletType(domain);
- return c ->
- domain.sameType(scopeletType, c.type()) && c.attributes().contains(CONSIDER_ACTIVENESS) ?
- // A ScopedInstances is requesting a Scopelet for the purposes of instantiating something else. Use the
- // scopeletSelectable.
- scopeletSelectable.select(c) :
- // A ScopedInstances is requesting something "normal". Use the unadorned supplied Selectable.
- selectable.select(c);
- }
-
- // Invoked by method reference only
- // (Actually, not used?)
- @Deprecated(forRemoval = true)
- private static final Bean> handleInactiveScopelets(final Collection extends Bean>> beans, final AttributedType attributedType) {
- if (beans.size() < 2) { // 2 because we're disambiguating
- throw new IllegalArgumentException("beans: " + beans);
- }
- Bean> b2 = null;
- Scopelet> s2 = null;
- final Iterator extends Bean>> i = beans.iterator();
- while (i.hasNext()) {
- final Bean> b1 = i.next();
- if (b1.factory() instanceof Scopelet> s1) {
- if (s2 == null) {
- assert b2 == null;
- if (i.hasNext()) {
- b2 = i.next();
- if (b2.factory() instanceof Scopelet> s) {
- s2 = s;
- } else {
- s2 = null;
- b2 = null;
- break;
- }
- } else {
- s2 = s1;
- b2 = b1;
- break;
- }
- }
- assert b2 != null;
- if (s2.active()) {
- if (s1.active()) {
- throw new TooManyActiveScopeletsException("scopelet1: " + s1 + "; scopelet2: " + s2);
- }
- // drop s1; keep s2
- } else if (s1.active()) {
- // drop s2; keep s1
- s2 = s1;
- b2 = b1;
- } else {
- // both are inactive; drop 'em both and keep going
- s2 = null;
- b2 = null;
- }
- } else {
- s2 = null;
- b2 = null;
- break;
+ private final boolean considerActiveness(final Collection extends AnnotationMirror> c) {
+ for (final AnnotationMirror a : c) {
+ if (sameAnnotation(this.considerActiveness, a)) {
+ return true;
}
}
- if (s2 == null) {
- throw new AmbiguousResolutionException(attributedType,
- beans,
- "TODO: this message needs to be better; can't resolve these alternates: " + beans);
- }
- assert b2 != null;
- return b2;
- }
-
- private static final TypeMirror scopeletType(final Domain domain) {
- return domain.declaredType(null, domain.typeElement(Scopelet.class.getCanonicalName()), domain.wildcardType());
+ return false;
}
}
diff --git a/src/main/java/org/microbean/scopelet/Scopelet.java b/src/main/java/org/microbean/scopelet/Scopelet.java
index 28741ba..1f83667 100644
--- a/src/main/java/org/microbean/scopelet/Scopelet.java
+++ b/src/main/java/org/microbean/scopelet/Scopelet.java
@@ -1,6 +1,6 @@
/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
*
- * Copyright © 2023–2025 microBean™.
+ * Copyright © 2023–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,23 +19,11 @@
import java.util.List;
import java.util.Map;
-import org.microbean.assign.AttributedType;
-import org.microbean.assign.Qualifiers;
-
-import org.microbean.attributes.Attributes;
-import org.microbean.attributes.BooleanValue;
-import org.microbean.attributes.Value;
-
-import org.microbean.bean.Bean;
import org.microbean.bean.Creation;
import org.microbean.bean.Destruction;
import org.microbean.bean.Factory;
import org.microbean.bean.ReferencesSelector;
-import org.microbean.construct.Domain;
-
-import org.microbean.event.Events;
-
import static java.lang.invoke.MethodHandles.lookup;
/**
@@ -200,11 +188,11 @@ public final S singleton() {
/**
* Returns {@code true} when invoked to indicate that {@link Scopelet} implementations {@linkplain
- * Factory#destroy(Object, org.microbean.bean.Destruction) destroy} what they {@linkplain #create(Creation) create}.
+ * Factory#destroy(Object, Destruction) destroy} what they {@linkplain #create(Creation) create}.
*
* @return {@code true} when invoked
*
- * @see Factory#destroy(Object, org.microbean.bean.Destruction)
+ * @see Factory#destroy(Object, Destruction)
*
* @see #create(Creation)
*/
@@ -252,11 +240,11 @@ public boolean active() {
* @exception InactiveScopeletException if this {@link Scopelet} {@linkplain #active() is not active}
*
* @exception ClassCastException if destruction is called for, {@code creation} is non-{@code null}, and {@code
- * creation} does not implement {@link org.microbean.bean.Destruction}, a requirement of its contract
+ * creation} does not implement {@link Destruction}, a requirement of its contract
*
* @see Creation
*
- * @see org.microbean.bean.Destruction
+ * @see Destruction
*
* @see Factory#destroys()
*/
diff --git a/src/main/java/org/microbean/scopelet/Scopes.java b/src/main/java/org/microbean/scopelet/Scopes.java
index a28fc2b..cb52e69 100644
--- a/src/main/java/org/microbean/scopelet/Scopes.java
+++ b/src/main/java/org/microbean/scopelet/Scopes.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,20 +13,34 @@
*/
package org.microbean.scopelet;
-import java.util.ArrayDeque;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
-import java.util.Queue;
-import org.microbean.bean.Qualifiers;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.Name;
+import javax.lang.model.element.VariableElement;
-import org.microbean.attributes.Attributes;
-import org.microbean.attributes.BooleanValue;
-import org.microbean.attributes.Value;
+// import org.microbean.bean.Qualifiers;
+
+import org.microbean.construct.Domain;
+
+import org.microbean.construct.element.SyntheticAnnotationMirror;
+import org.microbean.construct.element.SyntheticAnnotationTypeElement;
+import org.microbean.construct.element.SyntheticAnnotationValue;
import static java.util.Objects.requireNonNull;
+import static java.util.function.Predicate.not;
+
+import static javax.lang.model.element.ElementKind.ENUM_CONSTANT;
+import static javax.lang.model.element.ElementKind.METHOD;
+
+import static org.microbean.construct.element.AnnotationMirrors.sameAnnotation;
+import static org.microbean.construct.element.AnnotationMirrors.streamBreadthFirst;
+
/**
* A utility class for working with scopes and their identifiers.
*
@@ -34,185 +48,366 @@
*/
public class Scopes { // deliberately not final
- private static final Map> NORMAL = Map.of("normal", BooleanValue.of(true));
-
- private static final Map> PSEUDO = Map.of("normal", BooleanValue.of(false));
+ private final AnnotationMirror metaNormalScope;
+
+ private final AnnotationMirror metaPseudoScope;
- private static final Attributes SCOPE = Attributes.of("Scope");
+ private final AnnotationMirror noneScope;
- private final Attributes NONE;
+ private final AnnotationMirror singletonScope;
- private final Attributes SINGLETON;
-
- private final Qualifiers qualifiers;
+ private final org.microbean.assign.Qualifiers aq;
+
+ private final Qualifiers sq;
/**
* Creates a new {@link Scopes}.
*
- * @param qualifiers a {@link Qualifiers}; must not be {@code null}
+ * @param d a non-{@code null} {@link Domain}
+ *
+ * @param aq a non-{@code null} {@link org.microbean.assign.Qualifiers}
+ *
+ * @param sq a non-{@code null} {@link Qualifiers}
*
- * @exception NullPointerException if {@code qualifiers} is {@code null}
+ * @exception NullPointerException if any argument is {@code null}
+ *
+ * @see #Scopes(Domain, org.microbean.assign.Qualifiers, Qualifiers, AnnotationMirror, AnnotationMirror, AnnotationMirror,
+ * AnnotationMirror)
*/
- public Scopes(final Qualifiers qualifiers) {
- super();
- this.qualifiers = requireNonNull(qualifiers, "qualifiers");
- this.SINGLETON =
- Attributes.of("Singleton",
- PSEUDO,
- Map.of(),
- Map.of("Singleton",
- List.of(qualifiers.qualifier(),
- SCOPE,
- qualifiers.primordialQualifier())));
- this.NONE =
- Attributes.of("None",
- PSEUDO,
- Map.of(),
- Map.of("None",
- List.of(qualifiers.qualifier(),
- SCOPE,
- SINGLETON)));
+ public Scopes(final Domain d, final org.microbean.assign.Qualifiers aq, final Qualifiers sq) {
+ this(d, aq, sq, null, null, null, null);
}
/**
- * Returns the first {@link Attributes} that is present in the supplied {@link Collection} of {@link Attributes}, and
- * their {@linkplain Attributes#attributes() meta-attributes}, for which an invocation of the {@link
- * #scope(Attributes)} method returns {@code true}, or {@code null}, if no such {@link Attributes} exists.
+ * Creates a new {@link Scopes}.
*
- * The search is conducted in a breadth-first manner.
+ * @param aq a non-{@code null} {@link org.microbean.assign.Qualifiers}
*
- * @param c a {@link Collection} of {@link Attributes}; must not be {@code null}
+ * @param sq a non-{@code null} {@link Qualifiers}
*
- * @return the first {@link Attributes} that is present in the supplied {@link Collection} of {@link Attributes}, and
- * their {@linkplain Attributes#attributes() meta-attributes}, for which an invocation of the {@link
- * #scope(Attributes)} method returns {@code true}, or {@code null}, if no such {@link Attributes} exists
+ * @param metaNormalScope a non-{@code null} {@link AnnotationMirror} designating another {@link AnnotationMirror} as
+ * identifying a normal scope
*
- * @exception NullPointerException if {@code c} is {@code null}
+ * @param metaPseudoScope a non-{@code null} {@link AnnotationMirror} designating another {@link AnnotationMirror} as
+ * identifying a pseudo scope
+ *
+ * @param singletonScope a non-{@code null} {@link AnnotationMirror} identifying the singleton scope
+ *
+ * @param noneScope a non-{@code null} {@link AnnotationMirror} identifying the none scope
+ *
+ * @exception NullPointerException if any argument is {@code null}
+ *
+ * @see #Scopes(Domain, org.mcirobean.assign.Qualifiers, Qualifires, AnnotationMirror, AnnotationMirror,
+ * AnnotationMirror, AnnotationMirror)
*/
- public Attributes findScope(final Collection extends Attributes> c) {
- if (c.isEmpty()) {
- return null;
- }
- // Breadth first on purpose. Scope Attributes closer to the Attributes they attribute win over Scope Attributes
- // further away.
- final Queue q = new ArrayDeque<>(c);
- while (!q.isEmpty()) {
- final Attributes a = q.poll();
- if (this.scope(a)) {
- return a;
+ public Scopes(final org.microbean.assign.Qualifiers aq,
+ final Qualifiers sq,
+ final AnnotationMirror metaNormalScope,
+ final AnnotationMirror metaPseudoScope,
+ final AnnotationMirror singletonScope,
+ final AnnotationMirror noneScope) {
+ this(null,
+ aq,
+ sq,
+ requireNonNull(metaNormalScope, "metaNormalScope"),
+ requireNonNull(metaPseudoScope, "metaPseudoScope"),
+ requireNonNull(singletonScope, "singletonScope"),
+ requireNonNull(noneScope, "noneScope"));
+ }
+
+ /**
+ * Creates a new {@link Scopes}.
+ *
+ * @param d a {@link Domain}; if {@code null}, then {@code metaNormalScope}, {@code metaPseudoScope}, {@code
+ * singletonScope}, and {@code noneScope} must be non-{@code null}
+ *
+ * @param aq a non-{@code null} {@link org.microbean.assign.Qualifiers}
+ *
+ * @param sq a non-{@code null} {@link Qualifiers}
+ *
+ * @param metaNormalScope an {@link AnnotationMirror} designating another {@link AnnotationMirror} as identifying a
+ * normal scope; may be {@code null} in which case {@code d} must be non-{@code null}
+ *
+ * @param metaPseudoScope an {@link AnnotationMirror} designating another {@link AnnotationMirror} as identifying a
+ * pseudo scope; may be {@code null} in which case {@code d} must be non-{@code null}
+ *
+ * @param singletonScope an {@link AnnotationMirror} identifying the singleton scope; may be {@code null}
+ * in which case {@code d} must be non-{@code null}
+ *
+ * @param noneScope an {@link AnnotationMirror} identifying the none scope; may be {@code null} in which
+ * case {@code d} must be non-{@code null}
+ *
+ * @exception NullPointerException if {@code aq} or {@code sq} is {@code null}, or if {@code d} is {@code null} in
+ * certain situations
+ */
+ public Scopes(final Domain d,
+ final org.microbean.assign.Qualifiers aq,
+ final Qualifiers sq,
+ final AnnotationMirror metaNormalScope,
+ final AnnotationMirror metaPseudoScope,
+ final AnnotationMirror singletonScope,
+ final AnnotationMirror noneScope) {
+ super();
+ this.aq = requireNonNull(aq, "aq");
+ this.sq = requireNonNull(sq, "sq");
+ if (metaNormalScope == null || metaPseudoScope == null || singletonScope == null || noneScope == null) {
+
+ final List extends AnnotationMirror> as = d.typeElement("java.lang.annotation.Documented").getAnnotationMirrors();
+ assert as.size() == 3; // @Documented, @Retention, @Target, in that order, all annotated in turn with each other
+
+ final AnnotationMirror documentedAnnotation = as.get(0);
+ final AnnotationMirror retentionAnnotation = as.get(1);
+ final List documentedRetentionTarget =
+ List.of(documentedAnnotation, // @Documented
+ retentionAnnotation, // @Retention(TargetType.RUNTIME) (happens fortuitously to be RUNTIME)
+ as.get(2)); // @Target(ANNOTATION_TYPE)
+
+ this.metaNormalScope =
+ metaNormalScope == null ?
+ new SyntheticAnnotationMirror(new SyntheticAnnotationTypeElement(documentedRetentionTarget, "NormalScope")) :
+ metaNormalScope;
+
+ this.metaPseudoScope =
+ metaPseudoScope == null ?
+ new SyntheticAnnotationMirror(new SyntheticAnnotationTypeElement(documentedRetentionTarget, "Scope")) :
+ metaPseudoScope;
+
+ final List savs = new ArrayList<>(4);
+ for (final Element e : d.typeElement("java.lang.annotation.ElementType").getEnclosedElements()) {
+ if (e.getKind() == ENUM_CONSTANT && e instanceof VariableElement ve) {
+ final Name n = e.getSimpleName();
+ if (n.contentEquals("TYPE") || n.contentEquals("METHOD") || n.contentEquals("FIELD") || n.contentEquals("PARAMETER")) {
+ savs.add(new SyntheticAnnotationValue(ve));
+ }
+ }
}
- q.addAll(a.attributes());
+ final List metaAnnotations =
+ List.of(aq.metaQualifier(),
+ this.metaPseudoScope,
+ sq.primordialMetaQualifier(),
+ retentionAnnotation,
+ new SyntheticAnnotationMirror(d.typeElement("java.lang.annotation.Target"), Map.of("value", savs)),
+ documentedAnnotation);
+
+ this.singletonScope =
+ singletonScope == null ?
+ new SyntheticAnnotationMirror(new SyntheticAnnotationTypeElement(metaAnnotations, "Singleton")) :
+ singletonScope;
+
+ this.noneScope =
+ noneScope == null ?
+ new SyntheticAnnotationMirror(new SyntheticAnnotationTypeElement(metaAnnotations, "None")) :
+ noneScope;
+ } else {
+ this.metaNormalScope = metaNormalScope;
+ this.metaPseudoScope = metaPseudoScope;
+ this.singletonScope = singletonScope;
+ this.noneScope = noneScope;
}
- return null;
}
/**
- * Returns a non-{@code null}, determinate {@link Attributes} representing the identifier for the none
- * scope.
+ * Returns the first {@link AnnotationMirror} that is present in the supplied {@link Collection} of {@link
+ * AnnotationMirror}s, and their {@linkplain Element#getAnnotationMirrors() meta-annotations}, for which an invocation
+ * of the {@link #normalScope(AnnotationMirror)} method returns {@code true}, or {@code null}, if no such {@link
+ * AnnotationMirror} exists.
*
- * @return a non-{@code null}, determinate {@link Attributes} representing the identifier for the none
- * scope
+ * The search is conducted in a breadth-first manner.
+ *
+ * @param c a {@link Collection} of {@link AnnotationMirror}s; must not be {@code null}
+ *
+ * @return the first {@link AnnotationMirror} that is present in the supplied {@link Collection} of {@link
+ * AnnotationMirror}s, and their {@linkplain Element#getAnnotationMirrors() meta-annotations}, for which an invocation
+ * of the {@link #normalScope(AnnotationMirror)} method returns {@code true}, or {@code null}, if no such {@link
+ * AnnotationMirror} exists
+ *
+ * @exception NullPointerException if {@code c} is {@code null}
*/
- public Attributes none() {
- return NONE;
+ public AnnotationMirror findNormalScope(final Collection extends AnnotationMirror> c) {
+ return c.isEmpty() ? null : streamBreadthFirst(c)
+ .dropWhile(not(this::normalScope))
+ .findFirst()
+ .orElse(null);
+ }
+
+ /**
+ * Returns the first {@link AnnotationMirror} that is present in the supplied {@link Collection} of {@link
+ * AnnotationMirror}s, and their {@linkplain Element#getAnnotationMirrors() meta-annotations}, for which an invocation
+ * of the {@link #scope(AnnotationMirror)} method returns {@code true}, or {@code null}, if no such {@link
+ * AnnotationMirror} exists.
+ *
+ * The search is conducted in a breadth-first manner.
+ *
+ * @param c a {@link Collection} of {@link AnnotationMirror}s; must not be {@code null}
+ *
+ * @return the first {@link AnnotationMirror} that is present in the supplied {@link Collection} of {@link
+ * AnnotationMirror}s, and their {@linkplain Element#getAnnotationMirrors() meta-annotations}, for which an invocation
+ * of the {@link #scope(AnnotationMirror)} method returns {@code true}, or {@code null}, if no such {@link
+ * AnnotationMirror} exists
+ *
+ * @exception NullPointerException if {@code c} is {@code null}
+ */
+ public AnnotationMirror findScope(final Collection extends AnnotationMirror> c) {
+ return c.isEmpty() ? null : streamBreadthFirst(c)
+ .dropWhile(not(this::scope))
+ .findFirst()
+ .orElse(null);
}
/**
- * Returns a non-{@code null}, determinate, immutable {@link Map} of {@linkplain Map#size() size} {@code 1} that can
- * be used with a {@linkplain #scope() scope} to designate it as a normal scope.
+ * Returns a non-{@code null}, determinate {@link AnnotationMirror} representing the identifier for the none
+ * scope.
*
- * @return a non-{@code null}, determinate, immutable {@link Map} of {@linkplain Map#size() size} {@code 1} that can
- * be used with a {@linkplain #scope() scope} to designate it as a normal scope
+ * @return a non-{@code null}, determinate {@link AnnotationMirror} representing the identifier for the none
+ * scope
*/
- public Map> normal() {
- return NORMAL;
+ public final AnnotationMirror noneScope() {
+ return this.noneScope;
}
/**
- * Returns {@code true} if the supplied {@link Attributes} has elements that might be used to indicate that it is a
- * normal scope.
+ * Returns {@code true} if the supplied {@link AnnotationMirror} has elements that might be used to indicate that it
+ * is a normal scope.
*
- * @param a an {@link Attributes}; must not be {@code null}
+ * @param a a non-{@code null}, determinate {@link AnnotationMirror}
*
- * @return {@code true} if the supplied {@link Attributes} has elements that might be used to indicate that it is a
- * normal scope
+ * @return {@code true} if the supplied {@link AnnotationMirror} has elements that might be used to indicate that it
+ * is a normal scope
*
* @exception NullPointerException if {@code a} is {@code null}
*
- * @see #scope(Attributes)
- *
- * @see #normal()
+ * @see #scope(AnnotationMirror)
*/
- public boolean normal(final Attributes a) {
- final BooleanValue v = a.value(this.normal().keySet().iterator().next());
- return v != null && v.value();
+ public boolean normal(final AnnotationMirror a) {
+ for (final Element ee : a.getAnnotationType().asElement().getEnclosedElements()) {
+ if (ee.getKind() == METHOD && ee.getSimpleName().contentEquals("normal")) {
+ return true;
+ }
+ }
+ return false;
}
/**
- * Returns a non-{@code null}, determinate, immutable {@link Map} of {@linkplain Map#size() size} {@code 1} that can
- * be used with a {@linkplain #scope() scope} to designate it as a pseudo scope.
+ * Returns the non-{@code null}, determinate {@link AnnotationMirror} that can be used to designate other {@link
+ * AnnotationMirror}s as normal scopes.
+ *
+ * @return the non-{@code null}, determinate {@link AnnotationMirror} that can be used to designate other {@link
+ * AnnotationMirror}s as normal scopes
+ */
+ public final AnnotationMirror metaNormalScope() {
+ return this.metaNormalScope;
+ }
+
+ /**
+ * Returns the non-{@code null}, determinate {@link AnnotationMirror} that can be used to designate other {@link
+ * AnnotationMirror}s as scopes.
*
- * @return a non-{@code null}, determinate, immutable {@link Map} of {@linkplain Map#size() size} {@code 1} that can
- * be used with a {@linkplain #scope() scope} to designate it as a pseudo scope
+ * @return the non-{@code null}, determinate {@link AnnotationMirror} that can be used to designate other {@link
+ * AnnotationMirror}s as scopes
*/
- public Map> pseudo() {
- return PSEUDO;
+ public final AnnotationMirror metaPseudoScope() {
+ return this.metaPseudoScope;
}
/**
- * Returns the non-{@code null}, determinate {@link Attributes} that can be used to designate other {@link Attributes}
- * as a scope.
+ * Returns {@code true} if and only if the supplied {@link AnnotationMirror} is a normal scope identifier.
+ *
+ * @param a an {@link AnnotationMirror}; must not be {@code null}
*
- * @return the non-{@code null}, determinate {@link Attributes} that can be used to designate other {@link Attributes}
- * as a scope
+ * @return {@code true} if and only if the supplied {@link AnnotationMirror} is a normal scope identifier
+ *
+ * @exception NullPointerException if {@code a} is {@code null}
*/
- public Attributes scope() {
- return SCOPE;
+ public boolean normalScope(final AnnotationMirror a) {
+ // In this implementation, all scopes, normal or otherwise, must also be qualifiers.
+ boolean metaNormalScopeFound = false;
+ boolean metaQualifierFound = false;
+ for (final AnnotationMirror ma : a.getAnnotationType().asElement().getAnnotationMirrors()) {
+ if (!metaNormalScopeFound) {
+ if (sameAnnotation(ma, this.metaNormalScope())) {
+ metaNormalScopeFound = true;
+ continue;
+ }
+ }
+
+ if (!metaQualifierFound) {
+ if (sameAnnotation(ma, this.aq.metaQualifier())) {
+ metaQualifierFound = true;
+ continue;
+ }
+ }
+
+ if (sameAnnotation(ma, this.metaPseudoScope())) {
+ // Can't be a pseudo- and a normal scope at the same time.
+ return false;
+ }
+ }
+ return metaNormalScopeFound && metaQualifierFound;
}
/**
- * Returns {@code true} if and only if the supplied {@link Attributes} is a scope identifier.
+ * Returns {@code true} if and only if the supplied {@link AnnotationMirror} is a pseudo-scope identifier.
*
- * @param a an {@link Attributes}; must not be {@code null}
+ * @param a an {@link AnnotationMirror}; must not be {@code null}
*
- * @return {@code true} if and only if the supplied {@link Attributes} is a scope identifier
+ * @return {@code true} if and only if the supplied {@link AnnotationMirror} is a pseudo-scope identifier
*
* @exception NullPointerException if {@code a} is {@code null}
*/
- public boolean scope(final Attributes a) {
- boolean scopeFound = false;
- boolean qualifierFound = false;
- for (final Attributes ma : a.attributes()) {
- if (scopeFound) {
- if (!qualifierFound && ma.equals(this.qualifiers.qualifier())) {
- qualifierFound = true;
- break;
+ public boolean pseudoScope(final AnnotationMirror a) {
+ // In this implementation, all scopes, pseudo- or otherwise, must also be qualifiers.
+ // TODO: should also check that normalScope annotation is not present
+ boolean metaPseudoScopeFound = false;
+ boolean metaQualifierFound = false;
+ for (final AnnotationMirror ma : a.getAnnotationType().asElement().getAnnotationMirrors()) {
+ if (!metaPseudoScopeFound) {
+ if (sameAnnotation(ma, this.metaPseudoScope())) {
+ metaPseudoScopeFound = true;
+ continue;
}
- } else if (qualifierFound) {
- if (ma.equals(this.scope())) {
- scopeFound = true;
- break;
+ }
+
+ if (!metaQualifierFound) {
+ if (sameAnnotation(ma, this.aq.metaQualifier())) {
+ metaQualifierFound = true;
+ continue;
}
- } else if (ma.equals(this.scope())) {
- // a is annotated with @Scope
- scopeFound = true;
- } else if (ma.equals(this.qualifiers.qualifier())) {
- // a is annotated with @Qualifier
- qualifierFound = true;
+ }
+
+ if (sameAnnotation(ma, this.metaNormalScope())) {
+ // Can't be a pseudo- and a normal scope at the same time.
+ return false;
}
}
- return scopeFound && qualifierFound;
+ return metaPseudoScopeFound && metaQualifierFound;
+ }
+
+ /**
+ * Returns {@code true} if and only if the supplied {@link AnnotationMirror} is a scope identifier (either
+ * a {@linkplain #pseudoScope(AnnotationMirror) pseudo-scope identifier} or a {@linkplain
+ * #normalScope(AnnotationMirror) normal scope identifier}.
+ *
+ * @param a an {@link AnnotationMirror}; must not be {@code null}
+ *
+ * @return {@code true} if and only if the supplied {@link AnnotationMirror} is a scope identifier (either
+ * a {@linkplain #pseudoScope(AnnotationMirror) pseudo-scope identifier} or a {@linkplain
+ * #normalScope(AnnotationMirror) normal scope identifier}
+ *
+ * @exception NullPointerException if {@code a} is {@code null}
+ */
+ public final boolean scope(final AnnotationMirror a) {
+ return this.pseudoScope(a) || this.normalScope(a);
}
/**
- * Returns a non-{@code null}, determinate {@link Attributes} representing the identifier for the singleton
- * pseudo-scope.
+ * Returns a non-{@code null}, determinate {@link AnnotationMirror} representing the identifier for the
+ * singleton pseudo-scope.
*
- * @return a non-{@code null}, determinate {@link Attributes} representing the identifier for the singleton
- * pseudo-scope
+ * @return a non-{@code null}, determinate {@link AnnotationMirror} representing the identifier for the
+ * singleton pseudo-scope
*/
- public Attributes singleton() {
- return SINGLETON;
+ public final AnnotationMirror singletonScope() {
+ return this.singletonScope;
}
}
diff --git a/src/main/java/org/microbean/scopelet/SingletonScopelet.java b/src/main/java/org/microbean/scopelet/SingletonScopelet.java
index 0d0bf16..12a385e 100644
--- a/src/main/java/org/microbean/scopelet/SingletonScopelet.java
+++ b/src/main/java/org/microbean/scopelet/SingletonScopelet.java
@@ -1,6 +1,6 @@
/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
*
- * Copyright © 2023–2025 microBean™.
+ * Copyright © 2023–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,7 +13,6 @@
*/
package org.microbean.scopelet;
-import java.lang.constant.ClassDesc;
import java.lang.constant.Constable;
import java.lang.constant.ConstantDesc;
import java.lang.constant.DynamicConstantDesc;
@@ -40,7 +39,7 @@ public SingletonScopelet() {
@Override // Constable
public Optional extends ConstantDesc> describeConstable() {
- return Optional.of(DynamicConstantDesc.of(BSM_INVOKE, ofConstructor(ClassDesc.of(this.getClass().getName()))));
+ return Optional.of(DynamicConstantDesc.of(BSM_INVOKE, ofConstructor(this.getClass().describeConstable().orElseThrow())));
}
}
diff --git a/src/main/java/org/microbean/scopelet/TooManyActiveScopeletsException.java b/src/main/java/org/microbean/scopelet/TooManyActiveScopeletsException.java
index fb67240..79f5c7f 100644
--- a/src/main/java/org/microbean/scopelet/TooManyActiveScopeletsException.java
+++ b/src/main/java/org/microbean/scopelet/TooManyActiveScopeletsException.java
@@ -1,6 +1,6 @@
/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
*
- * Copyright © 2023 microBean™.
+ * Copyright © 2023–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
diff --git a/src/test/java/org/microbean/scopelet/TestScopes.java b/src/test/java/org/microbean/scopelet/TestScopes.java
deleted file mode 100644
index 546f2e9..0000000
--- a/src/test/java/org/microbean/scopelet/TestScopes.java
+++ /dev/null
@@ -1,86 +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.scopelet;
-
-import java.util.List;
-import java.util.Map;
-
-import org.junit.jupiter.api.BeforeAll;
-import org.junit.jupiter.api.Test;
-
-import org.microbean.attributes.Attributes;
-import org.microbean.attributes.BooleanValue;
-
-import org.microbean.bean.Qualifiers;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertNull;
-import static org.junit.jupiter.api.Assertions.assertSame;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-final class TestScopes {
-
- private static Qualifiers qualifiers;
-
- private static Scopes scopes;
-
- private TestScopes() {
- super();
- }
-
- @BeforeAll
- static final void setup() {
- qualifiers = new Qualifiers();
- scopes = new Scopes(qualifiers);
- }
-
- @Test
- final void testIsScope() {
- // The Scope Attributes is not, itself, a scope identifier.
- assertFalse(scopes.scope(scopes.scope()));
-
- // scopes.singleton() is a scope identifier because it is attributed with the Scope Attributes.
- assertTrue(scopes.scope(scopes.singleton()));
-
- // scopes.none() is a scope identifier for the same reason.
- assertTrue(scopes.scope(scopes.none()));
-
- // Even though here the Any Attributes is attributed with scopes.none(), that doesn't make it a scope identifier (it
- // is not attributed with scopes.scope(); scopes.scope() is not transitive).
- final Attributes a = Attributes.of("Any", scopes.none());
- assertFalse(scopes.scope(a));
-
- // An instance of scopes.none() without any additional attributes is equal to the canonical scopes.none() which does
- // have additional attributes. This is because by design only names and values are considered in equality
- // comparisons.
- assertEquals(Attributes.of("None", Map.of("normal", BooleanValue.of(false)), Map.of(), Map.of()), scopes.findScope(a.attributes()));
- }
-
- @Test
- final void testNoneIsNotPrimordial() {
- assertSame(scopes.none(), scopes.findScope(List.of(scopes.none())));
- assertFalse(scopes.none().attributes().contains(qualifiers.primordialQualifier()));
- assertSame(scopes.singleton(), scopes.findScope(scopes.none().attributes()));
- }
-
- @Test
- final void testSingletonIsPrimordial() {
- assertSame(scopes.singleton(), scopes.findScope(List.of(scopes.singleton())));
- assertTrue(scopes.singleton().attributes().contains(qualifiers.primordialQualifier()));
- assertNull(scopes.findScope((scopes.singleton().attributes())));
-
- }
-
-}