diff --git a/.github/workflows/mvn-release-prepare-perform.yaml b/.github/workflows/mvn-release-prepare-perform.yaml
index c858555..968b79f 100644
--- a/.github/workflows/mvn-release-prepare-perform.yaml
+++ b/.github/workflows/mvn-release-prepare-perform.yaml
@@ -24,20 +24,20 @@ jobs:
steps:
- id: 'checkout'
name: 'Step: Check Out Project'
- uses: 'actions/checkout@v4'
+ uses: 'actions/checkout@v6'
with:
fetch-depth: 1
persist-credentials: false
- id: 'setup-java'
name: 'Step: Set Up Java and Maven'
- uses: 'actions/setup-java@v4'
+ uses: 'actions/setup-java@v5'
with:
cache: 'maven'
distribution: 'temurin'
gpg-passphrase: 'GPG_PASSPHRASE'
gpg-private-key: '${{ secrets.GPG_PRIVATE_KEY }}'
- java-version: '23'
- mvn-toolchain-id: 'Temurin 23'
+ java-version: '25'
+ mvn-toolchain-id: 'Temurin 25'
mvn-toolchain-vendor: 'openjdk' # see ../../pom.xml
server-id: 'sonatype-oss-repository-hosting' # see https://github.com/microbean/microbean-parent/blob/master/pom.xml#L38
server-password: 'SONATYPE_OSSRH_PASSWORD'
diff --git a/.github/workflows/mvn-verify.yaml b/.github/workflows/mvn-verify.yaml
index a414901..faefc80 100644
--- a/.github/workflows/mvn-verify.yaml
+++ b/.github/workflows/mvn-verify.yaml
@@ -12,18 +12,18 @@ jobs:
steps:
- id: 'checkout'
name: 'Step: Checkout'
- uses: 'actions/checkout@v4'
+ uses: 'actions/checkout@v6'
with:
fetch-depth: 1
persist-credentials: false
- id: 'setup-java'
name: 'Step: Set Up Java and Maven'
- uses: 'actions/setup-java@v4'
+ uses: 'actions/setup-java@v5'
with:
cache: 'maven'
distribution: 'temurin'
- java-version: '23'
- mvn-toolchain-id: 'Temurin 23'
+ java-version: '25'
+ mvn-toolchain-id: 'Temurin 25'
mvn-toolchain-vendor: 'openjdk' # see ../../pom.xml
- id: 'mvn-verify'
name: 'Step: Maven Verify'
diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties
index d58dfb7..c652895 100644
--- a/.mvn/wrapper/maven-wrapper.properties
+++ b/.mvn/wrapper/maven-wrapper.properties
@@ -1,19 +1,13 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you 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
+# Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE
+# file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file
+# to you 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.
-wrapperVersion=3.3.2
+# 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.
+wrapperVersion=3.3.4
distributionType=only-script
-distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.12/apache-maven-3.9.12-bin.zip
diff --git a/README.md b/README.md
index 8ccd399..328d7b0 100644
--- a/README.md
+++ b/README.md
@@ -27,7 +27,7 @@ dependency:
org.microbeanmicrobean-scopelet
- 0.0.8
+ 0.0.9
```
diff --git a/mvnw b/mvnw
index 19529dd..bd8896b 100755
--- a/mvnw
+++ b/mvnw
@@ -19,7 +19,7 @@
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
-# Apache Maven Wrapper startup batch script, version 3.3.2
+# Apache Maven Wrapper startup batch script, version 3.3.4
#
# Optional ENV vars
# -----------------
@@ -105,14 +105,17 @@ trim() {
printf "%s" "${1}" | tr -d '[:space:]'
}
+scriptDir="$(dirname "$0")"
+scriptName="$(basename "$0")"
+
# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties
while IFS="=" read -r key value; do
case "${key-}" in
distributionUrl) distributionUrl=$(trim "${value-}") ;;
distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;;
esac
-done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties"
-[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties"
+done <"$scriptDir/.mvn/wrapper/maven-wrapper.properties"
+[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
case "${distributionUrl##*/}" in
maven-mvnd-*bin.*)
@@ -130,7 +133,7 @@ maven-mvnd-*bin.*)
distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip"
;;
maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;;
-*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;;
+*) MVN_CMD="mvn${scriptName#mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;;
esac
# apply MVNW_REPOURL and calculate MAVEN_HOME
@@ -227,7 +230,7 @@ if [ -n "${distributionSha256Sum-}" ]; then
echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
exit 1
elif command -v sha256sum >/dev/null; then
- if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then
+ if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c - >/dev/null 2>&1; then
distributionSha256Result=true
fi
elif command -v shasum >/dev/null; then
@@ -252,8 +255,41 @@ if command -v unzip >/dev/null; then
else
tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar"
fi
-printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url"
-mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME"
+
+# Find the actual extracted directory name (handles snapshots where filename != directory name)
+actualDistributionDir=""
+
+# First try the expected directory name (for regular distributions)
+if [ -d "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" ]; then
+ if [ -f "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/bin/$MVN_CMD" ]; then
+ actualDistributionDir="$distributionUrlNameMain"
+ fi
+fi
+
+# If not found, search for any directory with the Maven executable (for snapshots)
+if [ -z "$actualDistributionDir" ]; then
+ # enable globbing to iterate over items
+ set +f
+ for dir in "$TMP_DOWNLOAD_DIR"/*; do
+ if [ -d "$dir" ]; then
+ if [ -f "$dir/bin/$MVN_CMD" ]; then
+ actualDistributionDir="$(basename "$dir")"
+ break
+ fi
+ fi
+ done
+ set -f
+fi
+
+if [ -z "$actualDistributionDir" ]; then
+ verbose "Contents of $TMP_DOWNLOAD_DIR:"
+ verbose "$(ls -la "$TMP_DOWNLOAD_DIR")"
+ die "Could not find Maven distribution directory in extracted archive"
+fi
+
+verbose "Found extracted Maven distribution directory: $actualDistributionDir"
+printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$actualDistributionDir/mvnw.url"
+mv -- "$TMP_DOWNLOAD_DIR/$actualDistributionDir" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME"
clean || :
exec_maven "$@"
diff --git a/mvnw.cmd b/mvnw.cmd
index b150b91..5761d94 100644
--- a/mvnw.cmd
+++ b/mvnw.cmd
@@ -19,7 +19,7 @@
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
-@REM Apache Maven Wrapper startup batch script, version 3.3.2
+@REM Apache Maven Wrapper startup batch script, version 3.3.4
@REM
@REM Optional ENV vars
@REM MVNW_REPOURL - repo url base for downloading maven distribution
@@ -40,7 +40,7 @@
@SET __MVNW_ARG0_NAME__=
@SET MVNW_USERNAME=
@SET MVNW_PASSWORD=
-@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*)
+@IF NOT "%__MVNW_CMD__%"=="" ("%__MVNW_CMD__%" %*)
@echo Cannot start maven from wrapper >&2 && exit /b 1
@GOTO :EOF
: end batch / begin powershell #>
@@ -73,16 +73,30 @@ switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) {
# apply MVNW_REPOURL and calculate MAVEN_HOME
# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/
if ($env:MVNW_REPOURL) {
- $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" }
- $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')"
+ $MVNW_REPO_PATTERN = if ($USE_MVND -eq $False) { "/org/apache/maven/" } else { "/maven/mvnd/" }
+ $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace "^.*$MVNW_REPO_PATTERN",'')"
}
$distributionUrlName = $distributionUrl -replace '^.*/',''
$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$',''
-$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain"
+
+$MAVEN_M2_PATH = "$HOME/.m2"
if ($env:MAVEN_USER_HOME) {
- $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain"
+ $MAVEN_M2_PATH = "$env:MAVEN_USER_HOME"
+}
+
+if (-not (Test-Path -Path $MAVEN_M2_PATH)) {
+ New-Item -Path $MAVEN_M2_PATH -ItemType Directory | Out-Null
+}
+
+$MAVEN_WRAPPER_DISTS = $null
+if ((Get-Item $MAVEN_M2_PATH).Target[0] -eq $null) {
+ $MAVEN_WRAPPER_DISTS = "$MAVEN_M2_PATH/wrapper/dists"
+} else {
+ $MAVEN_WRAPPER_DISTS = (Get-Item $MAVEN_M2_PATH).Target[0] + "/wrapper/dists"
}
-$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join ''
+
+$MAVEN_HOME_PARENT = "$MAVEN_WRAPPER_DISTS/$distributionUrlNameMain"
+$MAVEN_HOME_NAME = ([System.Security.Cryptography.SHA256]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join ''
$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME"
if (Test-Path -Path "$MAVEN_HOME" -PathType Container) {
@@ -134,7 +148,33 @@ if ($distributionSha256Sum) {
# unzip and move
Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null
-Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null
+
+# Find the actual extracted directory name (handles snapshots where filename != directory name)
+$actualDistributionDir = ""
+
+# First try the expected directory name (for regular distributions)
+$expectedPath = Join-Path "$TMP_DOWNLOAD_DIR" "$distributionUrlNameMain"
+$expectedMvnPath = Join-Path "$expectedPath" "bin/$MVN_CMD"
+if ((Test-Path -Path $expectedPath -PathType Container) -and (Test-Path -Path $expectedMvnPath -PathType Leaf)) {
+ $actualDistributionDir = $distributionUrlNameMain
+}
+
+# If not found, search for any directory with the Maven executable (for snapshots)
+if (!$actualDistributionDir) {
+ Get-ChildItem -Path "$TMP_DOWNLOAD_DIR" -Directory | ForEach-Object {
+ $testPath = Join-Path $_.FullName "bin/$MVN_CMD"
+ if (Test-Path -Path $testPath -PathType Leaf) {
+ $actualDistributionDir = $_.Name
+ }
+ }
+}
+
+if (!$actualDistributionDir) {
+ Write-Error "Could not find Maven distribution directory in extracted archive"
+}
+
+Write-Verbose "Found extracted Maven distribution directory: $actualDistributionDir"
+Rename-Item -Path "$TMP_DOWNLOAD_DIR/$actualDistributionDir" -NewName $MAVEN_HOME_NAME | Out-Null
try {
Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null
} catch {
diff --git a/pom.xml b/pom.xml
index ae2e437..871454f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -52,20 +52,11 @@
-
- sonatype-oss-repository-hosting
-
- https://oss.sonatype.org/service/local/staging/deploy/maven2/
- Github PagesmicroBean™ Scopelet Sitehttps://microbean.github.io/microbean-scopelet/
-
- sonatype-oss-repository-hosting
- https://oss.sonatype.org/content/repositories/snapshots
-
@@ -89,7 +80,6 @@
deployment[maven-release-plugin] [skip ci]v@{project.version}
- false
@@ -101,13 +91,9 @@
truefalse
-
-
- true
-
- https://oss.sonatype.org/
- 10
-
+
+ ${project.name} v${project.version}
+
UTF8UTF8
@@ -125,7 +111,7 @@
org.junitjunit-bom
- 5.12.2
+ 6.0.1pomimport
@@ -135,33 +121,39 @@
org.microbeanmicrobean-assign
- 0.0.5
+ 0.0.11org.microbeanmicrobean-attributes
- 0.0.2
+ 0.0.5org.microbeanmicrobean-bean
- 0.0.17
+ 0.0.22org.microbeanmicrobean-construct
- 0.0.10
+ 0.0.18org.microbean
- microbean-reference
+ microbean-event0.0.3
+
+ org.microbean
+ microbean-reference
+ 0.0.5
+
+
@@ -191,6 +183,12 @@
compile
+
+ org.microbean
+ microbean-event
+ compile
+
+
org.microbeanmicrobean-reference
@@ -218,11 +216,11 @@
maven-antrun-plugin
- 3.1.0
+ 3.2.0maven-assembly-plugin
- 3.7.1
+ 3.8.0maven-checkstyle-plugin
@@ -332,13 +330,13 @@
com.puppycrawl.toolscheckstyle
- 10.12.6
+ 12.3.0maven-clean-plugin
- 3.4.1
+ 3.5.0
@@ -353,7 +351,7 @@
maven-compiler-plugin
- 3.14.0
+ 3.14.1-Xlint:all
@@ -363,7 +361,7 @@
maven-dependency-plugin
- 3.8.1
+ 3.9.0maven-deploy-plugin
@@ -371,12 +369,11 @@
maven-enforcer-plugin
- 3.5.0
+ 3.6.2maven-gpg-plugin
-
- 3.2.7
+ 3.2.8maven-install-plugin
@@ -384,11 +381,11 @@
maven-jar-plugin
- 3.4.2
+ 3.5.0maven-javadoc-plugin
- 3.11.2
+ 3.12.0true
@@ -420,16 +417,15 @@
maven-release-plugin
-
- 3.1.1
+ 3.3.1maven-resources-plugin
- 3.3.1
+ 3.4.0maven-scm-plugin
- 2.1.0
+ 2.2.1maven-scm-publish-plugin
@@ -441,7 +437,7 @@
maven-source-plugin
- 3.3.1
+ 3.4.0attach-sources
@@ -453,7 +449,7 @@
maven-surefire-plugin
- 3.5.3
+ 3.5.4maven-toolchains-plugin
@@ -462,35 +458,25 @@
com.github.spotbugsspotbugs-maven-plugin
- 4.9.3.0
+ 4.9.8.2org.codehaus.mojoversions-maven-plugin
- 2.18.0
+ 2.20.1io.smallryejandex-maven-plugin
- 3.3.0
+ 3.5.3
- org.sonatype.plugins
- nexus-staging-maven-plugin
- 1.6.13
+ org.sonatype.central
+ central-publishing-maven-plugin
+ 0.9.0true
-
-
-
- com.thoughtworks.xstream
- xstream
- 1.4.20
-
-
- sonatype-oss-repository-hosting
- ${nexusUrl}
- ${autoReleaseAfterClose}
+ central.sonatype.com
@@ -536,8 +522,8 @@
- org.sonatype.plugins
- nexus-staging-maven-plugin
+ org.sonatype.central
+ central-publishing-maven-plugin
diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java
index 2287955..b58b070 100644
--- a/src/main/java/module-info.java
+++ b/src/main/java/module-info.java
@@ -26,6 +26,7 @@
requires transitive org.microbean.bean;
requires org.microbean.constant;
requires org.microbean.construct;
+ requires org.microbean.event;
requires transitive org.microbean.reference;
}
diff --git a/src/main/java/org/microbean/scopelet/Instance.java b/src/main/java/org/microbean/scopelet/Instance.java
index b2b158b..6eb031a 100644
--- a/src/main/java/org/microbean/scopelet/Instance.java
+++ b/src/main/java/org/microbean/scopelet/Instance.java
@@ -35,7 +35,7 @@
*
* @see Destruction
*/
-public final class Instance implements AutoCloseable, Supplier {
+final class Instance implements AutoCloseable, Supplier {
/*
@@ -83,9 +83,9 @@ public final class Instance implements AutoCloseable, Supplier {
*
* @param destruction a {@link Destruction}; may be {@code null}
*/
- public Instance(final I contextualInstance,
- final Destructor destructor,
- final Destruction destruction) {
+ Instance(final I contextualInstance,
+ 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 67b405b..088bb84 100644
--- a/src/main/java/org/microbean/scopelet/MapBackedScopelet.java
+++ b/src/main/java/org/microbean/scopelet/MapBackedScopelet.java
@@ -156,6 +156,8 @@ private final Supplier supplier(final Object id,
if (creationLock == newLock) {
+ // The creation lock is our new lock, which means there wasn't a pre-existing creation lock. Create.
+
try {
// (The finally block will unlock creationLock/newLock.)
@@ -186,18 +188,19 @@ private final Supplier supplier(final Object id,
}
}
- // There was a Lock in the creationLocks map already that was not the newLock we just created. That's either another
- // thread performing creation (an OK situation) or we have re-entered this method on the current thread and have
- // encountered a newLock ancestor (probably not such a great situation). In any event, "our" newLock was never
- // inserted into the map. It will therefore be unlocked (it was never locked in the first place). Discard it in
- // preparation for switching locks to creationLock instead.
+ // The creationLock was not our newLock. That means there was a Lock in the creationLocks map already that was not
+ // the newLock we just created. That's either another thread performing creation (an OK situation) or we have
+ // re-entered this method on the current thread and have encountered a newLock ancestor (probably not such a great
+ // situation). In any event, "our" newLock was never inserted into the map. It will therefore be unlocked (it was
+ // never locked in the first place). Discard it in preparation for switching locks to creationLock instead.
assert !newLock.isLocked() : "newLock was locked: " + newLock;
assert !this.creationLocks.containsValue(newLock) :
"Creation locks contained " + newLock + "; creationLock: " + creationLock;
// Lock and unlock in rapid succession. Why? lock() will block if another thread is currently creating, and will
- // return immediately if it is not. This is kind of a cheap way of doing Object.wait().
+ // return immediately if it is not. This is kind of a cheap way of doing Object.wait(). (Probably could use a
+ // Condition or a Semaphore here too. This is simple.)
try {
- creationLock.lock(); // potentially blocks
+ creationLock.lock(); // potentially blocks if creationLock is locked by another thread
} finally {
creationLock.unlock();
}
@@ -233,6 +236,14 @@ public boolean remove(final Object id) {
return false;
}
+ @Override // Scopelet
+ public boolean removes() {
+ if (!this.active()) {
+ throw new InactiveScopeletException();
+ }
+ return true;
+ }
+
/*
* Static methods.
diff --git a/src/main/java/org/microbean/scopelet/NoneScopelet.java b/src/main/java/org/microbean/scopelet/NoneScopelet.java
index b81e2d1..850b6ca 100644
--- a/src/main/java/org/microbean/scopelet/NoneScopelet.java
+++ b/src/main/java/org/microbean/scopelet/NoneScopelet.java
@@ -13,22 +13,29 @@
*/
package org.microbean.scopelet;
+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;
-import java.lang.constant.MethodHandleDesc;
import java.util.Optional;
-import org.microbean.bean.AutoCloseableRegistry;
import org.microbean.bean.Creation;
import org.microbean.bean.Destruction;
-import org.microbean.bean.DisposableReference;
import org.microbean.bean.Factory;
+import org.microbean.reference.DestructorRegistry;
+
import static java.lang.constant.ConstantDescs.BSM_INVOKE;
+import static java.lang.constant.MethodHandleDesc.ofConstructor;
+
+import static java.lang.System.getLogger;
+
+import static java.lang.System.Logger.Level.WARNING;
+
/**
* A {@link Scopelet} implementation that does not cache objects at all.
*
@@ -36,8 +43,19 @@
*/
public class NoneScopelet extends Scopelet implements Constable {
- private static final boolean useDisposableReferences =
- Boolean.parseBoolean(System.getProperty("useDisposableReferences", "false"));
+
+ /*
+ * Static fields.
+ */
+
+
+ private static final Logger LOGGER = getLogger(NoneScopelet.class.getName());
+
+
+ /*
+ * Constructors.
+ */
+
/**
* Creates a new {@link NoneScopelet}.
@@ -46,6 +64,42 @@ public NoneScopelet() {
super();
}
+
+ /*
+ * Instance methods.
+ */
+
+
+ /**
+ * Checks to see if this {@link Scopelet} {@linkplain #active() is active} and then returns a contextual instance
+ * {@linkplain Factory#create(Creation) created by the supplied Factory}.
+ *
+ *
This method (and its overrides) may return {@code null}.
+ *
+ *
If the supplied {@link Factory} is {@code null}, this method will (and its overrides must) return {@code null}.
+ *
+ * @param ignoredBeanId an identifier; ignored by the default implementation; may be {@code null}
+ *
+ * @param factory a {@link Factory}; may be {@code null} in which case {@code null} will and must be returned
+ *
+ * @param creation a {@link Creation}, typically the one in effect that is causing this method to be invoked in the
+ * first place; may be {@code null}; most commonly also an instance of {@link DestructorRegistry}
+ *
+ * @return a contextual instance, or {@code null}
+ *
+ * @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
+ *
+ * @see DestructorRegistry
+ *
+ * @see Creation
+ *
+ * @see Destruction
+ *
+ * @see Factory#destroys()
+ */
// All parameters are nullable.
// Non-final to permit subclasses to, e.g., add logging.
@Override // Scopelet
@@ -57,14 +111,10 @@ public I instance(final Object ignoredBeanId, final Factory factory, fina
}
final I returnValue = factory.create(creation);
if (factory.destroys()) {
- if (useDisposableReferences) {
- // Merely creating a DisposableReference will cause it to get disposed *IF* garbage collection runs (which is not
- // guaranteed).
- new DisposableReference<>(returnValue, referent -> factory.destroy(referent, (Destruction)creation));
- } else if (creation instanceof AutoCloseableRegistry acr) {
- acr.register(new Instance(returnValue, factory::destroy, (Destruction)creation));
- } else {
- // TODO: warn or otherwise point out that dependencies will not be destroyed
+ if (creation instanceof DestructorRegistry dr && creation instanceof Destruction d) {
+ dr.register(returnValue, () -> factory.destroy(returnValue, d));
+ } else if (LOGGER.isLoggable(WARNING)) {
+ LOGGER.log(WARNING, "Dependent objects will not be destroyed");
}
}
return returnValue;
@@ -72,9 +122,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,
- MethodHandleDesc.ofConstructor(ClassDesc.of(this.getClass().getName()))));
+ return Optional.of(DynamicConstantDesc.of(BSM_INVOKE, ofConstructor(ClassDesc.of(this.getClass().getName()))));
}
}
diff --git a/src/main/java/org/microbean/scopelet/ScopedInstances.java b/src/main/java/org/microbean/scopelet/ScopedInstances.java
index 0074ad2..05e2a04 100644
--- a/src/main/java/org/microbean/scopelet/ScopedInstances.java
+++ b/src/main/java/org/microbean/scopelet/ScopedInstances.java
@@ -17,35 +17,37 @@
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
+import java.util.Map;
+import java.util.Objects;
import java.util.Queue;
+import java.util.Set;
import java.util.function.BiFunction;
+import java.util.function.BooleanSupplier;
import java.util.function.Supplier;
import javax.lang.model.type.TypeMirror;
+import org.microbean.assign.AttributedType;
+import org.microbean.assign.Selectable;
+
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.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.RankedReducer;
-import org.microbean.bean.Reducer;
-import org.microbean.bean.Reducible;
-import org.microbean.bean.Selectable;
+import org.microbean.bean.Qualifiers;
+import org.microbean.bean.ReferencesSelector;
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;
+import static java.util.Objects.requireNonNull;
/**
* An {@link Instances} implementation that is based on scopes.
@@ -64,14 +66,19 @@ public class ScopedInstances implements Instances {
*/
- private static final Attributes FOR_INSTANTIATION = Attributes.of("ForInstantiation");
-
+ // Note: deliberately not a scope or qualifier
+ private static final Attributes CONSIDER_ACTIVENESS = Attributes.of("ConsiderActiveness");
+
/*
* Instance fields.
*/
+ private final Qualifiers qualifiers;
+
+ private final Scopes scopes;
+
private final TypeMirror scopeletType;
@@ -85,10 +92,16 @@ public class ScopedInstances implements Instances {
*
* @param domain a {@link Domain}; must not be {@code null}
*
- * @exception NullPointerException if {@code domain} is {@code null}
+ * @param qualifiers a {@link Qualifiers}; must not be {@code null}
+ *
+ * @param scopes a {@link Scopes}; must not be {@code null}
+ *
+ * @exception NullPointerException if any argument is {@code null}
*/
- public ScopedInstances(final Domain domain) {
+ public ScopedInstances(final Domain domain, final Qualifiers qualifiers, final Scopes scopes) {
super();
+ this.qualifiers = requireNonNull(qualifiers, "qualifiers");
+ this.scopes = requireNonNull(scopes, "scopes");
this.scopeletType = scopeletType(domain);
}
@@ -99,69 +112,49 @@ public ScopedInstances(final Domain domain) {
/**
- * 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}
+ * Returns {@code true} if and only if the supplied {@link Id} is proxiable.
*
- * @return the first {@link Attributes} found in the supplied {@link Attributes}' {@linkplain Attributes#attributes()
- * attributes} that is a scope, or {@code null}
+ * @param id an {@link Id}; must not be {@code null}
*
- * @exception NullPointerException if {@code a} is {@code null}
+ * @return {@code true} if and only if the supplied {@link Id} is proxiable
*
- * @see #findScopeId(Collection)
+ * @exception NullPointerException if {@code id} is {@code null}
*/
- 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);
+ @Override // Instances
+ public boolean proxiable(final Id id) {
+ if (!id.types().proxiable()) {
+ return false;
}
- return scopeId;
+ final Attributes scopeId = this.findScope(id);
+ return scopeId != null && this.scopes.normal(scopeId);
}
- /**
- * 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}
- */
- protected Attributes findScopeId(final Collection extends Attributes> c) {
- if (c.isEmpty()) {
- return null;
+ @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.
+ if (scopeId == null) {
+ throw new IllegalStateException();
}
- // 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.isScopeId(a)) {
- return a;
+ final Factory factory = bean.factory();
+ 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.
+ I scopelet = factory.singleton();
+ if (scopelet == null) {
+ return () -> factory.create(request);
}
- q.addAll(a.attributes());
+ assert scopelet == factory : "scopelet != factory: " + scopelet + " != " + factory;
+ return factory::singleton;
}
- return null;
+ final AttributedType st = this.scopeletAttributedType(scopeId);
+ // Get the Scopelet and have it provide the instance
+ return () -> request.>reference(st).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.
*
@@ -170,36 +163,15 @@ protected Attributes findScopeId(final Collection extends Attributes> c) {
* @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.
*/
- protected boolean isScopeId(final Attributes a) {
- boolean scopeFound = false;
- boolean qualifierFound = false;
- for (final Attributes a0 : a.attributes()) {
- if (scopeFound) {
- if (!qualifierFound && a0.equals(qualifier())) {
- return true;
- }
- } else if (qualifierFound) {
- if (a0.equals(Scopelet.SCOPE)) {
- return true;
- }
- } else if (a0.equals(Scopelet.SCOPE)) {
- scopeFound = true;
- } else if (a0.equals(qualifier())) {
- qualifierFound = true;
- }
- }
- return false;
- }
-
- private final boolean normal(final Attributes a) {
- final BooleanValue v = a.value("normal");
- return v != null && v.value();
- }
-
- private final boolean primordial(final Attributed a) {
- return this.primordial(a.attributes());
- }
+ // @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
@@ -207,7 +179,7 @@ private final boolean primordial(final Attributed a) {
*
*
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}
*
@@ -215,51 +187,57 @@ private final boolean primordial(final Attributed a) {
* 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(primordialQualifier());
+ return c.contains(this.qualifiers.primordialQualifier());
}
/**
- * Returns {@code true} if and only if the supplied {@link Id} is proxiable.
+ * Finds and returns the nearest scope identifier in the forest represented by the supplied {@link
+ * Attributes}.
*
- * @param id an {@link Id}; must not be {@code null}
+ * @param c a {@link Collection} of {@link Attributes}; must not be {@code null}
*
- * @return {@code true} if and only if the supplied {@link Id} is proxiable
+ * @return the nearest scope identifier in the forest represented by the supplied {@link
+ * Attributes}, or {@code null}
*
- * @exception NullPointerException if {@code id} is {@code null}
+ * @exception NullPointerException if {@code c} is {@code null}
+ *
+ * @see Scopes#findScope(Collection)
+ *
+ * @deprecated Please use {@link Scopes#findScope(Collection)} instead.
*/
- @Override // Instances
- public boolean proxiable(final Id id) {
- if (!id.types().proxiable()) {
- return false;
- }
- final Attributes scopeId = this.findScopeId(id);
- return scopeId != null && this.normal(scopeId);
+ @Deprecated(forRemoval = true)
+ final Attributes findScopeId(final Collection extends Attributes> c) {
+ return this.scopes.findScope(c);
}
- @Override // Instances
- public final Supplier extends I> supplier(final Bean bean, final Creation request) {
- 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();
- }
- final Factory factory = bean.factory();
- 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);
+ private final Attributes 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());
+ break;
}
- assert scopelet == factory : "scopelet != factory: " + scopelet + " != " + factory;
- return factory::singleton;
}
- final AttributedType t = AttributedType.of(this.scopeletType, findScopeId(scopeId), FOR_INSTANTIATION);
- return () -> request.>references(t).get().instance(id, factory, request); // assumes a specific kind of reduction; see #reducible
+ if (scopeId == null) {
+ throw new IllegalArgumentException("id: " + id);
+ }
+ return scopeId;
+ }
+
+ private final boolean primordial(final Attributed a) {
+ return this.primordial(a.attributes());
+ }
+
+ private final AttributedType scopeletAttributedType(final Attributes scopeId) {
+ return AttributedType.of(this.scopeletType, scopeId, CONSIDER_ACTIVENESS);
}
@@ -268,14 +246,56 @@ public final Supplier extends I> supplier(final Bean bean, final Creati
*/
+ /**
+ * 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
- static final Bean> handleInactiveScopelets(final Collection extends Bean>> beans, final AttributedType attributedType) {
+ // (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(); // we use Iterator for good reasons
+ final Iterator extends Bean>> i = beans.iterator();
while (i.hasNext()) {
final Bean> b1 = i.next();
if (b1.factory() instanceof Scopelet> s1) {
@@ -297,26 +317,20 @@ static final Bean> handleInactiveScopelets(final Collection extends Bean>>
}
}
assert b2 != null;
- // if (s2.scopeId().equals(s1.scopeId())) { // TODO: would like to make this go away
- 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;
+ if (s2.active()) {
+ if (s1.active()) {
+ throw new TooManyActiveScopeletsException("scopelet1: " + s1 + "; scopelet2: " + s2);
}
- // } else {
- // s2 = null;
- // b2 = null;
- // break;
- // }
+ // 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;
@@ -324,96 +338,16 @@ static final Bean> handleInactiveScopelets(final Collection extends Bean>>
}
}
if (s2 == null) {
- throw new AmbiguousReductionException(attributedType,
- beans,
- "TODO: this message needs to be better; can't resolve these alternates: " + beans);
+ throw new AmbiguousResolutionException(attributedType,
+ beans,
+ "TODO: this message needs to be better; can't resolve these alternates: " + beans);
}
assert b2 != null;
return b2;
}
- static final TypeMirror scopeletType(final Domain domain) {
+ private static final TypeMirror scopeletType(final Domain domain) {
return domain.declaredType(null, domain.typeElement(Scopelet.class.getCanonicalName()), domain.wildcardType());
}
- /**
- * 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}
- *
- * @return a non-{@code null} {@link Reducible}
- *
- * @exception NullPointerException if any argument is {@code null}
- *
- * @see #reducible(Domain, Selectable, Reducer)
- *
- * @see RankedReducer#of()
- */
- public static final Reducible> 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 List extends Bean>>, ? 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 40154d9..28741ba 100644
--- a/src/main/java/org/microbean/scopelet/Scopelet.java
+++ b/src/main/java/org/microbean/scopelet/Scopelet.java
@@ -19,18 +19,24 @@
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 static java.lang.invoke.MethodHandles.lookup;
+import org.microbean.construct.Domain;
-import static org.microbean.assign.Qualifiers.primordialQualifier;
-import static org.microbean.assign.Qualifiers.qualifier;
+import org.microbean.event.Events;
+
+import static java.lang.invoke.MethodHandles.lookup;
/**
* A manager of object lifespans on behalf of one or more notional scopes.
@@ -63,38 +69,6 @@ public abstract class Scopelet> implements AutoCloseable,
}
}
- /**
- * An {@link Attributes} identifying the scope designator.
- */
- public static final Attributes SCOPE = Attributes.of("Scope");
-
- private static final Map> normalScope = Map.of("normal", BooleanValue.of(true));
-
- private static final Map> pseudoScope = Map.of("normal", BooleanValue.of(false));
-
- /**
- * An {@link Attributes} identifying the (well-known) singleton pseudo-scope.
- *
- *
The {@link Attributes} constituting the singleton pseudo-scope identifier is {@linkplain Attributes#attributes()
- * attributed} with {@linkplain #SCOPE the scope designator}, {@linkplain org.microbean.assign.Qualifiers#qualifier()
- * the qualifier designator}, and {@linkplain org.microbean.assign.Qualifiers#primordialQualifier() the primordial
- * qualifier}, indicating that the scope it identifies governs itself.
- */
- public static final Attributes SINGLETON_ID =
- Attributes.of("Singleton", pseudoScope, Map.of(), Map.of("Singleton", List.of(qualifier(), SCOPE, primordialQualifier())));
-
- /**
- * An {@link Attributes} identifying the (well-known and normal) application scope.
- */
- public static final Attributes APPLICATION_ID =
- Attributes.of("Application", normalScope, Map.of(), Map.of("Application", List.of(qualifier(), SCOPE, SINGLETON_ID)));
-
- /**
- * An {@link Attributes} identifying the (well-known) none pseudo-scope.
- */
- public static final Attributes NONE_ID =
- Attributes.of("None", pseudoScope, Map.of(), Map.of("None", List.of(qualifier(), SCOPE, SINGLETON_ID)));
-
/*
* Instance fields.
@@ -127,19 +101,87 @@ protected Scopelet() {
/**
* Creates this {@link Scopelet} by simply returning it.
*
+ * @param c a {@link Creation}; may be {@code null} in certain primordial cases
+ *
* @return this {@link Scopelet}
*/
@Override // Factory
@SuppressWarnings("unchecked")
- public final S create(final Creation r) {
+ public final S create(final Creation c) {
+ if (this.closed()) {
+ throw new IllegalStateException("closed");
+ }
if (ME.compareAndSet(this, null, this)) { // volatile write
- if (r != null) {
- // TODO: emit initialized event
+ if (c != null) {
+ this.fireScopeletInitialized(c);
}
}
return (S)this;
}
+ @Override
+ public final void destroy(final S me, final Destruction creation) {
+ if (this.closed()) {
+ throw new IllegalStateException("closed");
+ }
+ if (creation == null) {
+ Factory.super.destroy(me, creation);
+ this.me = null; // volatile write
+ return;
+ } else if (!(creation instanceof Creation>)) {
+ throw new IllegalArgumentException("creation: " + creation);
+ }
+ final Creation c = (Creation)creation;
+ this.fireScopeletDestroying(c);
+ Factory.super.destroy(me, creation);
+ this.me = null; // volatile write
+ this.fireScopeletDestroyed(c);
+ }
+
+ /**
+ * Informs any interested observers that this {@link Scopelet} is about to be destroyed.
+ *
+ * @param r a {@link ReferencesSelector}; must not be {@code null}
+ *
+ * @exception NullPointerException if {@code r} is {@code null}
+ */
+ protected void fireScopeletDestroying(final ReferencesSelector r) {
+
+ }
+
+ /**
+ * Informs any interested observers that this {@link Scopelet} has been irrevocably destroyed.
+ *
+ * @param r a {@link ReferencesSelector}; must not be {@code null}
+ *
+ * @exception NullPointerException if {@code r} is {@code null}
+ */
+ protected void fireScopeletDestroyed(final ReferencesSelector r) {
+
+ }
+
+ /**
+ * Informs any interested observers that this {@link Scopelet} has just been initialized.
+ *
+ * @param r a {@link ReferencesSelector}; must not be {@code null}
+ *
+ * @exception NullPointerException if {@code r} is {@code null}
+ */
+ // The specification says scopes should fire an event when they're open for business but there are lots of weird
+ // ramifications to this. We break this out into a protected method so overrides can do what they want, or nothing at
+ // all.
+ protected void fireScopeletInitialized(final ReferencesSelector r) {
+ // final Domain d = r.domain();
+ // final Events e = r.reference(new AttributedType(d.declaredType(d.typeElement(Events.class.getCanonicalName())),
+ // defaultQualifiers()));
+ // if (e != null) {
+ // e.fire(null, // typeArgumentSource; not needed here; maybe could do wild S reflective introspection
+ // List.of(), // qualifiers/attributes; TODO: @Initialized
+ // this, // event object; can be anything
+ // c);
+ // }
+ }
+
/**
* Returns this {@link Scopelet} if it has been created via the {@link #create(Creation)} method, or {@code null} if
* that method has not yet been invoked.
@@ -150,6 +192,9 @@ public final S create(final Creation r) {
*/
@Override // Factory
public final S singleton() {
+ if (this.closed()) {
+ throw new IllegalStateException("closed");
+ }
return this.me; // volatile read
}
@@ -206,10 +251,14 @@ public boolean active() {
*
* @exception InactiveScopeletException if this {@link Scopelet} {@linkplain #active() is not active}
*
- * @exception ClassCastException if {@code creation} is non-{@code null} and does not implement {@link
- * org.microbean.bean.Destruction}, a requirement of its contract
+ * @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
*
* @see Creation
+ *
+ * @see org.microbean.bean.Destruction
+ *
+ * @see Factory#destroys()
*/
public abstract I instance(final Object id, final Factory factory, final Creation creation);
@@ -233,6 +282,27 @@ public boolean remove(final Object id) {
return false;
}
+ /**
+ * Returns {@code true} if and only if this {@link Scopelet} stores contextual instances, and hence is capable of
+ * {@linkplain #remove(Object) removing} them.
+ *
+ *
The default implementation of this method returns {@code false}. Subclasses are encouraged to
+ * override it as appropriate.
The search is conducted in a breadth-first manner.
+ *
+ * @param c a {@link Collection} of {@link Attributes}; must not be {@code null}
+ *
+ * @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
+ *
+ * @exception NullPointerException if {@code c} is {@code null}
+ */
+ 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;
+ }
+ q.addAll(a.attributes());
+ }
+ return null;
+ }
+
+ /**
+ * Returns a non-{@code null}, determinate {@link Attributes} representing the identifier for the none
+ * scope.
+ *
+ * @return a non-{@code null}, determinate {@link Attributes} representing the identifier for the none
+ * scope
+ */
+ public Attributes none() {
+ return NONE;
+ }
+
+ /**
+ * 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.
+ *
+ * @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
+ */
+ public Map> normal() {
+ return NORMAL;
+ }
+
+ /**
+ * Returns {@code true} if the supplied {@link Attributes} has elements that might be used to indicate that it is a
+ * normal scope.
+ *
+ * @param a an {@link Attributes}; must not be {@code null}
+ *
+ * @return {@code true} if the supplied {@link Attributes} 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()
+ */
+ public boolean normal(final Attributes a) {
+ final BooleanValue v = a.value(this.normal().keySet().iterator().next());
+ return v != null && v.value();
+ }
+
+ /**
+ * 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.
+ *
+ * @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
+ */
+ public Map> pseudo() {
+ return PSEUDO;
+ }
+
+ /**
+ * Returns the non-{@code null}, determinate {@link Attributes} that can be used to designate other {@link Attributes}
+ * as a scope.
+ *
+ * @return the non-{@code null}, determinate {@link Attributes} that can be used to designate other {@link Attributes}
+ * as a scope
+ */
+ public Attributes scope() {
+ return SCOPE;
+ }
+
+ /**
+ * Returns {@code true} if and only if the supplied {@link Attributes} is a scope identifier.
+ *
+ * @param a an {@link Attributes}; must not be {@code null}
+ *
+ * @return {@code true} if and only if the supplied {@link Attributes} is a 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;
+ }
+ } else if (qualifierFound) {
+ if (ma.equals(this.scope())) {
+ scopeFound = true;
+ break;
+ }
+ } 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;
+ }
+ }
+ return scopeFound && qualifierFound;
+ }
+
+ /**
+ * Returns a non-{@code null}, determinate {@link Attributes} representing the identifier for the singleton
+ * pseudo-scope.
+ *
+ * @return a non-{@code null}, determinate {@link Attributes} representing the identifier for the singleton
+ * pseudo-scope
+ */
+ public Attributes singleton() {
+ return SINGLETON;
+ }
+
+}
diff --git a/src/main/java/org/microbean/scopelet/SingletonScopelet.java b/src/main/java/org/microbean/scopelet/SingletonScopelet.java
index 8a0933b..0d0bf16 100644
--- a/src/main/java/org/microbean/scopelet/SingletonScopelet.java
+++ b/src/main/java/org/microbean/scopelet/SingletonScopelet.java
@@ -17,12 +17,13 @@
import java.lang.constant.Constable;
import java.lang.constant.ConstantDesc;
import java.lang.constant.DynamicConstantDesc;
-import java.lang.constant.MethodHandleDesc;
import java.util.Optional;
import static java.lang.constant.ConstantDescs.BSM_INVOKE;
+import static java.lang.constant.MethodHandleDesc.ofConstructor;
+
/**
* A {@link MapBackedScopelet} implementation that caches singletons.
*
@@ -39,9 +40,7 @@ public SingletonScopelet() {
@Override // Constable
public Optional extends ConstantDesc> describeConstable() {
- return
- Optional.of(DynamicConstantDesc.of(BSM_INVOKE,
- MethodHandleDesc.ofConstructor(ClassDesc.of(this.getClass().getName()))));
+ return Optional.of(DynamicConstantDesc.of(BSM_INVOKE, ofConstructor(ClassDesc.of(this.getClass().getName()))));
}
}
diff --git a/src/site/site.xml b/src/site/site.xml
index fb69fe9..b8967ea 100644
--- a/src/site/site.xml
+++ b/src/site/site.xml
@@ -13,7 +13,7 @@
org.apache.maven.skinsmaven-fluido-skin
- 2.0.0
+ 2.1.0