From 0a4fd104d6dd59f783e2621fe30ba37041d5a176 Mon Sep 17 00:00:00 2001 From: Laird Nelson Date: Fri, 21 Mar 2025 15:32:13 -0700 Subject: [PATCH] Significantly overhauls Scopelet contract Signed-off-by: Laird Nelson --- NOTES.md | 7 + README.md | 4 +- pom.xml | 6 +- .../microbean/scopelet/MapBackedScopelet.java | 11 +- .../org/microbean/scopelet/NoneScopelet.java | 16 +- .../microbean/scopelet/ScopedInstances.java | 303 ++++++++++++------ .../java/org/microbean/scopelet/Scopelet.java | 120 +------ .../microbean/scopelet/SingletonScopelet.java | 19 +- .../scopelet/TestScopedInstances.java | 14 +- 9 files changed, 254 insertions(+), 246 deletions(-) create mode 100644 NOTES.md diff --git a/NOTES.md b/NOTES.md new file mode 100644 index 0000000..14ce3fa --- /dev/null +++ b/NOTES.md @@ -0,0 +1,7 @@ +# Scopes + +A Scope used as a scope and not used as a qualifier goes on the Any qualifier. + +A Scope used as a qualifier goes directly on a Scopelet. + +A Scope used as a "parent" scope goes on another Scope. diff --git a/README.md b/README.md index 52937f3..98d78e9 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ changes, regardless of project version and without notice.** # Requirements -microBean™ Scopelet requires a Java runtime of version 21 or higher. +microBean™ Scopelet requires a Java runtime of version 16 or higher. # Installation @@ -27,7 +27,7 @@ dependency: org.microbean microbean-scopelet - 0.0.6 + 0.0.7 ``` diff --git a/pom.xml b/pom.xml index 18bb957..e3d940b 100644 --- a/pom.xml +++ b/pom.xml @@ -73,7 +73,7 @@ ${project.basedir}/src/test/java/logging.properties - 23 + 16 true true @@ -147,7 +147,7 @@ org.microbean microbean-bean - 0.0.15 + 0.0.16 @@ -159,7 +159,7 @@ org.microbean microbean-reference - 0.0.1 + 0.0.2 diff --git a/src/main/java/org/microbean/scopelet/MapBackedScopelet.java b/src/main/java/org/microbean/scopelet/MapBackedScopelet.java index 917c040..be7adf8 100644 --- a/src/main/java/org/microbean/scopelet/MapBackedScopelet.java +++ b/src/main/java/org/microbean/scopelet/MapBackedScopelet.java @@ -45,15 +45,10 @@ public abstract class MapBackedScopelet> extends /** * Creates a new {@link MapBackedScopelet}. * - * @param scopeId an {@link Attributes} identifying the scope this {@link MapBackedScopelet} serves; must not be - * {@code null} - * - * @exception NullPointerException if {@code scopeId} is {@code null} - * - * @see Scopelet#Scopelet(Attributes) + * @see Scopelet#Scopelet() */ - protected MapBackedScopelet(final Attributes scopeId) { - super(scopeId); + protected MapBackedScopelet() { + super(); this.creationLocks = new ConcurrentHashMap<>(); this.instances = new ConcurrentHashMap<>(); } diff --git a/src/main/java/org/microbean/scopelet/NoneScopelet.java b/src/main/java/org/microbean/scopelet/NoneScopelet.java index 6cd964d..842b274 100644 --- a/src/main/java/org/microbean/scopelet/NoneScopelet.java +++ b/src/main/java/org/microbean/scopelet/NoneScopelet.java @@ -19,15 +19,12 @@ import java.lang.constant.DynamicConstantDesc; import java.lang.constant.MethodHandleDesc; -import java.util.List; import java.util.Objects; import java.util.Optional; import org.microbean.bean.AutoCloseableRegistry; -import org.microbean.bean.BeanTypeList; import org.microbean.bean.DisposableReference; import org.microbean.bean.Factory; -import org.microbean.bean.Id; import org.microbean.bean.Request; import org.microbean.construct.Domain; @@ -56,21 +53,10 @@ public class NoneScopelet extends Scopelet implements Constable { * @exception NullPointerException if {@code domain} is {@code null} */ public NoneScopelet(final Domain domain) { - super(NONE_ID); // the scope we implement + super(); this.domain = Objects.requireNonNull(domain, "domain"); } - @Override // Scopelet - public Id id() { - return - new Id(BeanTypeList.of(this.domain, - List.of(this.domain.declaredType(NoneScopelet.class.getCanonicalName()), - this.domain.declaredType(null, - this.domain.typeElement(Scopelet.class.getCanonicalName()), - this.domain.declaredType(NoneScopelet.class.getCanonicalName())))), - List.of(NONE_ID, anyQualifier())); // qualifiers - } - // All parameters are nullable. // Non-final to permit subclasses to, e.g., add logging. @Override // Scopelet diff --git a/src/main/java/org/microbean/scopelet/ScopedInstances.java b/src/main/java/org/microbean/scopelet/ScopedInstances.java index 8a7080f..f280303 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 © 2024–2025 microBean™. + * 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 @@ -19,33 +19,32 @@ import java.util.List; import java.util.Queue; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; - +import java.util.function.BiFunction; import java.util.function.Supplier; import javax.lang.model.type.TypeMirror; +import org.microbean.attributes.Attributed; import org.microbean.attributes.Attributes; import org.microbean.attributes.BooleanValue; import org.microbean.bean.AmbiguousReductionException; import org.microbean.bean.AttributedType; import org.microbean.bean.Bean; -import org.microbean.bean.BeanReduction; -import org.microbean.bean.BeanTypeList; import org.microbean.bean.Factory; import org.microbean.bean.Id; +import org.microbean.bean.RankedReducer; import org.microbean.bean.Reducer; import org.microbean.bean.Reducible; import org.microbean.bean.Request; import org.microbean.bean.Selectable; -import org.microbean.bean.UnsatisfiedReductionException; +import org.microbean.bean.Reducer; import org.microbean.construct.Domain; import org.microbean.reference.Instances; +import static org.microbean.assign.Qualifiers.anyQualifier; import static org.microbean.assign.Qualifiers.primordialQualifier; import static org.microbean.assign.Qualifiers.qualifier; @@ -54,13 +53,9 @@ * * @author Laird Nelson */ -public final class ScopedInstances implements Instances { - - private final Domain domain; +public class ScopedInstances implements Instances { - private final ConcurrentMap> scopeletBeanCache; - - private final Reducible> scopeletReducible; + private static final Attributes FOR_INSTANTIATION = Attributes.of("ForInstantiation"); private final TypeMirror scopeletType; @@ -69,30 +64,54 @@ public final class ScopedInstances implements Instances { * * @param domain a {@link Domain}; must not be {@code null} * - * @param selectable a {@link Selectable} used to select {@link Scopelet}s; must not be {@code null} - * - * @param reducer a {@link Reducer} used to disambiguate {@linkplain Scopelet#active() active} {@link Scopelet}s; must - * not be {@code null} - * - * @exception NullPointerException if any argument is {@code null} + * @exception NullPointerException if {@code domain} is {@code null} */ - public ScopedInstances(final Domain domain, - final Selectable> selectable, - final Reducer> reducer) { + public ScopedInstances(final Domain domain) { super(); - final ConcurrentMap> scopeletBeanCache = new ConcurrentHashMap<>(); - this.scopeletBeanCache = scopeletBeanCache; - this.scopeletReducible = - Reducible.>ofCaching(selectable, reducer, ScopedInstances::handleInactiveScopelets, scopeletBeanCache::computeIfAbsent); - this.scopeletType = domain.declaredType(null, domain.typeElement(Scopelet.class.getCanonicalName()), domain.wildcardType()); - this.domain = domain; + this.scopeletType = scopeletType(domain); } - @Override // Instances (AutoCloseable) - public final void close() { - this.scopeletBeanCache.clear(); + + /* + * Instance methods. + */ + + + /** + * Calls the {@link #findScopeId(Collection)} method with the result of an invocation of the {@link + * Attributes#attributes()} method on the supplied {@link Attributes} and returns the result. + * + * @param a an {@link Attributes}; normally itself a scope; must not be {@code null} + * + * @return the first {@link Attributes} found in the supplied {@link Attributes}' {@linkplain Attributes#attributes() + * attributes} that is a scope, or {@code null} + * + * @exception NullPointerException if {@code a} is {@code null} + * + * @see #findScopeId(Collection) + */ + private final Attributes findScopeId(final Attributes a) { + return this.findScopeId(a.attributes()); } + private final Attributes findScopeId(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 = anyQualifier(); + Attributes scopeId = null; + for (final Attributes a : id.attributes()) { + if (a.equals(anyQualifier)) { + scopeId = this.findScopeId(a); + break; + } + } + if (scopeId == null) { + throw new IllegalArgumentException("id: " + id); + } + return scopeId; + } + /** * Finds and returns the nearest scope identifier in the forest represented by the supplied {@link * Attributes}. @@ -157,76 +176,95 @@ private final boolean normal(final Attributes a) { return v != null && v.value(); } - @Override // Instances - public boolean proxiable(final Request r) { - if (r.primordial()) { - return false; - } - final Id id = r.beanReduction().bean().id(); - if (!id.types().proxiable()) { - return false; - } - final Attributes scopeId = this.findScopeId(id.attributes()); - return scopeId != null && this.normal(scopeId); + private final boolean primordial(final Attributed a) { + return this.primordial(a.attributes()); } - - @Override // Instances (InstanceRemover) - public final boolean remove(final Id id) { - return id != null && this.scopelet(this.scopeletBeanReduction(this.findScopeId(id.attributes()))).remove(id); + + /** + * Returns {@code true} if and only if the supplied {@link Collection} of {@link Attributes} 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.assign.Qualifiers#primordialQualifier() primordial qualifier}.

