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;
017import java.lang.invoke.MethodHandles.Lookup;
018import java.lang.invoke.VarHandle;
019
020import java.util.Objects;
021
022import org.microbean.bean.Bean;
023import org.microbean.bean.Id;
024import org.microbean.bean.Factory;
025import org.microbean.bean.Request;
026
027import org.microbean.qualifier.NamedAttributeMap;
028
029import org.microbean.scope.ScopeMember;
030
031import static org.microbean.scope.Scope.SINGLETON_ID;
032
033/**
034 * A manager of object lifespans identified by a {@linkplain org.microbean.scope.Scope scope}.
035 *
036 * @param <S> the {@link Scopelet} subtype extending this class
037 *
038 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a>
039 *
040 * @see org.microbean.scope.Scope
041 *
042 * @see ScopeMember
043 */
044public abstract class Scopelet<S extends Scopelet<S>> implements AutoCloseable, Factory<S>, ScopeMember {
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 = MethodHandles.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  /*
068   * Instance fields.
069   */
070
071
072  private volatile S me;
073
074  private volatile boolean closed;
075
076  private final NamedAttributeMap<?> scopeId;
077
078
079  /*
080   * Constructors.
081   */
082
083
084  /**
085   * Creates a new {@link Scopelet}.
086   *
087   * @param scopeId a {@link NamedAttributeMap} identifying the scope being implemented; must not be {@code null}
088   *
089   * @exception NullPointerException if {@code scopeId} is {@code null}
090   */
091  protected Scopelet(final NamedAttributeMap<?> scopeId) {
092    super();
093    this.scopeId = Objects.requireNonNull(scopeId, "scopeId");
094  }
095
096
097  /*
098   * Instance methods.
099   */
100
101
102  /**
103   * Returns an {@link Id} representing this {@link Scopelet}.
104   *
105   * <p>Implementations of this method must return determinate values.</p>
106   *
107   * @return an {@link Id} representing this {@link Scopelet}; never {@code null}
108   *
109   * @see #governingScopeId()
110   *
111   * @see #bean()
112   */
113  public abstract Id id();
114
115  /**
116   * Returns a {@link Bean} for this {@link Scopelet}.
117   *
118   * <p>This {@link Scopelet} will be used as the {@link Bean}'s {@linkplain Bean#factory() associated
119   * <code>Factory</code>}. This {@link Scopelet}'s {@link #id() Id} will be used as the {@link Bean}'s {@linkplain
120   * Bean#id() identifier}.</p>
121   *
122   * @return a {@link Bean} for this {@link Scopelet}; never {@code null}
123   *
124   * @see #id()
125   */
126  public final Bean<S> bean() {
127    return new Bean<>(this.id(), this);
128  }
129
130  /**
131   * Creates this {@link Scopelet} by simply returning it.
132   *
133   * @return this {@link Scopelet}
134   */
135  @Override // Factory<S>
136  @SuppressWarnings("unchecked")
137  public final S create(final Request<S> r) {
138    if (ME.compareAndSet(this, null, this)) { // volatile write
139      if (r != null) {
140        // TODO: emit initialized event
141      }
142    }
143    return (S)this;
144  }
145
146  /**
147   * Returns this {@link Scopelet} if it has been created via the {@link #create(Request)} method, or {@code null} if
148   * that method has not yet been invoked.
149   *
150   * @return this {@link Scopelet} if it has been "{@linkplain #create(Request) created}"; {@code null} otherwise
151   *
152   * @see #create(Request)
153   */
154  @Override // Factory<S>
155  public final S singleton() {
156    return this.me; // volatile read
157  }
158
159  /**
160   * Returns {@code true} when invoked to indicate that {@link Scopelet} implementations {@linkplain
161   * Factory#destroy(Object, Request) destroy} what they {@linkplain #create(Request) create}.
162   *
163   * @return {@code true} when invoked
164   *
165   * @see Factory#destroy(Object, Request)
166   *
167   * @see #create(Request)
168   */
169  @Override // Factory<S>
170  public final boolean destroys() {
171    return true;
172  }
173
174  /**
175   * Returns a hashcode for this {@link Scopelet}.
176   *
177   * @return a hashcode for this {@link Scopelet}
178   */
179  @Override // Object
180  public int hashCode() {
181    int hashCode = 17;
182    hashCode = 31 * hashCode + this.id().hashCode();
183    hashCode = 31 * hashCode + this.scopeId().hashCode();
184    return hashCode;
185  }
186
187  /**
188   * Returns {@code true} if and only if the supplied {@link Object} is not {@code null}, has the same class as this
189   * {@link Scopelet}, has an {@link #id() Id} {@linkplain Id#equals(Object) equal to} that of this {@link Scopelet},
190   * and a {@linkplain #scopeId() scope identifier} {@linkplain NamedAttributeMap#equals(Object) equal to} that of this
191   * {@link Scopelet}.
192   *
193   * @param other the {@link Object} to test; may be {@code null}
194   *
195   * @return {@code true} if and only if the supplied {@link Object} is not {@code null}, has the same class as this
196   * {@link Scopelet}, has an {@link #id() Id} {@linkplain Id#equals(Object) equal to} that of this {@link Scopelet},
197   * and a {@linkplain #scopeId() scope identifier} {@linkplain NamedAttributeMap#equals(Object) equal to} that of this
198   * {@link Scopelet}
199   */
200  @Override // Object
201  public boolean equals(final Object other) {
202    if (other == this) {
203      return true;
204    } else if (other != null && other.getClass().equals(this.getClass())) {
205      final Scopelet<?> her = (Scopelet<?>)other;
206      return
207        Objects.equals(this.id(), her.id()) &&
208        Objects.equals(this.scopeId(), her.scopeId());
209    } else {
210      return false;
211    }
212  }
213
214  /**
215   * Returns the {@link NamedAttributeMap} representing the identifier of the scope to which this {@link Scopelet}
216   * belongs.
217   *
218   * @return the {@link NamedAttributeMap} representing the identifier of the scope to which this {@link Scopelet}
219   * belongs; never {@code null}
220   *
221   * @see ScopeMember
222   */
223  @Override // ScopeMember
224  public final NamedAttributeMap<?> governingScopeId() {
225    return this.id().governingScopeId();
226  }
227
228  /**
229   * Returns {@code true} if this {@link Scopelet} is governed by the scope represented by the supplied {@link
230   * NamedAttributeMap}.
231   *
232   * @param scopeId a {@link NamedAttributeMap} identifying a scope; must not be {@code null}
233   *
234   * @return {@code true} if this {@link Scopelet} is governed by the scope represented by the supplied {@link
235   * NamedAttributeMap}
236   *
237   * @exception NullPointerException if {@code scopeId} is {@code null}
238   */
239  @Override // ScopeMember
240  public final boolean governedBy(final NamedAttributeMap<?> scopeId) {
241    return this.id().governedBy(scopeId);
242  }
243
244
245  /*
246   * Repository-like concerns.
247   */
248
249
250  /**
251   * Returns the {@link NamedAttributeMap} that identifies this {@link Scopelet}'s affiliated scope.
252   *
253   * @return the {@link NamedAttributeMap} that identifies this {@link Scopelet}'s affiliated scope; never {@code null}
254   */
255  public final NamedAttributeMap<?> scopeId() {
256    return this.scopeId;
257  }
258
259  /**
260   * Returns {@code true} if and only if this {@link Scopelet} is <dfn>active</dfn> at the moment of the call.
261   *
262   * <p>Overrides of this method must ensure that if {@link #closed()} returns {@code true}, this method must return
263   * {@code false}.</p>
264   *
265   * @return {@code true} if and only if this {@link Scopelet} is <dfn>active</dfn> at the moment of the call
266   *
267   * @see #closed()
268   */
269  public boolean active() {
270    return !this.closed(); // volatile read
271  }
272
273  /**
274   * Checks to see if this {@link Scopelet} {@linkplain #active() is active} and then returns {@code true} if and only
275   * if, at the moment of an invocation, this {@link Scopelet} {@linkplain #active() is active} and already contains an
276   * object identified by the supplied {@link Object}.
277   *
278   * <p>The default implementation of this method checks to see if this {@link Scopelet} {@linkplain #active() is
279   * active}, and then {@code true} if and only if the result of invoking the {@link #instance(Object, Factory,
280   * Request)} method with the supplied {@code id}, {@code null}, and {@code null} is not {@code null}.</p>
281   *
282   * <p>Subclasses are encouraged to override this method to be more efficient or to use a different algorithm.</p>
283   *
284   * @param id the {@link Object} serving as an identifier; may be {@code null} in certain pathological cases
285   *
286   * @return {@code true} if and only if, at the moment of an invocation, this {@link Scopelet} {@linkplain #active() is
287   * active} and contains a preexisting object identified by the supplied {@link Object}
288   *
289   * @exception InactiveScopeletException if this {@link Scopelet} {@linkplain #active() is not active}
290   *
291   * @see #active()
292   *
293   * @see #instance(Object, Factory, Request)
294   */
295  public boolean containsId(final Object id) {
296    return (id instanceof Request<?> r ? this.instance(r) : this.instance(id, null, null)) != null;
297  }
298
299  /**
300   * Checks to see if this {@link Scopelet} {@linkplain #active() is active}, and then returns the preexisting
301   * contextual instance identified by the supplied {@link Object}, or {@code null} if no such instance exists.
302   *
303   * <p>This convenience method checks to see if this {@link Scopelet} {@linkplain #active() is active}, and then, if
304   * the supplied {@link Object} is not a {@link Request}, calls the {@link #instance(Object, Factory, Request)} method
305   * with the supplied {@code id}, {@code null}, and {@code null}, and returns its result.</p>
306   *
307   * <p>If the supplied {@link Object} <em>is</em> a {@link Request}, this method calls the {@link #instance(Request)}
308   * method with the supplied (cast) {@code id} and returns its result.</p>
309   *
310   * @param <I> the type of contextual instance
311   *
312   * @param id an {@link Object} serving as an identifier; may be {@code null} in certain pathological cases
313   *
314   * @return the contextual instance identified by the supplied {@link Object}, or {@code null} if no such instance
315   * exists
316   *
317   * @exception InactiveScopeletException if this {@link Scopelet} {@linkplain #active() is not active}
318   *
319   * @see #instance(Object, Factory, Request)
320   *
321   * @see #instance(Request)
322   */
323  // id is nullable.
324  @SuppressWarnings("unchecked")
325  public final <I> I get(final Object id) {
326    return id instanceof Request<?> r ? this.instance((Request<I>)r) : this.instance(id, null, null);
327  }
328
329  /**
330   * Checks to see if this {@link Scopelet} {@linkplain #active() is active} and then eturns a contextual instance
331   * identified by the {@linkplain Request#beanReduction() identifying information} present within the supplied {@link
332   * Request}, creating the instance and associating it with the {@linkplain Request#beanReduction() identifying
333   * information} present within the supplied {@link Request} if necessary.
334   *
335   * @param <I> the type of contextual instance
336   *
337   * @param request a {@link Request}; may be {@code null} in which case the return value of an invocation of {@link
338   * #instance(Object, Factory, Request)} with {@code null} supplied for all three arguments will be returned instead
339   *
340   * @return an appropriate contextual instance, or {@code null}
341   *
342   * @exception InactiveScopeletException if this {@link Scopelet} {@linkplain #active() is not active}
343   *
344   * @see Request#beanReduction()
345   *
346   * @see #instance(Object, Factory, Request)
347   */
348  public final <I> I instance(final Request<I> request) {
349    if (request == null) {
350      return this.instance(null, null, null);
351    }
352    final Bean<I> bean = request.beanReduction().bean();
353    return this.instance(bean.id(), bean.factory(), request);
354  }
355
356  /**
357   * Checks to see if this {@link Scopelet} {@linkplain #active() is active} and then returns a pre-existing or
358   * created-on-demand contextual instance suitable for the combination of identifier, {@link Factory} and {@link
359   * Request}.
360   *
361   * @param <I> the type of contextual instance
362   *
363   * @param id an identifier that can identify a contextual instance; may be {@code null}
364   *
365   * @param factory a {@link Factory}; may be {@code null}
366   *
367   * @param request a {@link Request}, typically the one in effect that is causing this method to be invoked in the
368   * first place; may be {@code null}
369   *
370   * @return a contextual instance, possibly pre-existing, or possibly created just in time, or {@code null}
371   *
372   * @exception InactiveScopeletException if this {@link Scopelet} {@linkplain #active() is not active}
373   */
374  // All parameters are nullable, perhaps pathologically. This helps permit super early bootstrapping.
375  public abstract <I> I instance(final Object id, final Factory<I> factory, final Request<I> request);
376
377  /**
378   * Checks to see if this {@link Scopelet} {@linkplain #active() is active} and then removes any contextual instance
379   * stored under the supplied {@code id}, returning {@code true} if and only if removal actually took place.
380   *
381   * <p>The default implementation of this method always returns {@code false}. Subclasses are encouraged to override
382   * it as appropriate.</p>
383   *
384   * @param id an identifier; may be {@code null}
385   *
386   * @return {@code true} if and only if removal actually occurred
387   *
388   * @exception InactiveScopeletException if this {@link Scopelet} {@linkplain #active() is not active}
389   */
390  // id is nullable.
391  public boolean remove(final Object id) {
392    if (!this.active()) {
393      throw new InactiveScopeletException();
394    }
395    return false;
396  }
397
398  /**
399   * Irrevocably closes this {@link Scopelet}, and, by doing so, notionally makes it irrevocably {@linkplain #active()
400   * inactive}.
401   *
402   * @see #closed()
403   *
404   * @see #active()
405   */
406  // Most scopelets will want to override this to do additional work. They must call super.close() to ensure #closed()
407  // returns an appropriate value.
408  @Override // AutoCloseable
409  public void close() {
410    CLOSED.compareAndSet(this, false, true); // volatile write
411  }
412
413  /**
414   * Returns {@code true} if and only if at the moment of invocation this {@link Scopelet} is (irrevocably) closed (and
415   * therefore also {@linkplain #active() not active}).
416   *
417   * @return {@code true} if and only if at the moment of invocation this {@link Scopelet} is (irrevocably) closed (and
418   * therefore also {@linkplain #active() not active})
419   */
420  protected final boolean closed() {
421    return this.closed; // volatile read
422  }
423
424}