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}