+ * + * @param c a {@link Collection}; 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 + * + * @exception NullPointerException if {@code c} is {@code null} + */ + protected boolean primordial(final Collection c) { + return c.contains(primordialQualifier()); } - // Called by #remove(Id) - private final Scopelet scopelet(final BeanReduction> scopeletBeanReduction) { - return this.scopelet(scopeletBeanReduction, null); + /** + * Returns {@code true} if and only if the supplied {@link Request} is deemed to be primordial. + * + *

A {@link Request} is normally primordial if it is {@code null} or if an invocation of its {@link + * Request#primordial() primordial()} method returns {@code true}.

+ * + * @param r a {@link Request}; may be {@code null} in which case {@code true} will be returned + * + * @return {@code true} if and only if the supplied {@link Request} is deemed to be primordial + */ + protected boolean primordial(final Request r) { + return r == null || r.primordial(); } - // Yes, you need the BeanReduction parameter, not just Bean, because you're going to call r.newChild(BeanReduction) - private final Scopelet scopelet(final BeanReduction> scopeletBeanReduction, final Request r) { - final Bean> scopeletBean = scopeletBeanReduction.bean(); - final BeanReduction> governingScopeletBeanReduction = - this.scopeletBeanReduction(this.findScopeId(scopeletBean.id().attributes())); - if (scopeletBean.equals(governingScopeletBeanReduction.bean())) { - final Factory> scopeletFactory = scopeletBean.factory(); - final Scopelet scopelet = scopeletFactory.singleton(); - return - scopelet == null ? - scopeletFactory.create(r == null ? null : r.child(scopeletBeanReduction)) : - scopelet; + /** + * Returns {@code true} if and only if the supplied {@link Id} is proxiable. + * + * @param id an {@link Id}; must not be {@code null} + * + * @return {@code true} if and only if the supplied {@link Id} is proxiable + * + * @exception NullPointerException if {@code id} is {@code null} + */ + protected boolean proxiable(final Id id) { + if (!id.types().proxiable()) { + return false; } - return scopelet(governingScopeletBeanReduction, r); // recurse + final Attributes scopeId = this.findScopeId(id); + return scopeId != null && this.normal(scopeId); } - private final BeanReduction> scopeletBeanReduction(final Attributes scopeId) { - final AttributedType scopeletAttributedType = new AttributedType(this.scopeletType, List.of(scopeId)); - @SuppressWarnings("unchecked") - final Bean> scopeletBean = (Bean>)this.scopeletReducible.reduce(scopeletAttributedType); - if (scopeletBean == null) { - throw new UnsatisfiedReductionException(scopeletAttributedType, null, null); - } - return new BeanReduction<>(scopeletAttributedType, scopeletBean); + @Override // Instances + public final boolean proxiable(final Request r) { + return !this.primordial(r) && this.proxiable(r.beanReduction().bean().id()); } @Override // Instances - @SuppressWarnings("unchecked") public final Supplier supplier(final Request request) { - if (request.primordial()) { + if (this.primordial(request)) { + // The supplied Request is a request for a Request, i.e. it's primordial, so return a Supplier that simply returns + // the request. + @SuppressWarnings("unchecked") final I instance = (I)request; return () -> instance; } final Bean bean = request.beanReduction().bean(); final Factory factory = bean.factory(); - final I singleton = factory.singleton(); - if (singleton == null) { - final Id id = bean.id(); - final BeanReduction> scopeletBeanReduction = this.scopeletBeanReduction(this.findScopeId(id.attributes())); - if (bean.equals(scopeletBeanReduction.bean())) { + final Id id = bean.id(); + final Attributes scopeId = this.findScopeId(id); + // In this implementation, all Ids must have scopes. + if (scopeId == null) { + throw new IllegalStateException(); + } + if (factory instanceof Scopelet && this.primordial(scopeId)) { + // This is a request for, e.g., the Singleton Scopelet, which backs the primordial (notional) singleton scope. + // Scopelets are always their own factories. The Scopelet implementing the primordial scope (normally Singleton) + // is not made or stored by any other Scopelet. + final I scopelet = factory.singleton(); + if (scopelet == null) { return () -> factory.create(request); } - final Scopelet scopelet = this.scopelet(scopeletBeanReduction, request); - assert scopelet != null; - return () -> scopelet.instance(id, factory, request); + assert scopelet == factory : "scopelet != factory: " + scopelet + " != " + factory; + return factory::singleton; } - return factory::singleton; + final AttributedType t = AttributedType.of(this.scopeletType, findScopeId(scopeId), FOR_INSTANTIATION); + return () -> request.>reference(t).instance(id, factory, request); // assumes a specific kind of reduction; see #reducible } @@ -236,8 +274,7 @@ public final Supplier supplier(final Request request) { // Invoked by method reference only - private static final Bean handleInactiveScopelets(final Collection> beans, - final AttributedType attributedType) { + static final Bean handleInactiveScopelets(final Collection> beans, final AttributedType attributedType) { if (beans.size() < 2) { // 2 because we're disambiguating throw new IllegalArgumentException("beans: " + beans); } @@ -265,7 +302,7 @@ private static final Bean handleInactiveScopelets(final Collection handleInactiveScopelets(final Collection handleInactiveScopelets(final Collection> reducible(final Domain domain, + final Selectable> selectable) { + return reducible(domain, selectable, RankedReducer.of()); + } + + /** + * Returns a {@link Reducible} suitable for use with {@link Scopelet}s. + * + * @param domain a {@link Domain} (that is normally shared among other cooperating components); must not be {@code null} + * + * @param selectable a {@link Selectable}; must not be {@code null} + * + * @param reducer a {@link Reducer}; must not be {@code null} + * + * @return a non-{@code null} {@link Reducible} + * + * @exception NullPointerException if any argument is {@code null} + * + * @see #reducible(Domain, Selectable, Reducer, BiFunction) + * + * @see Reducer#fail(List, Object) + */ + public static final Reducible> reducible(final Domain domain, + final Selectable> selectable, + final Reducer> reducer) { + return reducible(domain, selectable, reducer, Reducer::fail); + } + + /** + * Returns a {@link Reducible} suitable for use with {@link Scopelet}s. + * + * @param domain a {@link Domain} (that is normally shared among other cooperating components); must not be {@code null} + * + * @param selectable a {@link Selectable}; must not be {@code null} + * + * @param reducer a {@link Reducer}; must not be {@code null} + * + * @param failureHandler a {@link BiFunction} serving as the supplied {@link Reducer}'s failure handler; + * must not be {@code null} + * + * @return a non-{@code null} {@link Reducible} + * + * @exception NullPointerException if any argument is {@code null} + */ + public static final Reducible> + reducible(final Domain domain, + final Selectable> selectable, + final Reducer> reducer, + final BiFunction>, ? super AttributedType, ? extends Bean> failureHandler) { + // Normal reductions are cached. + final Reducible> cachingReducible = Reducible.ofCaching(selectable, reducer, failureHandler); + // Reductions of scopelets can't be cached because a Scopelet may be active or inactive at any point for any reason. + final Reducible> scopeletReducible = + Reducible.>of(selectable, reducer, ScopedInstances::handleInactiveScopelets); + final TypeMirror scopeletType = scopeletType(domain); + return c -> + (domain.sameType(scopeletType, c.type()) && c.attributes().contains(FOR_INSTANTIATION) ? + // A ScopedInstances is requesting a Scopelet for the purposes of instantiating something else. Use the + // scopeletReducible. + scopeletReducible : + // A ScopedInstances is requesting something "normal". Use the cachingReducible. + cachingReducible) + .reduce(c); + } + } diff --git a/src/main/java/org/microbean/scopelet/Scopelet.java b/src/main/java/org/microbean/scopelet/Scopelet.java index e9b54c1..8a8015a 100644 --- a/src/main/java/org/microbean/scopelet/Scopelet.java +++ b/src/main/java/org/microbean/scopelet/Scopelet.java @@ -19,24 +19,20 @@ import java.util.List; import java.util.Map; -import java.util.Objects; import org.microbean.attributes.Attributes; import org.microbean.attributes.BooleanValue; import org.microbean.attributes.Value; import org.microbean.bean.Bean; -import org.microbean.bean.Id; import org.microbean.bean.Factory; import org.microbean.bean.Request; -import org.microbean.attributes.Attributes; - import static org.microbean.assign.Qualifiers.primordialQualifier; import static org.microbean.assign.Qualifiers.qualifier; /** - * A manager of object lifespans identified by a scope. + * A manager of object lifespans on behalf of one or more notional scopes. * * @param the {@link Scopelet} subtype extending this class * @@ -64,12 +60,6 @@ public abstract class Scopelet> implements AutoCloseable, } } - - // - // Experimental; relocating/dismissing microbean-scope - // - - /** * An {@link Attributes} identifying the scope designator. */ @@ -79,7 +69,6 @@ public abstract class Scopelet> implements AutoCloseable, private static final Map> pseudoScope = Map.of("normal", BooleanValue.of(false)); - /** * An {@link Attributes} identifying the (well-known) singleton pseudo-scope. * @@ -104,11 +93,6 @@ public abstract class Scopelet> implements AutoCloseable, Attributes.of("None", pseudoScope, Map.of(), Map.of("None", List.of(qualifier(), SCOPE, SINGLETON_ID))); - // - // End experimental - // - - /* * Instance fields. */ @@ -118,8 +102,6 @@ public abstract class Scopelet> implements AutoCloseable, private volatile boolean closed; - private final Attributes scopeId; - /* * Constructors. @@ -128,14 +110,9 @@ public abstract class Scopelet> implements AutoCloseable, /** * Creates a new {@link Scopelet}. - * - * @param scopeId an {@link Attributes} identifying the scope being implemented; must not be {@code null} - * - * @exception NullPointerException if {@code scopeId} is {@code null} */ - protected Scopelet(final Attributes scopeId) { + protected Scopelet() { super(); - this.scopeId = Objects.requireNonNull(scopeId, "scopeId"); } @@ -144,32 +121,6 @@ protected Scopelet(final Attributes scopeId) { */ - /** - * Returns an {@link Id} representing this {@link Scopelet}. - * - *

Implementations of this method must return determinate values.

- * - * @return an {@link Id} representing this {@link Scopelet}; never {@code null} - * - * @see #bean() - */ - public abstract Id id(); - - /** - * A convenience method that eturns a {@link Bean} for this {@link Scopelet}. - * - *

This {@link Scopelet} will be used as the {@link Bean}'s {@linkplain Bean#factory() associated - * Factory}. This {@link Scopelet}'s {@link #id() Id} will be used as the {@link Bean}'s {@linkplain - * Bean#id() identifier}.

- * - * @return a {@link Bean} for this {@link Scopelet}; never {@code null} - * - * @see #id() - */ - public final Bean bean() { - return new Bean<>(this.id(), this); - } - /** * Creates this {@link Scopelet} by simply returning it. * @@ -214,61 +165,12 @@ public final boolean destroys() { return true; } - /** - * Returns a hashcode for this {@link Scopelet}. - * - * @return a hashcode for this {@link Scopelet} - */ - @Override // Object - public int hashCode() { - int hashCode = 17; - hashCode = 31 * hashCode + this.id().hashCode(); - hashCode = 31 * hashCode + this.scopeId().hashCode(); - return hashCode; - } - - /** - * Returns {@code true} if and only if the supplied {@link Object} is not {@code null}, has the same class as this - * {@link Scopelet}, has an {@link #id() Id} {@linkplain Id#equals(Object) equal to} that of this {@link Scopelet}, - * and a {@linkplain #scopeId() scope identifier} {@linkplain Attributes#equals(Object) equal to} that of this - * {@link Scopelet}. - * - * @param other the {@link Object} to test; may be {@code null} - * - * @return {@code true} if and only if the supplied {@link Object} is not {@code null}, has the same class as this - * {@link Scopelet}, has an {@link #id() Id} {@linkplain Id#equals(Object) equal to} that of this {@link Scopelet}, - * and a {@linkplain #scopeId() scope identifier} {@linkplain Attributes#equals(Object) equal to} that of this - * {@link Scopelet} - */ - @Override // Object - public boolean equals(final Object other) { - if (other == this) { - return true; - } else if (other != null && other.getClass().equals(this.getClass())) { - final Scopelet her = (Scopelet)other; - return - Objects.equals(this.id(), her.id()) && - Objects.equals(this.scopeId(), her.scopeId()); - } else { - return false; - } - } - /* * Repository-like concerns. */ - /** - * Returns the {@link Attributes} that identifies this {@link Scopelet}'s affiliated scope. - * - * @return the {@link Attributes} that identifies this {@link Scopelet}'s affiliated scope; never {@code null} - */ - public final Attributes scopeId() { - return this.scopeId; - } - /** * Returns {@code true} if and only if this {@link Scopelet} is active at the moment of the call. * @@ -305,6 +207,7 @@ public boolean active() { * * @see #instance(Object, Factory, Request) */ + // @Deprecated // This method is not actually used but would need to exist for design flaws in CDI public boolean containsId(final Object id) { return (id instanceof Request r ? this.instance(r) : this.instance(id, null, null)) != null; } @@ -359,8 +262,8 @@ public final I get(final Object id) { * @see #instance(Object, Factory, Request) */ public final I instance(final Request request) { - if (request == null) { - return this.instance(null, null, null); + if (request == null || request.primordial()) { + return this.instance(null, null, request); } final Bean bean = request.beanReduction().bean(); return this.instance(bean.id(), bean.factory(), request); @@ -391,8 +294,8 @@ public final I instance(final Request request) { * Checks to see if this {@link Scopelet} {@linkplain #active() is active} and then removes any contextual instance * stored under the supplied {@code id}, returning {@code true} if and only if removal actually took place. * - *

The default implementation of this method always returns {@code false}. Subclasses are encouraged to override - * it as appropriate.

+ *

The default implementation of this method always returns {@code false}. Subclasses are + * encouraged to override it as appropriate.

* * @param id an identifier; may be {@code null} * @@ -409,8 +312,11 @@ public boolean remove(final Object id) { } /** - * Irrevocably closes this {@link Scopelet}, and, by doing so, notionally makes it irrevocably {@linkplain #active() - * inactive}. + * Irrevocably closes this {@link Scopelet}, and, by doing so, notionally makes it irrevocably {@linkplain #closed() + * closed} and {@linkplain #active() inactive}. + * + *

Overrides of this method must call {@link Scopelet#close() super.close()} as part of their implementation or + * undefined behavior may result.

* * @see #closed() * @@ -429,6 +335,8 @@ public void close() { * * @return {@code true} if and only if at the moment of invocation this {@link Scopelet} is (irrevocably) closed (and * therefore also {@linkplain #active() not active}) + * + * @see #active() */ protected final boolean closed() { return this.closed; // volatile read diff --git a/src/main/java/org/microbean/scopelet/SingletonScopelet.java b/src/main/java/org/microbean/scopelet/SingletonScopelet.java index df1bef9..9831622 100644 --- a/src/main/java/org/microbean/scopelet/SingletonScopelet.java +++ b/src/main/java/org/microbean/scopelet/SingletonScopelet.java @@ -19,19 +19,13 @@ import java.lang.constant.DynamicConstantDesc; import java.lang.constant.MethodHandleDesc; -import java.util.List; import java.util.Objects; import java.util.Optional; -import org.microbean.bean.BeanTypeList; -import org.microbean.bean.Id; - import org.microbean.construct.Domain; import static java.lang.constant.ConstantDescs.BSM_INVOKE; -import static org.microbean.assign.Qualifiers.anyQualifier; - /** * A {@link MapBackedScopelet} implementation that caches singletons. * @@ -49,21 +43,10 @@ public class SingletonScopelet extends MapBackedScopelet impl * @exception NullPointerException if {@code domain} is {@code null} */ public SingletonScopelet(final Domain domain) { - super(SINGLETON_ID); // the scope we implement + super(); this.domain = Objects.requireNonNull(domain, "domain"); } - @Override // Scopelet - public Id id() { - return - new Id(BeanTypeList.of(this.domain, - List.of(this.domain.declaredType(SingletonScopelet.class.getCanonicalName()), - this.domain.declaredType(null, - this.domain.typeElement(Scopelet.class.getCanonicalName()), - this.domain.declaredType(SingletonScopelet.class.getCanonicalName())))), - List.of(SINGLETON_ID, anyQualifier())); // qualifiers - } - @Override // Constable public Optional describeConstable() { return (this.domain instanceof Constable c ? c.describeConstable() : Optional.empty()) diff --git a/src/test/java/org/microbean/scopelet/TestScopedInstances.java b/src/test/java/org/microbean/scopelet/TestScopedInstances.java index 2ea4130..f11b4dd 100644 --- a/src/test/java/org/microbean/scopelet/TestScopedInstances.java +++ b/src/test/java/org/microbean/scopelet/TestScopedInstances.java @@ -30,6 +30,7 @@ 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; @@ -53,7 +54,7 @@ private TestScopedInstances() { @BeforeAll static final void setup() { d = new DefaultDomain(); - i = new ScopedInstances(d, Selectable.of(), Reducer.ofFailing()); + i = new ScopedInstances(d); } @Test @@ -78,11 +79,18 @@ final void testIsScope() { assertEquals(Attributes.of("None", Map.of("normal", BooleanValue.of(false)), Map.of(), Map.of()), i.findScopeId(a.attributes())); } + @Test + final void testNoneIsNotPrimordial() { + assertSame(NONE_ID, i.findScopeId(List.of(NONE_ID))); + assertFalse(NONE_ID.attributes().contains(primordialQualifier())); + assertSame(SINGLETON_ID, i.findScopeId(NONE_ID.attributes())); + } + @Test final void testSingletonIsPrimordial() { - final Attributes singletonId = i.findScopeId(List.of(SINGLETON_ID)); - assertSame(SINGLETON_ID, singletonId); + assertSame(SINGLETON_ID, i.findScopeId(List.of(SINGLETON_ID))); assertTrue(SINGLETON_ID.attributes().contains(primordialQualifier())); + assertNull(i.findScopeId(SINGLETON_ID.attributes())); } }