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.attributes.Attributes;
023import org.microbean.attributes.BooleanValue;
024import org.microbean.attributes.Value;
025
026import org.microbean.bean.Bean;
027import org.microbean.bean.Creation;
028import org.microbean.bean.Factory;
029
030import static java.lang.invoke.MethodHandles.lookup;
031
032import static org.microbean.assign.Qualifiers.primordialQualifier;
033import static org.microbean.assign.Qualifiers.qualifier;
034
035/**
036 * A manager of object lifespans on behalf of one or more notional <dfn>scopes</dfn>.
037 *
038 * @param <S> the {@link Scopelet} subtype extending this class
039 *
040 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a>
041 *
042 * @see #instance(Object, Factory, Creation)
043 */
044public abstract class Scopelet<S extends Scopelet<S>> implements AutoCloseable, Factory<S> {
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 = 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   * An {@link Attributes} identifying the <dfn>scope designator</dfn>.
068   */
069  public static final Attributes SCOPE = Attributes.of("Scope");
070
071  private static final Map<String, Value<?>> normalScope = Map.of("normal", BooleanValue.of(true));
072
073  private static final Map<String, Value<?>> pseudoScope = Map.of("normal", BooleanValue.of(false));
074
075  /**
076   * An {@link Attributes} identifying the (well-known) <dfn>singleton pseudo-scope</dfn>.
077   *
078   * <p>The {@link Attributes} constituting the singleton pseudo-scope identifier is {@linkplain Attributes#attributes()
079   * attributed} with {@linkplain #SCOPE the scope designator}, {@linkplain org.microbean.assign.Qualifiers#qualifier()
080   * the qualifier designator}, and {@linkplain org.microbean.assign.Qualifiers#primordialQualifier() the primordial
081   * qualifier}, indicating that the scope it identifies governs itself.</p>
082   */
083  public static final Attributes SINGLETON_ID =
084    Attributes.of("Singleton", pseudoScope, Map.of(), Map.of("Singleton", List.of(qualifier(), SCOPE, primordialQualifier())));
085
086  /**
087   * An {@link Attributes} identifying the (well-known and <dfn>normal</dfn>) <dfn>application scope</dfn>.
088   */
089  public static final Attributes APPLICATION_ID =
090    Attributes.of("Application", normalScope, Map.of(), Map.of("Application", List.of(qualifier(), SCOPE, SINGLETON_ID)));
091
092  /**
093   * An {@link Attributes} identifying the (well-known) <dfn>none pseudo-scope</dfn>.
094   */
095  public static final Attributes NONE_ID =
096    Attributes.of("None", pseudoScope, Map.of(), Map.of("None", List.of(qualifier(), SCOPE, SINGLETON_ID)));
097
098
099  /*
100   * Instance fields.
101   */
102
103
104  private volatile S me;
105
106  private volatile boolean closed;
107
108
109  /*
110   * Constructors.
111   */
112
113
114  /**
115   * Creates a new {@link Scopelet}.
116   */
117  protected Scopelet() {
118    super();
119  }
120
121
122  /*
123   * Instance methods.
124   */
125
126
127  /**
128   * Creates this {@link Scopelet} by simply returning it.
129   *
130   * @return this {@link Scopelet}
131   */
132  @Override // Factory<S>
133  @SuppressWarnings("unchecked")
134  public final S create(final Creation<S> r) {
135    if (ME.compareAndSet(this, null, this)) { // volatile write
136      if (r != null) {
137        // TODO: emit initialized event
138      }
139    }
140    return (S)this;
141  }
142
143  /**
144   * Returns this {@link Scopelet} if it has been created via the {@link #create(Creation)} method, or {@code null} if
145   * that method has not yet been invoked.
146   *
147   * @return this {@link Scopelet} if it has been "{@linkplain #create(Creation) created}"; {@code null} otherwise
148   *
149   * @see #create(Creation)
150   */
151  @Override // Factory<S>
152  public final S singleton() {
153    return this.me; // volatile read
154  }
155
156  /**
157   * Returns {@code true} when invoked to indicate that {@link Scopelet} implementations {@linkplain
158   * Factory#destroy(Object, org.microbean.bean.Destruction) destroy} what they {@linkplain #create(Creation) create}.
159   *
160   * @return {@code true} when invoked
161   *
162   * @see Factory#destroy(Object, org.microbean.bean.Destruction)
163   *
164   * @see #create(Creation)
165   */
166  @Override // Factory<S>
167  public final boolean destroys() {
168    return true;
169  }
170
171
172  /*
173   * Repository-like concerns.
174   */
175
176
177  /**
178   * Returns {@code true} if and only if this {@link Scopelet} is <dfn>active</dfn> at the moment of the call.
179   *
180   * <p>Overrides of this method must ensure that if {@link #closed()} returns {@code true}, this method must return
181   * {@code false}.</p>
182   *
183   * @return {@code true} if and only if this {@link Scopelet} is <dfn>active</dfn> at the moment of the call
184   *
185   * @see #closed()
186   */
187  public boolean active() {
188    return !this.closed(); // volatile read
189  }
190
191  /**
192   * Checks to see if this {@link Scopelet} {@linkplain #active() is active} and then returns a pre-existing or
193   * created-on-demand contextual instance suitable for the combination of identifier, {@link Factory} and {@link
194   * Creation}, or {@code null}
195   *
196   * @param <I> the type of contextual instance
197   *
198   * @param id an identifier that can identify a contextual instance; may be {@code null}
199   *
200   * @param factory a {@link Factory}; may be {@code null}
201   *
202   * @param creation a {@link Creation}, typically the one in effect that is causing this method to be invoked in the
203   * first place; may be {@code null}
204   *
205   * @return a contextual instance, possibly pre-existing, or possibly created just in time, or {@code null}
206   *
207   * @exception InactiveScopeletException if this {@link Scopelet} {@linkplain #active() is not active}
208   *
209   * @exception ClassCastException if {@code creation} is non-{@code null} and does not implement {@link
210   * org.microbean.bean.Destruction}, a requirement of its contract
211   *
212   * @see Creation
213   */
214  public abstract <I> I instance(final Object id, final Factory<I> factory, final Creation<I> creation);
215
216  /**
217   * Checks to see if this {@link Scopelet} {@linkplain #active() is active} and then removes any contextual instance
218   * stored under the supplied {@code id}, returning {@code true} if and only if removal actually took place.
219   *
220   * <p><strong>The default implementation of this method always returns {@code false}.</strong> Subclasses are
221   * encouraged to override it as appropriate.</p>
222   *
223   * @param id an identifier; may be {@code null}
224   *
225   * @return {@code true} if and only if removal actually occurred
226   *
227   * @exception InactiveScopeletException if this {@link Scopelet} {@linkplain #active() is not active}
228   */
229  public boolean remove(final Object id) {
230    if (!this.active()) {
231      throw new InactiveScopeletException();
232    }
233    return false;
234  }
235
236  /**
237   * Irrevocably closes this {@link Scopelet}, and, by doing so, notionally makes it irrevocably {@linkplain #closed()
238   * closed} and {@linkplain #active() inactive}.
239   *
240   * <p>Overrides of this method <strong>must</strong> call {@link Scopelet#close() super.close()} as part of their
241   * implementation or undefined behavior may result.</p>
242   *
243   * @see #closed()
244   *
245   * @see #active()
246   */
247  @Override // AutoCloseable
248  public void close() {
249    CLOSED.compareAndSet(this, false, true); // volatile write
250  }
251
252  /**
253   * Returns {@code true} if and only if at the moment of invocation this {@link Scopelet} is (irrevocably) closed (and
254   * therefore also {@linkplain #active() not active}).
255   *
256   * @return {@code true} if and only if at the moment of invocation this {@link Scopelet} is (irrevocably) closed (and
257   * therefore also {@linkplain #active() not active})
258   *
259   * @see #active()
260   */
261  protected final boolean closed() {
262    return this.closed; // volatile read
263  }
264
265}