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