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.ArrayList;
017import java.util.Collection;
018import java.util.List;
019import java.util.Map;
020
021import javax.lang.model.element.AnnotationMirror;
022import javax.lang.model.element.Element;
023import javax.lang.model.element.Name;
024import javax.lang.model.element.VariableElement;
025
026// import org.microbean.bean.Qualifiers;
027
028import org.microbean.construct.Domain;
029
030import org.microbean.construct.element.SyntheticAnnotationMirror;
031import org.microbean.construct.element.SyntheticAnnotationTypeElement;
032import org.microbean.construct.element.SyntheticAnnotationValue;
033
034import static java.util.Objects.requireNonNull;
035
036import static java.util.function.Predicate.not;
037
038import static javax.lang.model.element.ElementKind.ENUM_CONSTANT;
039import static javax.lang.model.element.ElementKind.METHOD;
040
041import static org.microbean.construct.element.AnnotationMirrors.sameAnnotation;
042import static org.microbean.construct.element.AnnotationMirrors.streamBreadthFirst;
043
044/**
045 * A utility class for working with <dfn>scopes</dfn> and their identifiers.
046 *
047 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a>
048 */
049public class Scopes { // deliberately not final
050
051  private final AnnotationMirror metaNormalScope;
052  
053  private final AnnotationMirror metaPseudoScope;
054
055  private final AnnotationMirror noneScope;
056
057  private final AnnotationMirror singletonScope;
058
059  private final org.microbean.assign.Qualifiers aq;
060  
061  private final Qualifiers sq;
062
063  /**
064   * Creates a new {@link Scopes}.
065   *
066   * @param d a non-{@code null} {@link Domain}
067   *
068   * @param aq a non-{@code null} {@link org.microbean.assign.Qualifiers}
069   *
070   * @param sq a non-{@code null} {@link Qualifiers}
071   *
072   * @exception NullPointerException if any argument is {@code null}
073   *
074   * @see #Scopes(Domain, org.microbean.assign.Qualifiers, Qualifiers, AnnotationMirror, AnnotationMirror, AnnotationMirror,
075   * AnnotationMirror)
076   */
077  public Scopes(final Domain d, final org.microbean.assign.Qualifiers aq, final Qualifiers sq) {
078    this(d, aq, sq, null, null, null, null);
079  }
080
081  /**
082   * Creates a new {@link Scopes}.
083   *
084   * @param aq a non-{@code null} {@link org.microbean.assign.Qualifiers}
085   *
086   * @param sq a non-{@code null} {@link Qualifiers}
087   *
088   * @param metaNormalScope a non-{@code null} {@link AnnotationMirror} designating another {@link AnnotationMirror} as
089   * identifying a <dfn>normal scope</dfn>
090   *
091   * @param metaPseudoScope a non-{@code null} {@link AnnotationMirror} designating another {@link AnnotationMirror} as
092   * identifying a <dfn>pseudo scope</dfn>
093   *
094   * @param singletonScope a non-{@code null} {@link AnnotationMirror} identifying the <dfn>singleton scope</dfn>
095   *
096   * @param noneScope a non-{@code null} {@link AnnotationMirror} identifying the <dfn>none scope</dfn>
097   *
098   * @exception NullPointerException if any argument is {@code null}
099   *
100   * @see #Scopes(Domain, org.mcirobean.assign.Qualifiers, Qualifires, AnnotationMirror, AnnotationMirror,
101   * AnnotationMirror, AnnotationMirror)
102   */
103  public Scopes(final org.microbean.assign.Qualifiers aq,
104                final Qualifiers sq,
105                final AnnotationMirror metaNormalScope,
106                final AnnotationMirror metaPseudoScope,
107                final AnnotationMirror singletonScope,
108                final AnnotationMirror noneScope) {
109    this(null,
110         aq,
111         sq,
112         requireNonNull(metaNormalScope, "metaNormalScope"),
113         requireNonNull(metaPseudoScope, "metaPseudoScope"),
114         requireNonNull(singletonScope, "singletonScope"),
115         requireNonNull(noneScope, "noneScope"));
116  }
117  
118  /**
119   * Creates a new {@link Scopes}.
120   *
121   * @param d a {@link Domain}; if {@code null}, then {@code metaNormalScope}, {@code metaPseudoScope}, {@code
122   * singletonScope}, and {@code noneScope} must be non-{@code null}
123   *
124   * @param aq a non-{@code null} {@link org.microbean.assign.Qualifiers}
125   *
126   * @param sq a non-{@code null} {@link Qualifiers}
127   *
128   * @param metaNormalScope an {@link AnnotationMirror} designating another {@link AnnotationMirror} as identifying a
129   * <dfn>normal scope</dfn>; may be {@code null} in which case {@code d} must be non-{@code null}
130   *
131   * @param metaPseudoScope an {@link AnnotationMirror} designating another {@link AnnotationMirror} as identifying a
132   * <dfn>pseudo scope</dfn>; may be {@code null} in which case {@code d} must be non-{@code null}
133   *
134   * @param singletonScope an {@link AnnotationMirror} identifying the <dfn>singleton scope</dfn>; may be {@code null}
135   * in which case {@code d} must be non-{@code null}
136   *
137   * @param noneScope an {@link AnnotationMirror} identifying the <dfn>none scope</dfn>; may be {@code null} in which
138   * case {@code d} must be non-{@code null}
139   *
140   * @exception NullPointerException if {@code aq} or {@code sq} is {@code null}, or if {@code d} is {@code null} in
141   * certain situations
142   */
143  public Scopes(final Domain d,
144                final org.microbean.assign.Qualifiers aq,
145                final Qualifiers sq,
146                final AnnotationMirror metaNormalScope,
147                final AnnotationMirror metaPseudoScope,
148                final AnnotationMirror singletonScope,
149                final AnnotationMirror noneScope) {
150    super();    
151    this.aq = requireNonNull(aq, "aq");
152    this.sq = requireNonNull(sq, "sq");
153    if (metaNormalScope == null || metaPseudoScope == null || singletonScope == null || noneScope == null) {
154      
155      final List<? extends AnnotationMirror> as = d.typeElement("java.lang.annotation.Documented").getAnnotationMirrors();
156      assert as.size() == 3; // @Documented, @Retention, @Target, in that order, all annotated in turn with each other
157
158      final AnnotationMirror documentedAnnotation = as.get(0);
159      final AnnotationMirror retentionAnnotation = as.get(1);
160      final List<AnnotationMirror> documentedRetentionTarget =
161        List.of(documentedAnnotation, // @Documented
162                retentionAnnotation, // @Retention(TargetType.RUNTIME) (happens fortuitously to be RUNTIME)
163                as.get(2)); // @Target(ANNOTATION_TYPE)
164      
165      this.metaNormalScope =
166        metaNormalScope == null ?
167        new SyntheticAnnotationMirror(new SyntheticAnnotationTypeElement(documentedRetentionTarget, "NormalScope")) :
168        metaNormalScope;
169
170      this.metaPseudoScope =
171        metaPseudoScope == null ?
172        new SyntheticAnnotationMirror(new SyntheticAnnotationTypeElement(documentedRetentionTarget, "Scope")) :
173        metaPseudoScope;
174
175      final List<SyntheticAnnotationValue> savs = new ArrayList<>(4);
176      for (final Element e : d.typeElement("java.lang.annotation.ElementType").getEnclosedElements()) {
177        if (e.getKind() == ENUM_CONSTANT && e instanceof VariableElement ve) {
178          final Name n = e.getSimpleName();
179          if (n.contentEquals("TYPE") || n.contentEquals("METHOD") || n.contentEquals("FIELD") || n.contentEquals("PARAMETER")) {
180            savs.add(new SyntheticAnnotationValue(ve));
181          }
182        }
183      }
184      final List<AnnotationMirror> metaAnnotations =
185        List.of(aq.metaQualifier(),
186                this.metaPseudoScope,
187                sq.primordialMetaQualifier(),
188                retentionAnnotation,
189                new SyntheticAnnotationMirror(d.typeElement("java.lang.annotation.Target"), Map.of("value", savs)),
190                documentedAnnotation);
191      
192      this.singletonScope =
193        singletonScope == null ?
194        new SyntheticAnnotationMirror(new SyntheticAnnotationTypeElement(metaAnnotations, "Singleton")) :
195        singletonScope;
196
197      this.noneScope =
198        noneScope == null ?
199        new SyntheticAnnotationMirror(new SyntheticAnnotationTypeElement(metaAnnotations, "None")) :
200        noneScope;
201    } else {
202      this.metaNormalScope = metaNormalScope;
203      this.metaPseudoScope = metaPseudoScope;
204      this.singletonScope = singletonScope;
205      this.noneScope = noneScope;
206    }
207  }
208
209  /**
210   * Returns the first {@link AnnotationMirror} that is present in the supplied {@link Collection} of {@link
211   * AnnotationMirror}s, and their {@linkplain Element#getAnnotationMirrors() meta-annotations}, for which an invocation
212   * of the {@link #normalScope(AnnotationMirror)} method returns {@code true}, or {@code null}, if no such {@link
213   * AnnotationMirror} exists.
214   *
215   * <p>The search is conducted in a breadth-first manner.</p>
216   *
217   * @param c a {@link Collection} of {@link AnnotationMirror}s; must not be {@code null}
218   *
219   * @return the first {@link AnnotationMirror} that is present in the supplied {@link Collection} of {@link
220   * AnnotationMirror}s, and their {@linkplain Element#getAnnotationMirrors() meta-annotations}, for which an invocation
221   * of the {@link #normalScope(AnnotationMirror)} method returns {@code true}, or {@code null}, if no such {@link
222   * AnnotationMirror} exists
223   *
224   * @exception NullPointerException if {@code c} is {@code null}
225   */
226  public AnnotationMirror findNormalScope(final Collection<? extends AnnotationMirror> c) {
227    return c.isEmpty() ? null : streamBreadthFirst(c)
228      .dropWhile(not(this::normalScope))
229      .findFirst()
230      .orElse(null);
231  }
232  
233  /**
234   * Returns the first {@link AnnotationMirror} that is present in the supplied {@link Collection} of {@link
235   * AnnotationMirror}s, and their {@linkplain Element#getAnnotationMirrors() meta-annotations}, for which an invocation
236   * of the {@link #scope(AnnotationMirror)} method returns {@code true}, or {@code null}, if no such {@link
237   * AnnotationMirror} exists.
238   *
239   * <p>The search is conducted in a breadth-first manner.</p>
240   *
241   * @param c a {@link Collection} of {@link AnnotationMirror}s; must not be {@code null}
242   *
243   * @return the first {@link AnnotationMirror} that is present in the supplied {@link Collection} of {@link
244   * AnnotationMirror}s, and their {@linkplain Element#getAnnotationMirrors() meta-annotations}, for which an invocation
245   * of the {@link #scope(AnnotationMirror)} method returns {@code true}, or {@code null}, if no such {@link
246   * AnnotationMirror} exists
247   *
248   * @exception NullPointerException if {@code c} is {@code null}
249   */
250  public AnnotationMirror findScope(final Collection<? extends AnnotationMirror> c) {
251    return c.isEmpty() ? null : streamBreadthFirst(c)
252      .dropWhile(not(this::scope))
253      .findFirst()
254      .orElse(null);
255  }
256
257  /**
258   * Returns a non-{@code null}, determinate {@link AnnotationMirror} representing the identifier for the <dfn>none</dfn>
259   * scope.
260   *
261   * @return a non-{@code null}, determinate {@link AnnotationMirror} representing the identifier for the <dfn>none</dfn>
262   * scope
263   */
264  public final AnnotationMirror noneScope() {
265    return this.noneScope;
266  }
267
268  /**
269   * Returns {@code true} if the supplied {@link AnnotationMirror} has elements that might be used to indicate that it
270   * is a <dfn>normal scope</dfn>.
271   *
272   * @param a a non-{@code null}, determinate {@link AnnotationMirror}
273   *
274   * @return {@code true} if the supplied {@link AnnotationMirror} has elements that might be used to indicate that it
275   * is a <dfn>normal scope</dfn>
276   *
277   * @exception NullPointerException if {@code a} is {@code null}
278   *
279   * @see #scope(AnnotationMirror)
280   */
281  public boolean normal(final AnnotationMirror a) {
282    for (final Element ee : a.getAnnotationType().asElement().getEnclosedElements()) {
283      if (ee.getKind() == METHOD && ee.getSimpleName().contentEquals("normal")) {
284        return true;
285      }
286    }
287    return false;
288  }
289
290  /**
291   * Returns the non-{@code null}, determinate {@link AnnotationMirror} that can be used to designate other {@link
292   * AnnotationMirror}s as <dfn>normal scopes</dfn>.
293   *
294   * @return the non-{@code null}, determinate {@link AnnotationMirror} that can be used to designate other {@link
295   * AnnotationMirror}s as <dfn>normal scopes</dfn>
296   */
297  public final AnnotationMirror metaNormalScope() {
298    return this.metaNormalScope;
299  }
300  
301  /**
302   * Returns the non-{@code null}, determinate {@link AnnotationMirror} that can be used to designate other {@link
303   * AnnotationMirror}s as <dfn>scopes</dfn>.
304   *
305   * @return the non-{@code null}, determinate {@link AnnotationMirror} that can be used to designate other {@link
306   * AnnotationMirror}s as <dfn>scopes</dfn>
307   */
308  public final AnnotationMirror metaPseudoScope() {
309    return this.metaPseudoScope;
310  }
311
312  /**
313   * Returns {@code true} if and only if the supplied {@link AnnotationMirror} is a <dfn>normal scope identifier</dfn>.
314   *
315   * @param a an {@link AnnotationMirror}; must not be {@code null}
316   *
317   * @return {@code true} if and only if the supplied {@link AnnotationMirror} is a <dfn>normal scope identifier</dfn>
318   *
319   * @exception NullPointerException if {@code a} is {@code null}
320   */
321  public boolean normalScope(final AnnotationMirror a) {
322    // In this implementation, all scopes, normal or otherwise, must also be qualifiers.
323    boolean metaNormalScopeFound = false;
324    boolean metaQualifierFound = false;
325    for (final AnnotationMirror ma : a.getAnnotationType().asElement().getAnnotationMirrors()) {
326      if (!metaNormalScopeFound) {
327        if (sameAnnotation(ma, this.metaNormalScope())) {
328          metaNormalScopeFound = true;
329          continue;
330        }
331      }
332
333      if (!metaQualifierFound) {
334        if (sameAnnotation(ma, this.aq.metaQualifier())) {
335          metaQualifierFound = true;
336          continue;
337        }
338      }
339
340      if (sameAnnotation(ma, this.metaPseudoScope())) {
341        // Can't be a pseudo- and a normal scope at the same time.
342        return false;
343      }
344    }
345    return metaNormalScopeFound && metaQualifierFound;
346  }
347
348  /**
349   * Returns {@code true} if and only if the supplied {@link AnnotationMirror} is a <dfn>pseudo-scope identifier</dfn>.
350   *
351   * @param a an {@link AnnotationMirror}; must not be {@code null}
352   *
353   * @return {@code true} if and only if the supplied {@link AnnotationMirror} is a <dfn>pseudo-scope identifier</dfn>
354   *
355   * @exception NullPointerException if {@code a} is {@code null}
356   */
357  public boolean pseudoScope(final AnnotationMirror a) {
358    // In this implementation, all scopes, pseudo- or otherwise, must also be qualifiers.
359    // TODO: should also check that normalScope annotation is not present
360    boolean metaPseudoScopeFound = false;
361    boolean metaQualifierFound = false;
362    for (final AnnotationMirror ma : a.getAnnotationType().asElement().getAnnotationMirrors()) {
363      if (!metaPseudoScopeFound) {
364        if (sameAnnotation(ma, this.metaPseudoScope())) {
365          metaPseudoScopeFound = true;
366          continue;
367        }
368      }
369
370      if (!metaQualifierFound) {
371        if (sameAnnotation(ma, this.aq.metaQualifier())) {
372          metaQualifierFound = true;
373          continue;
374        }
375      }
376
377      if (sameAnnotation(ma, this.metaNormalScope())) {
378        // Can't be a pseudo- and a normal scope at the same time.
379        return false;
380      }
381    }
382    return metaPseudoScopeFound && metaQualifierFound;
383  }
384
385  /**
386   * Returns {@code true} if and only if the supplied {@link AnnotationMirror} is a <dfn>scope identifier</dfn> (either
387   * a {@linkplain #pseudoScope(AnnotationMirror) <dfn>pseudo-scope identifier</dfn>} or a {@linkplain
388   * #normalScope(AnnotationMirror) <dfn>normal scope identifier</dfn>}.
389   *
390   * @param a an {@link AnnotationMirror}; must not be {@code null}
391   *
392   * @return {@code true} if and only if the supplied {@link AnnotationMirror} is a <dfn>scope identifier</dfn> (either
393   * a {@linkplain #pseudoScope(AnnotationMirror) <dfn>pseudo-scope identifier</dfn>} or a {@linkplain
394   * #normalScope(AnnotationMirror) <dfn>normal scope identifier</dfn>}
395   *
396   * @exception NullPointerException if {@code a} is {@code null}
397   */
398  public final boolean scope(final AnnotationMirror a) {
399    return this.pseudoScope(a) || this.normalScope(a);
400  }
401
402  /**
403   * Returns a non-{@code null}, determinate {@link AnnotationMirror} representing the identifier for the
404   * <dfn>singleton</dfn> pseudo-scope.
405   *
406   * @return a non-{@code null}, determinate {@link AnnotationMirror} representing the identifier for the
407   * <dfn>singleton</dfn> pseudo-scope
408   */
409  public final AnnotationMirror singletonScope() {
410    return this.singletonScope;
411  }
412
413}