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}