001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
002 *
003 * Copyright © 2025–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.util.ArrayDeque;
017import java.util.Collection;
018import java.util.Iterator;
019import java.util.List;
020import java.util.Map;
021import java.util.Objects;
022import java.util.Queue;
023import java.util.Set;
024
025import java.util.function.BiFunction;
026import java.util.function.BooleanSupplier;
027import java.util.function.Supplier;
028
029import javax.lang.model.AnnotatedConstruct;
030
031import javax.lang.model.element.AnnotationMirror;
032import javax.lang.model.element.Element;
033
034import javax.lang.model.type.TypeMirror;
035
036import org.microbean.assign.Annotated;
037import org.microbean.assign.Selectable;
038
039import org.microbean.bean.AmbiguousResolutionException;
040import org.microbean.bean.Bean;
041import org.microbean.bean.Creation;
042import org.microbean.bean.Factory;
043import org.microbean.bean.Id;
044import org.microbean.bean.ReferencesSelector;
045
046import org.microbean.construct.Domain;
047
048import org.microbean.construct.element.SyntheticAnnotationMirror;
049import org.microbean.construct.element.SyntheticAnnotationTypeElement;
050
051import org.microbean.construct.type.UniversalType;
052
053import org.microbean.reference.Instances;
054
055import static java.util.Objects.requireNonNull;
056
057import static org.microbean.construct.element.AnnotationMirrors.sameAnnotation;
058
059/**
060 * An {@link Instances} implementation that is based on scopes.
061 *
062 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a>
063 *
064 * @see #supplier(Bean, Creation)
065 *
066 * @see Instances
067 */
068public class ScopedInstances implements Instances {
069
070
071  /*
072   * Instance fields.
073   */
074
075  
076  private final Domain domain;
077  
078  private final org.microbean.bean.Qualifiers bq;
079
080  private final Qualifiers sq;
081
082  private final Scopes scopes;
083  
084  private final TypeMirror scopeletType;
085
086  // Deliberately not a scope or a qualifier
087  private final AnnotationMirror considerActiveness;
088
089
090  /*
091   * Constructors.
092   */
093
094
095  /**
096   * Creates a new {@link ScopedInstances}.
097   *
098   * @param domain a non-{@code null} {@link Domain}
099   *
100   * @param bq a non-{@code null} {@link org.microbean.bean.Qualifiers}
101   *
102   * @param sq a non-{@code null} {@link Qualifiers}
103   *
104   * @param scopes a non-{@code null} {@link Scopes}
105   *
106   * @param considerActiveness an {@link AnnotationMirror} used to signal that <dfn>activeness</dfn> should be taken
107   * into consideration during typesafe resolution; may be {@code null}
108   *
109   * @exception NullPointerException if any argument is {@code null}
110   */
111  public ScopedInstances(final Domain domain,
112                         final org.microbean.bean.Qualifiers bq,
113                         final Qualifiers sq,
114                         final Scopes scopes,
115                         final AnnotationMirror considerActiveness) {
116    super();
117    this.domain = domain;
118    this.scopeletType =
119      domain.declaredType(null, domain.typeElement(Scopelet.class.getCanonicalName()), domain.wildcardType());
120    this.scopes = requireNonNull(scopes, "scopes");
121    this.bq = bq;
122    this.sq = requireNonNull(sq, "sq");
123    this.considerActiveness =
124      considerActiveness == null ?
125      new SyntheticAnnotationMirror(new SyntheticAnnotationTypeElement("ConsiderActiveness")) :
126      considerActiveness;
127  }
128
129
130  /*
131   * Instance methods.
132   */
133
134
135  /**
136   * Returns {@code true} if and only if the supplied {@link Id} is <dfn>proxiable</dfn>.
137   *
138   * @param id an {@link Id}; must not be {@code null}
139   *
140   * @return {@code true} if and only if the supplied {@link Id} is <dfn>proxiable</dfn>
141   *
142   * @exception NullPointerException if {@code id} is {@code null}
143   */
144  @Override // Instances
145  public boolean proxiable(final Id id) {
146    return id.types().proxiable() && this.findNormalScope(id) != null;
147  }
148
149  /**
150   * Returns a {@link Selectable Selectable&lt;AnnotatedConstruct, Bean&lt;?&gt;&gt;} that properly considers the fact
151   * that a {@link Scopelet} may be {@linkplain Scopelet#active() active or inactive} at any point for any reason.
152   *
153   * @param selectable a {@link Selectable} that will be used for all {@link AnnotatedConstruct}s other than {@link
154   * Scopelet} types being sought for the purpose of instantiating or acquiring contextual instances; must not be
155   * {@code null}
156   *
157   * @return a non-{@code null} {@link Selectable}
158   *
159   * @exception  NullPointerException if any argument is {@code null}
160   */
161  public final Selectable<Annotated<? extends AnnotatedConstruct>, Bean<?>> selectableOf(final Selectable<? super Annotated<? extends AnnotatedConstruct>, Bean<?>> selectable) {
162    requireNonNull(selectable, "selectable");
163    final Selectable<Annotated<? extends AnnotatedConstruct>, Bean<?>> scopeletSelectable = aac -> {
164      Bean<?> activeScopeletBean = null;
165      for (final Bean<?> b : selectable.select(aac)) {
166        if (((Scopelet<?>)b.factory()).active()) {
167          if (activeScopeletBean == null) {
168            activeScopeletBean = b;
169          } else {
170            throw new TooManyActiveScopeletsException("scopelet1: " + activeScopeletBean + "; scopelet: " + b);
171          }
172        }
173      }
174      return activeScopeletBean == null ? List.of() : List.of(activeScopeletBean);
175    };
176    return aac ->
177      this.domain.sameType(this.scopeletType, type(aac)) && this.considerActiveness(aac.annotations()) ?
178      // A ScopedInstances is requesting a Scopelet for the purposes of instantiating something else. Use the
179      // scopeletSelectable.
180      scopeletSelectable.select(aac) :
181      // A ScopedInstances is requesting something "normal". Use the unadorned supplied Selectable.
182      selectable.select(aac);
183  }
184
185  private static final TypeMirror type(final Annotated<? extends AnnotatedConstruct> a) {
186    final AnnotatedConstruct ac = a.annotated();
187    if (ac instanceof TypeMirror t) {
188      return t;
189    }
190    return ((Element)ac).asType();
191  }
192
193  @Override // Instances
194  public final <I> Supplier<? extends I> supplier(final Bean<I> bean, final Creation<I> request) {
195    final Id id = bean.id();
196    final AnnotationMirror scopeId = this.findScope(id);
197    // In this implementation, all ids must have scopes.
198    if (scopeId == null) {
199      throw new IllegalStateException();
200    }
201    final Factory<I> factory = bean.factory();
202    if (factory instanceof Scopelet<?> && this.primordial(scopeId)) {
203      // This is a request for, e.g., the Singleton Scopelet, which backs the primordial (notional) singleton scope.
204      // Scopelets are always their own factories. The Scopelet implementing the primordial scope (normally Singleton)
205      // is not made or stored by any other Scopelet.
206      I scopelet = factory.singleton();
207      if (scopelet == null) {
208        return () -> factory.create(request);
209      }
210      assert scopelet == factory : "scopelet != factory: " + scopelet + " != " + factory;
211      return factory::singleton;
212    }
213    final Annotated<TypeMirror> ast = this.annotatedScopeletType(scopeId);
214    // Get the Scopelet and have it provide the instance
215    return () -> {
216      return request.<Scopelet<?>>reference(ast)
217        .instance(id, factory, request); // assumes Scopelet inactivity is handled
218    };
219  }
220
221  /**
222   * Returns {@code true} if and only if the supplied {@link Collection} of {@link AnnotationMirror}s is deemed to
223   * designate something as <dfn>primordial</dfn>.
224   *
225   * <p>The default implementation of this method returns {@code true} if and only if the supplied {@link Collection}
226   * contains an {@link AnnotationMirror} that is the {@linkplain
227   * org.microbean.construct.element.AnnotationMirrors#sameAnnotation(AnnotationMirror, AnnotationMirror) same
228   * annotation} as the {@link org.microbean.bean.Qualifiers#primordialQualifier() primordial qualifier}.</p>
229   *
230   * @param c a {@link Collection} of {@link AnnotationMirror}s; must not be {@code null}
231   *
232   * @return {@code true} if and only if the supplied {@link Collection} of {@link AnnotationMirror}s is deemed to
233   * designate something as <dfn>primordial</dfn>
234   *
235   * @exception NullPointerException if {@code c} is {@code null}
236   *
237   * @see Qualifiers#primordialQualifier()
238   */
239  private final boolean primordial(final Collection<? extends AnnotationMirror> c) {
240    for (final AnnotationMirror a : c) {
241      if (this.sq.primordialMetaQualifier(a)) {
242        return true;
243      }
244    }
245    return false;
246  }
247
248  private final boolean primordial(final AnnotationMirror a) {
249    return this.primordial(a.getAnnotationType().asElement().getAnnotationMirrors());
250  }
251
252  private final AnnotationMirror findNormalScope(final Id id) {
253    AnnotationMirror scopeId = null;
254    for (final AnnotationMirror a : id.annotations()) {
255      if (this.bq.anyQualifier(a)) {
256        scopeId = this.scopes.findNormalScope(a.getAnnotationType().asElement().getAnnotationMirrors());
257        break;
258      }
259    }
260    return scopeId;
261  }
262  
263  private final AnnotationMirror findScope(final Id id) {
264    // Looks for an Any qualifier, which every bean must possess, and then looks on *it* for the scope. This allows us
265    // to "tunnel" scopes (which are Qualifiers in this implementation) without disrupting typesafe resolution, since
266    // meta-annotations are not part of an AnnotationMirror's equality computation.
267    AnnotationMirror scopeId = null;
268    for (final AnnotationMirror a : id.annotations()) {
269      if (this.bq.anyQualifier(a)) {
270        scopeId = this.scopes.findScope(a.getAnnotationType().asElement().getAnnotationMirrors());
271        break;
272      }
273    }
274    if (scopeId == null) {
275      throw new IllegalArgumentException("id: " + id);
276    }
277    return scopeId;
278  }
279
280  private final Annotated<TypeMirror> annotatedScopeletType(final AnnotationMirror scopeId) {
281    return Annotated.of(new UniversalType(List.of(scopeId, this.considerActiveness),
282                                          this.scopeletType,
283                                          this.domain));
284  }
285
286  private final boolean considerActiveness(final Collection<? extends AnnotationMirror> c) {
287    for (final AnnotationMirror a : c) {
288      if (sameAnnotation(this.considerActiveness, a)) {
289        return true;
290      }
291    }
292    return false;
293  }
294
295}