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.assign.AttributedType; 023import org.microbean.assign.Qualifiers; 024 025import org.microbean.attributes.Attributes; 026import org.microbean.attributes.BooleanValue; 027import org.microbean.attributes.Value; 028 029import org.microbean.bean.Bean; 030import org.microbean.bean.Creation; 031import org.microbean.bean.Destruction; 032import org.microbean.bean.Factory; 033import org.microbean.bean.ReferencesSelector; 034 035import org.microbean.construct.Domain; 036 037import org.microbean.event.Events; 038 039import static java.lang.invoke.MethodHandles.lookup; 040 041/** 042 * A manager of object lifespans on behalf of one or more notional <dfn>scopes</dfn>. 043 * 044 * @param <S> the {@link Scopelet} subtype extending this class 045 * 046 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a> 047 * 048 * @see #instance(Object, Factory, Creation) 049 */ 050public abstract class Scopelet<S extends Scopelet<S>> implements AutoCloseable, Factory<S> { 051 052 053 /* 054 * Static fields. 055 */ 056 057 058 private static final VarHandle CLOSED; 059 060 private static final VarHandle ME; 061 062 static { 063 final Lookup lookup = lookup(); 064 try { 065 CLOSED = lookup.findVarHandle(Scopelet.class, "closed", boolean.class); 066 ME = lookup.findVarHandle(Scopelet.class, "me", Scopelet.class); 067 } catch (final NoSuchFieldException | IllegalAccessException e) { 068 throw new ExceptionInInitializerError(e); 069 } 070 } 071 072 073 /* 074 * Instance fields. 075 */ 076 077 078 private volatile S me; 079 080 private volatile boolean closed; 081 082 083 /* 084 * Constructors. 085 */ 086 087 088 /** 089 * Creates a new {@link Scopelet}. 090 */ 091 protected Scopelet() { 092 super(); 093 } 094 095 096 /* 097 * Instance methods. 098 */ 099 100 101 /** 102 * Creates this {@link Scopelet} by simply returning it. 103 * 104 * @param c a {@link Creation}; <strong>may be {@code null}</strong> in certain primordial cases 105 * 106 * @return this {@link Scopelet} 107 */ 108 @Override // Factory<S> 109 @SuppressWarnings("unchecked") 110 public final S create(final Creation<S> c) { 111 if (this.closed()) { 112 throw new IllegalStateException("closed"); 113 } 114 if (ME.compareAndSet(this, null, this)) { // volatile write 115 if (c != null) { 116 this.fireScopeletInitialized(c); 117 } 118 } 119 return (S)this; 120 } 121 122 @Override 123 public final void destroy(final S me, final Destruction creation) { 124 if (this.closed()) { 125 throw new IllegalStateException("closed"); 126 } 127 if (creation == null) { 128 Factory.super.destroy(me, creation); 129 this.me = null; // volatile write 130 return; 131 } else if (!(creation instanceof Creation<?>)) { 132 throw new IllegalArgumentException("creation: " + creation); 133 } 134 final Creation<S> c = (Creation<S>)creation; 135 this.fireScopeletDestroying(c); 136 Factory.super.destroy(me, creation); 137 this.me = null; // volatile write 138 this.fireScopeletDestroyed(c); 139 } 140 141 /** 142 * Informs any interested observers that this {@link Scopelet} is about to be destroyed. 143 * 144 * @param r a {@link ReferencesSelector}; must not be {@code null} 145 * 146 * @exception NullPointerException if {@code r} is {@code null} 147 */ 148 protected void fireScopeletDestroying(final ReferencesSelector r) { 149 150 } 151 152 /** 153 * Informs any interested observers that this {@link Scopelet} has been irrevocably destroyed. 154 * 155 * @param r a {@link ReferencesSelector}; must not be {@code null} 156 * 157 * @exception NullPointerException if {@code r} is {@code null} 158 */ 159 protected void fireScopeletDestroyed(final ReferencesSelector r) { 160 161 } 162 163 /** 164 * Informs any interested observers that this {@link Scopelet} has just been initialized. 165 * 166 * @param r a {@link ReferencesSelector}; must not be {@code null} 167 * 168 * @exception NullPointerException if {@code r} is {@code null} 169 */ 170 // The specification says scopes should fire an event when they're open for business but there are lots of weird 171 // ramifications to this. We break this out into a protected method so overrides can do what they want, or nothing at 172 // all. 173 protected void fireScopeletInitialized(final ReferencesSelector r) { 174 // final Domain d = r.domain(); 175 // final Events e = r.reference(new AttributedType(d.declaredType(d.typeElement(Events.class.getCanonicalName())), 176 // defaultQualifiers())); 177 // if (e != null) { 178 // e.fire(null, // typeArgumentSource; not needed here; maybe could do wild S reflective introspection 179 // List.of(), // qualifiers/attributes; TODO: @Initialized 180 // this, // event object; can be anything 181 // c); 182 // } 183 } 184 185 /** 186 * Returns this {@link Scopelet} if it has been created via the {@link #create(Creation)} method, or {@code null} if 187 * that method has not yet been invoked. 188 * 189 * @return this {@link Scopelet} if it has been "{@linkplain #create(Creation) created}"; {@code null} otherwise 190 * 191 * @see #create(Creation) 192 */ 193 @Override // Factory<S> 194 public final S singleton() { 195 if (this.closed()) { 196 throw new IllegalStateException("closed"); 197 } 198 return this.me; // volatile read 199 } 200 201 /** 202 * Returns {@code true} when invoked to indicate that {@link Scopelet} implementations {@linkplain 203 * Factory#destroy(Object, org.microbean.bean.Destruction) destroy} what they {@linkplain #create(Creation) create}. 204 * 205 * @return {@code true} when invoked 206 * 207 * @see Factory#destroy(Object, org.microbean.bean.Destruction) 208 * 209 * @see #create(Creation) 210 */ 211 @Override // Factory<S> 212 public final boolean destroys() { 213 return true; 214 } 215 216 217 /* 218 * Repository-like concerns. 219 */ 220 221 222 /** 223 * Returns {@code true} if and only if this {@link Scopelet} is <dfn>active</dfn> at the moment of the call. 224 * 225 * <p>Overrides of this method must ensure that if {@link #closed()} returns {@code true}, this method must return 226 * {@code false}.</p> 227 * 228 * @return {@code true} if and only if this {@link Scopelet} is <dfn>active</dfn> at the moment of the call 229 * 230 * @see #closed() 231 */ 232 public boolean active() { 233 return !this.closed(); // volatile read 234 } 235 236 /** 237 * Checks to see if this {@link Scopelet} {@linkplain #active() is active} and then returns a pre-existing or 238 * created-on-demand contextual instance suitable for the combination of identifier, {@link Factory} and {@link 239 * Creation}, or {@code null} 240 * 241 * @param <I> the type of contextual instance 242 * 243 * @param id an identifier that can identify a contextual instance; may be {@code null} 244 * 245 * @param factory a {@link Factory}; may be {@code null} 246 * 247 * @param creation a {@link Creation}, typically the one in effect that is causing this method to be invoked in the 248 * first place; may be {@code null} 249 * 250 * @return a contextual instance, possibly pre-existing, or possibly created just in time, or {@code null} 251 * 252 * @exception InactiveScopeletException if this {@link Scopelet} {@linkplain #active() is not active} 253 * 254 * @exception ClassCastException if destruction is called for, {@code creation} is non-{@code null}, and {@code 255 * creation} does not implement {@link org.microbean.bean.Destruction}, a requirement of its contract 256 * 257 * @see Creation 258 * 259 * @see org.microbean.bean.Destruction 260 * 261 * @see Factory#destroys() 262 */ 263 public abstract <I> I instance(final Object id, final Factory<I> factory, final Creation<I> creation); 264 265 /** 266 * Checks to see if this {@link Scopelet} {@linkplain #active() is active} and then removes any contextual instance 267 * stored under the supplied {@code id}, returning {@code true} if and only if removal actually took place. 268 * 269 * <p><strong>The default implementation of this method always returns {@code false}.</strong> Subclasses are 270 * encouraged to override it as appropriate.</p> 271 * 272 * @param id an identifier; may be {@code null} 273 * 274 * @return {@code true} if and only if removal actually occurred 275 * 276 * @exception InactiveScopeletException if this {@link Scopelet} {@linkplain #active() is not active} 277 */ 278 public boolean remove(final Object id) { 279 if (!this.active()) { 280 throw new InactiveScopeletException(); 281 } 282 return false; 283 } 284 285 /** 286 * Returns {@code true} if and only if this {@link Scopelet} stores contextual instances, and hence is capable of 287 * {@linkplain #remove(Object) removing} them. 288 * 289 * <p><strong>The default implementation of this method returns {@code false}.</strong> Subclasses are encouraged to 290 * override it as appropriate.</p> 291 * 292 * @return {@code true} if and only if this {@link Scopelet} stores contextual instances, and hence is capable of 293 * {@linkplain #remove(Object) removing} them 294 * 295 * @exception InactiveScopeletException if this {@link Scopelet} is not {@linkplain #active() active} 296 * 297 * @see #remove(Object) 298 */ 299 public boolean removes() { 300 if (!this.active()) { 301 throw new InactiveScopeletException(); 302 } 303 return false; 304 } 305 306 /** 307 * Irrevocably closes this {@link Scopelet}, and, by doing so, notionally makes it irrevocably {@linkplain #closed() 308 * closed} and {@linkplain #active() inactive}. 309 * 310 * <p>Overrides of this method <strong>must</strong> call {@link Scopelet#close() super.close()} as part of their 311 * implementation or undefined behavior may result.</p> 312 * 313 * @see #closed() 314 * 315 * @see #active() 316 */ 317 @Override // AutoCloseable 318 public void close() { 319 CLOSED.compareAndSet(this, false, true); // volatile write 320 } 321 322 /** 323 * Returns {@code true} if and only if at the moment of invocation this {@link Scopelet} is (irrevocably) closed (and 324 * therefore also {@linkplain #active() not active}). 325 * 326 * @return {@code true} if and only if at the moment of invocation this {@link Scopelet} is (irrevocably) closed (and 327 * therefore also {@linkplain #active() not active}) 328 * 329 * @see #active() 330 */ 331 protected final boolean closed() { 332 return this.closed; // volatile read 333 } 334 335}