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; 017import java.lang.invoke.MethodHandles.Lookup; 018import java.lang.invoke.VarHandle; 019 020import java.util.Objects; 021 022import org.microbean.bean.Bean; 023import org.microbean.bean.Id; 024import org.microbean.bean.Factory; 025import org.microbean.bean.Request; 026 027import org.microbean.qualifier.NamedAttributeMap; 028 029import org.microbean.scope.ScopeMember; 030 031import static org.microbean.scope.Scope.SINGLETON_ID; 032 033/** 034 * A manager of object lifespans identified by a {@linkplain org.microbean.scope.Scope scope}. 035 * 036 * @param <S> the {@link Scopelet} subtype extending this class 037 * 038 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a> 039 * 040 * @see org.microbean.scope.Scope 041 * 042 * @see ScopeMember 043 */ 044public abstract class Scopelet<S extends Scopelet<S>> implements AutoCloseable, Factory<S>, ScopeMember { 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 = MethodHandles.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 /* 068 * Instance fields. 069 */ 070 071 072 private volatile S me; 073 074 private volatile boolean closed; 075 076 private final NamedAttributeMap<?> scopeId; 077 078 079 /* 080 * Constructors. 081 */ 082 083 084 /** 085 * Creates a new {@link Scopelet}. 086 * 087 * @param scopeId a {@link NamedAttributeMap} identifying the scope being implemented; must not be {@code null} 088 * 089 * @exception NullPointerException if {@code scopeId} is {@code null} 090 */ 091 protected Scopelet(final NamedAttributeMap<?> scopeId) { 092 super(); 093 this.scopeId = Objects.requireNonNull(scopeId, "scopeId"); 094 } 095 096 097 /* 098 * Instance methods. 099 */ 100 101 102 /** 103 * Returns an {@link Id} representing this {@link Scopelet}. 104 * 105 * <p>Implementations of this method must return determinate values.</p> 106 * 107 * @return an {@link Id} representing this {@link Scopelet}; never {@code null} 108 * 109 * @see #governingScopeId() 110 * 111 * @see #bean() 112 */ 113 public abstract Id id(); 114 115 /** 116 * Returns a {@link Bean} for this {@link Scopelet}. 117 * 118 * <p>This {@link Scopelet} will be used as the {@link Bean}'s {@linkplain Bean#factory() associated 119 * <code>Factory</code>}. This {@link Scopelet}'s {@link #id() Id} will be used as the {@link Bean}'s {@linkplain 120 * Bean#id() identifier}.</p> 121 * 122 * @return a {@link Bean} for this {@link Scopelet}; never {@code null} 123 * 124 * @see #id() 125 */ 126 public final Bean<S> bean() { 127 return new Bean<>(this.id(), this); 128 } 129 130 /** 131 * Creates this {@link Scopelet} by simply returning it. 132 * 133 * @return this {@link Scopelet} 134 */ 135 @Override // Factory<S> 136 @SuppressWarnings("unchecked") 137 public final S create(final Request<S> r) { 138 if (ME.compareAndSet(this, null, this)) { // volatile write 139 if (r != null) { 140 // TODO: emit initialized event 141 } 142 } 143 return (S)this; 144 } 145 146 /** 147 * Returns this {@link Scopelet} if it has been created via the {@link #create(Request)} method, or {@code null} if 148 * that method has not yet been invoked. 149 * 150 * @return this {@link Scopelet} if it has been "{@linkplain #create(Request) created}"; {@code null} otherwise 151 * 152 * @see #create(Request) 153 */ 154 @Override // Factory<S> 155 public final S singleton() { 156 return this.me; // volatile read 157 } 158 159 /** 160 * Returns {@code true} when invoked to indicate that {@link Scopelet} implementations {@linkplain 161 * Factory#destroy(Object, Request) destroy} what they {@linkplain #create(Request) create}. 162 * 163 * @return {@code true} when invoked 164 * 165 * @see Factory#destroy(Object, Request) 166 * 167 * @see #create(Request) 168 */ 169 @Override // Factory<S> 170 public final boolean destroys() { 171 return true; 172 } 173 174 /** 175 * Returns a hashcode for this {@link Scopelet}. 176 * 177 * @return a hashcode for this {@link Scopelet} 178 */ 179 @Override // Object 180 public int hashCode() { 181 int hashCode = 17; 182 hashCode = 31 * hashCode + this.id().hashCode(); 183 hashCode = 31 * hashCode + this.scopeId().hashCode(); 184 return hashCode; 185 } 186 187 /** 188 * Returns {@code true} if and only if the supplied {@link Object} is not {@code null}, has the same class as this 189 * {@link Scopelet}, has an {@link #id() Id} {@linkplain Id#equals(Object) equal to} that of this {@link Scopelet}, 190 * and a {@linkplain #scopeId() scope identifier} {@linkplain NamedAttributeMap#equals(Object) equal to} that of this 191 * {@link Scopelet}. 192 * 193 * @param other the {@link Object} to test; may be {@code null} 194 * 195 * @return {@code true} if and only if the supplied {@link Object} is not {@code null}, has the same class as this 196 * {@link Scopelet}, has an {@link #id() Id} {@linkplain Id#equals(Object) equal to} that of this {@link Scopelet}, 197 * and a {@linkplain #scopeId() scope identifier} {@linkplain NamedAttributeMap#equals(Object) equal to} that of this 198 * {@link Scopelet} 199 */ 200 @Override // Object 201 public boolean equals(final Object other) { 202 if (other == this) { 203 return true; 204 } else if (other != null && other.getClass().equals(this.getClass())) { 205 final Scopelet<?> her = (Scopelet<?>)other; 206 return 207 Objects.equals(this.id(), her.id()) && 208 Objects.equals(this.scopeId(), her.scopeId()); 209 } else { 210 return false; 211 } 212 } 213 214 /** 215 * Returns the {@link NamedAttributeMap} representing the identifier of the scope to which this {@link Scopelet} 216 * belongs. 217 * 218 * @return the {@link NamedAttributeMap} representing the identifier of the scope to which this {@link Scopelet} 219 * belongs; never {@code null} 220 * 221 * @see ScopeMember 222 */ 223 @Override // ScopeMember 224 public final NamedAttributeMap<?> governingScopeId() { 225 return this.id().governingScopeId(); 226 } 227 228 /** 229 * Returns {@code true} if this {@link Scopelet} is governed by the scope represented by the supplied {@link 230 * NamedAttributeMap}. 231 * 232 * @param scopeId a {@link NamedAttributeMap} identifying a scope; must not be {@code null} 233 * 234 * @return {@code true} if this {@link Scopelet} is governed by the scope represented by the supplied {@link 235 * NamedAttributeMap} 236 * 237 * @exception NullPointerException if {@code scopeId} is {@code null} 238 */ 239 @Override // ScopeMember 240 public final boolean governedBy(final NamedAttributeMap<?> scopeId) { 241 return this.id().governedBy(scopeId); 242 } 243 244 245 /* 246 * Repository-like concerns. 247 */ 248 249 250 /** 251 * Returns the {@link NamedAttributeMap} that identifies this {@link Scopelet}'s affiliated scope. 252 * 253 * @return the {@link NamedAttributeMap} that identifies this {@link Scopelet}'s affiliated scope; never {@code null} 254 */ 255 public final NamedAttributeMap<?> scopeId() { 256 return this.scopeId; 257 } 258 259 /** 260 * Returns {@code true} if and only if this {@link Scopelet} is <dfn>active</dfn> at the moment of the call. 261 * 262 * <p>Overrides of this method must ensure that if {@link #closed()} returns {@code true}, this method must return 263 * {@code false}.</p> 264 * 265 * @return {@code true} if and only if this {@link Scopelet} is <dfn>active</dfn> at the moment of the call 266 * 267 * @see #closed() 268 */ 269 public boolean active() { 270 return !this.closed(); // volatile read 271 } 272 273 /** 274 * Checks to see if this {@link Scopelet} {@linkplain #active() is active} and then returns {@code true} if and only 275 * if, at the moment of an invocation, this {@link Scopelet} {@linkplain #active() is active} and already contains an 276 * object identified by the supplied {@link Object}. 277 * 278 * <p>The default implementation of this method checks to see if this {@link Scopelet} {@linkplain #active() is 279 * active}, and then {@code true} if and only if the result of invoking the {@link #instance(Object, Factory, 280 * Request)} method with the supplied {@code id}, {@code null}, and {@code null} is not {@code null}.</p> 281 * 282 * <p>Subclasses are encouraged to override this method to be more efficient or to use a different algorithm.</p> 283 * 284 * @param id the {@link Object} serving as an identifier; may be {@code null} in certain pathological cases 285 * 286 * @return {@code true} if and only if, at the moment of an invocation, this {@link Scopelet} {@linkplain #active() is 287 * active} and contains a preexisting object identified by the supplied {@link Object} 288 * 289 * @exception InactiveScopeletException if this {@link Scopelet} {@linkplain #active() is not active} 290 * 291 * @see #active() 292 * 293 * @see #instance(Object, Factory, Request) 294 */ 295 public boolean containsId(final Object id) { 296 return (id instanceof Request<?> r ? this.instance(r) : this.instance(id, null, null)) != null; 297 } 298 299 /** 300 * Checks to see if this {@link Scopelet} {@linkplain #active() is active}, and then returns the preexisting 301 * contextual instance identified by the supplied {@link Object}, or {@code null} if no such instance exists. 302 * 303 * <p>This convenience method checks to see if this {@link Scopelet} {@linkplain #active() is active}, and then, if 304 * the supplied {@link Object} is not a {@link Request}, calls the {@link #instance(Object, Factory, Request)} method 305 * with the supplied {@code id}, {@code null}, and {@code null}, and returns its result.</p> 306 * 307 * <p>If the supplied {@link Object} <em>is</em> a {@link Request}, this method calls the {@link #instance(Request)} 308 * method with the supplied (cast) {@code id} and returns its result.</p> 309 * 310 * @param <I> the type of contextual instance 311 * 312 * @param id an {@link Object} serving as an identifier; may be {@code null} in certain pathological cases 313 * 314 * @return the contextual instance identified by the supplied {@link Object}, or {@code null} if no such instance 315 * exists 316 * 317 * @exception InactiveScopeletException if this {@link Scopelet} {@linkplain #active() is not active} 318 * 319 * @see #instance(Object, Factory, Request) 320 * 321 * @see #instance(Request) 322 */ 323 // id is nullable. 324 @SuppressWarnings("unchecked") 325 public final <I> I get(final Object id) { 326 return id instanceof Request<?> r ? this.instance((Request<I>)r) : this.instance(id, null, null); 327 } 328 329 /** 330 * Checks to see if this {@link Scopelet} {@linkplain #active() is active} and then eturns a contextual instance 331 * identified by the {@linkplain Request#beanReduction() identifying information} present within the supplied {@link 332 * Request}, creating the instance and associating it with the {@linkplain Request#beanReduction() identifying 333 * information} present within the supplied {@link Request} if necessary. 334 * 335 * @param <I> the type of contextual instance 336 * 337 * @param request a {@link Request}; may be {@code null} in which case the return value of an invocation of {@link 338 * #instance(Object, Factory, Request)} with {@code null} supplied for all three arguments will be returned instead 339 * 340 * @return an appropriate contextual instance, or {@code null} 341 * 342 * @exception InactiveScopeletException if this {@link Scopelet} {@linkplain #active() is not active} 343 * 344 * @see Request#beanReduction() 345 * 346 * @see #instance(Object, Factory, Request) 347 */ 348 public final <I> I instance(final Request<I> request) { 349 if (request == null) { 350 return this.instance(null, null, null); 351 } 352 final Bean<I> bean = request.beanReduction().bean(); 353 return this.instance(bean.id(), bean.factory(), request); 354 } 355 356 /** 357 * Checks to see if this {@link Scopelet} {@linkplain #active() is active} and then returns a pre-existing or 358 * created-on-demand contextual instance suitable for the combination of identifier, {@link Factory} and {@link 359 * Request}. 360 * 361 * @param <I> the type of contextual instance 362 * 363 * @param id an identifier that can identify a contextual instance; may be {@code null} 364 * 365 * @param factory a {@link Factory}; may be {@code null} 366 * 367 * @param request a {@link Request}, typically the one in effect that is causing this method to be invoked in the 368 * first place; may be {@code null} 369 * 370 * @return a contextual instance, possibly pre-existing, or possibly created just in time, or {@code null} 371 * 372 * @exception InactiveScopeletException if this {@link Scopelet} {@linkplain #active() is not active} 373 */ 374 // All parameters are nullable, perhaps pathologically. This helps permit super early bootstrapping. 375 public abstract <I> I instance(final Object id, final Factory<I> factory, final Request<I> request); 376 377 /** 378 * Checks to see if this {@link Scopelet} {@linkplain #active() is active} and then removes any contextual instance 379 * stored under the supplied {@code id}, returning {@code true} if and only if removal actually took place. 380 * 381 * <p>The default implementation of this method always returns {@code false}. Subclasses are encouraged to override 382 * it as appropriate.</p> 383 * 384 * @param id an identifier; may be {@code null} 385 * 386 * @return {@code true} if and only if removal actually occurred 387 * 388 * @exception InactiveScopeletException if this {@link Scopelet} {@linkplain #active() is not active} 389 */ 390 // id is nullable. 391 public boolean remove(final Object id) { 392 if (!this.active()) { 393 throw new InactiveScopeletException(); 394 } 395 return false; 396 } 397 398 /** 399 * Irrevocably closes this {@link Scopelet}, and, by doing so, notionally makes it irrevocably {@linkplain #active() 400 * inactive}. 401 * 402 * @see #closed() 403 * 404 * @see #active() 405 */ 406 // Most scopelets will want to override this to do additional work. They must call super.close() to ensure #closed() 407 // returns an appropriate value. 408 @Override // AutoCloseable 409 public void close() { 410 CLOSED.compareAndSet(this, false, true); // volatile write 411 } 412 413 /** 414 * Returns {@code true} if and only if at the moment of invocation this {@link Scopelet} is (irrevocably) closed (and 415 * therefore also {@linkplain #active() not active}). 416 * 417 * @return {@code true} if and only if at the moment of invocation this {@link Scopelet} is (irrevocably) closed (and 418 * therefore also {@linkplain #active() not active}) 419 */ 420 protected final boolean closed() { 421 return this.closed; // volatile read 422 } 423 424}