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 -[![Maven Central](https://img.shields.io/maven-central/v/org.microbean/microbean-scopelet.svg?label=Maven%20Central)](https://search.maven.org/artifact/org.microbean/microbean-scopelet) +[![Maven Central](https://img.shields.io/maven-central/v/org.microbean/microbean-producer.svg?label=Maven%20Central)](https://central.sonatype.com/artifact/org.microbean/microbean-scopelet) -The microBean™ Scopelet project provides classes and interfaces assisting with implementing dependency injection scopes. +![0% AI](https://img.shields.io/badge/%F0%9F%A4%96_AI-0%25_%F0%9F%8C%BC-brightgreen) + +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 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 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 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, 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 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 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 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 c) { - return c.contains(this.qualifiers.primordialQualifier()); + private final boolean primordial(final Collection 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 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> 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> 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 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 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 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 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 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 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()))); - - } - -}