001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*- 002 * 003 * Copyright © 2023–2025 microBean™. 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 006 * the License. You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 011 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 012 * specific language governing permissions and limitations under the License. 013 */ 014package org.microbean.scopelet; 015 016import java.lang.invoke.MethodHandles.Lookup; 017import java.lang.invoke.VarHandle; 018 019import java.util.List; 020import java.util.Map; 021 022import org.microbean.attributes.Attributes; 023import org.microbean.attributes.BooleanValue; 024import org.microbean.attributes.Value; 025 026import org.microbean.bean.Bean; 027import org.microbean.bean.Creation; 028import org.microbean.bean.Factory; 029 030import static java.lang.invoke.MethodHandles.lookup; 031 032import static org.microbean.assign.Qualifiers.primordialQualifier; 033import static org.microbean.assign.Qualifiers.qualifier; 034 035/** 036 * A manager of object lifespans on behalf of one or more notional <dfn>scopes</dfn>. 037 * 038 * @param <S> the {@link Scopelet} subtype extending this class 039 * 040 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a> 041 * 042 * @see #instance(Object, Factory, Creation) 043 */ 044public abstract class Scopelet<S extends Scopelet<S>> implements AutoCloseable, Factory<S> { 045 046 047 /* 048 * Static fields. 049 */ 050 051 052 private static final VarHandle CLOSED; 053 054 private static final VarHandle ME; 055 056 static { 057 final Lookup lookup = lookup(); 058 try { 059 CLOSED = lookup.findVarHandle(Scopelet.class, "closed", boolean.class); 060 ME = lookup.findVarHandle(Scopelet.class, "me", Scopelet.class); 061 } catch (final NoSuchFieldException | IllegalAccessException e) { 062 throw new ExceptionInInitializerError(e); 063 } 064 } 065 066 /** 067 * An {@link Attributes} identifying the <dfn>scope designator</dfn>. 068 */ 069 public static final Attributes SCOPE = Attributes.of("Scope"); 070 071 private static final Map<String, Value<?>> normalScope = Map.of("normal", BooleanValue.of(true)); 072 073 private static final Map<String, Value<?>> pseudoScope = Map.of("normal", BooleanValue.of(false)); 074 075 /** 076 * An {@link Attributes} identifying the (well-known) <dfn>singleton pseudo-scope</dfn>. 077 * 078 * <p>The {@link Attributes} constituting the singleton pseudo-scope identifier is {@linkplain Attributes#attributes() 079 * attributed} with {@linkplain #SCOPE the scope designator}, {@linkplain org.microbean.assign.Qualifiers#qualifier() 080 * the qualifier designator}, and {@linkplain org.microbean.assign.Qualifiers#primordialQualifier() the primordial 081 * qualifier}, indicating that the scope it identifies governs itself.</p> 082 */ 083 public static final Attributes SINGLETON_ID = 084 Attributes.of("Singleton", pseudoScope, Map.of(), Map.of("Singleton", List.of(qualifier(), SCOPE, primordialQualifier()))); 085 086 /** 087 * An {@link Attributes} identifying the (well-known and <dfn>normal</dfn>) <dfn>application scope</dfn>. 088 */ 089 public static final Attributes APPLICATION_ID = 090 Attributes.of("Application", normalScope, Map.of(), Map.of("Application", List.of(qualifier(), SCOPE, SINGLETON_ID))); 091 092 /** 093 * An {@link Attributes} identifying the (well-known) <dfn>none pseudo-scope</dfn>. 094 */ 095 public static final Attributes NONE_ID = 096 Attributes.of("None", pseudoScope, Map.of(), Map.of("None", List.of(qualifier(), SCOPE, SINGLETON_ID))); 097 098 099 /* 100 * Instance fields. 101 */ 102 103 104 private volatile S me; 105 106 private volatile boolean closed; 107 108 109 /* 110 * Constructors. 111 */ 112 113 114 /** 115 * Creates a new {@link Scopelet}. 116 */ 117 protected Scopelet() { 118 super(); 119 } 120 121 122 /* 123 * Instance methods. 124 */ 125 126 127 /** 128 * Creates this {@link Scopelet} by simply returning it. 129 * 130 * @return this {@link Scopelet} 131 */ 132 @Override // Factory<S> 133 @SuppressWarnings("unchecked") 134 public final S create(final Creation<S> r) { 135 if (ME.compareAndSet(this, null, this)) { // volatile write 136 if (r != null) { 137 // TODO: emit initialized event 138 } 139 } 140 return (S)this; 141 } 142 143 /** 144 * Returns this {@link Scopelet} if it has been created via the {@link #create(Creation)} method, or {@code null} if 145 * that method has not yet been invoked. 146 * 147 * @return this {@link Scopelet} if it has been "{@linkplain #create(Creation) created}"; {@code null} otherwise 148 * 149 * @see #create(Creation) 150 */ 151 @Override // Factory<S> 152 public final S singleton() { 153 return this.me; // volatile read 154 } 155 156 /** 157 * Returns {@code true} when invoked to indicate that {@link Scopelet} implementations {@linkplain 158 * Factory#destroy(Object, org.microbean.bean.Destruction) destroy} what they {@linkplain #create(Creation) create}. 159 * 160 * @return {@code true} when invoked 161 * 162 * @see Factory#destroy(Object, org.microbean.bean.Destruction) 163 * 164 * @see #create(Creation) 165 */ 166 @Override // Factory<S> 167 public final boolean destroys() { 168 return true; 169 } 170 171 172 /* 173 * Repository-like concerns. 174 */ 175 176 177 /** 178 * Returns {@code true} if and only if this {@link Scopelet} is <dfn>active</dfn> at the moment of the call. 179 * 180 * <p>Overrides of this method must ensure that if {@link #closed()} returns {@code true}, this method must return 181 * {@code false}.</p> 182 * 183 * @return {@code true} if and only if this {@link Scopelet} is <dfn>active</dfn> at the moment of the call 184 * 185 * @see #closed() 186 */ 187 public boolean active() { 188 return !this.closed(); // volatile read 189 } 190 191 /** 192 * Checks to see if this {@link Scopelet} {@linkplain #active() is active} and then returns a pre-existing or 193 * created-on-demand contextual instance suitable for the combination of identifier, {@link Factory} and {@link 194 * Creation}, or {@code null} 195 * 196 * @param <I> the type of contextual instance 197 * 198 * @param id an identifier that can identify a contextual instance; may be {@code null} 199 * 200 * @param factory a {@link Factory}; may be {@code null} 201 * 202 * @param creation a {@link Creation}, typically the one in effect that is causing this method to be invoked in the 203 * first place; may be {@code null} 204 * 205 * @return a contextual instance, possibly pre-existing, or possibly created just in time, or {@code null} 206 * 207 * @exception InactiveScopeletException if this {@link Scopelet} {@linkplain #active() is not active} 208 * 209 * @exception ClassCastException if {@code creation} is non-{@code null} and does not implement {@link 210 * org.microbean.bean.Destruction}, a requirement of its contract 211 * 212 * @see Creation 213 */ 214 public abstract <I> I instance(final Object id, final Factory<I> factory, final Creation<I> creation); 215 216 /** 217 * Checks to see if this {@link Scopelet} {@linkplain #active() is active} and then removes any contextual instance 218 * stored under the supplied {@code id}, returning {@code true} if and only if removal actually took place. 219 * 220 * <p><strong>The default implementation of this method always returns {@code false}.</strong> Subclasses are 221 * encouraged to override it as appropriate.</p> 222 * 223 * @param id an identifier; may be {@code null} 224 * 225 * @return {@code true} if and only if removal actually occurred 226 * 227 * @exception InactiveScopeletException if this {@link Scopelet} {@linkplain #active() is not active} 228 */ 229 public boolean remove(final Object id) { 230 if (!this.active()) { 231 throw new InactiveScopeletException(); 232 } 233 return false; 234 } 235 236 /** 237 * Irrevocably closes this {@link Scopelet}, and, by doing so, notionally makes it irrevocably {@linkplain #closed() 238 * closed} and {@linkplain #active() inactive}. 239 * 240 * <p>Overrides of this method <strong>must</strong> call {@link Scopelet#close() super.close()} as part of their 241 * implementation or undefined behavior may result.</p> 242 * 243 * @see #closed() 244 * 245 * @see #active() 246 */ 247 @Override // AutoCloseable 248 public void close() { 249 CLOSED.compareAndSet(this, false, true); // volatile write 250 } 251 252 /** 253 * Returns {@code true} if and only if at the moment of invocation this {@link Scopelet} is (irrevocably) closed (and 254 * therefore also {@linkplain #active() not active}). 255 * 256 * @return {@code true} if and only if at the moment of invocation this {@link Scopelet} is (irrevocably) closed (and 257 * therefore also {@linkplain #active() not active}) 258 * 259 * @see #active() 260 */ 261 protected final boolean closed() { 262 return this.closed; // volatile read 263 } 264 265}