001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
002 *
003 * Copyright © 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.constant.Constable;
017import java.lang.constant.ConstantDesc;
018import java.lang.constant.DynamicConstantDesc;
019
020import java.util.ArrayList;
021import java.util.List;
022import java.util.Map;
023import java.util.Optional;
024
025import javax.lang.model.element.AnnotationMirror;
026import javax.lang.model.element.Element;
027import javax.lang.model.element.Name;
028import javax.lang.model.element.VariableElement;
029
030import org.microbean.construct.Domain;
031
032import org.microbean.construct.element.SyntheticAnnotationMirror;
033import org.microbean.construct.element.SyntheticAnnotationTypeElement;
034import org.microbean.construct.element.SyntheticAnnotationValue;
035
036import static java.lang.constant.ConstantDescs.BSM_INVOKE;
037
038import static java.lang.constant.MethodHandleDesc.ofConstructor;
039
040import static java.util.Objects.requireNonNull;
041
042import static javax.lang.model.element.ElementKind.ENUM_CONSTANT;
043
044import static org.microbean.construct.element.AnnotationMirrors.sameAnnotation;
045
046/**
047 * A utility class for working with qualifiers.
048 *
049 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a>
050 */
051public class Qualifiers implements Constable {
052
053  private final org.microbean.assign.Qualifiers baseQualifiers;
054
055  private final AnnotationMirror primordialMetaQualifier;
056
057  private final List<AnnotationMirror> primordialMetaQualifiers;
058
059  /**
060   * Creates a new {@link Qualifiers}.
061   *
062   * @param domain a non-{@code null} {@link Domain}
063   *
064   * @param baseQualifiers a non-{@code null} {@link org.microbean.assign.Qualifiers}
065   *
066   * @see #Qualifiers(Domain, org.microbean.assign.Qualifiers, AnnotationMirror)
067   */
068  public Qualifiers(final Domain domain,
069                    final org.microbean.assign.Qualifiers baseQualifiers) {
070    this(domain, baseQualifiers, null);
071  }
072
073  /**
074   * Creates a new {@link Qualifiers}.
075   *
076   * @param domain a {@link Domain}; may be {@code null} in which case {@code primordialMetaQualifier} must not be
077   * {@code null}
078   *
079   * @param baseQualifiers a non-{@code null} {@link org.microbean.assign.Qualifiers}
080   *
081   * @param primordialMetaQualifier an {@link AnnotationMirror} identifying the <dfn>primordial meta-qualifier</dfn>;
082   * may be {@code null} in which case {@code domain} must be non-{@code null}
083   *
084   * @exception NullPointerException if {@code baseQualifiers} is {@code null}, or if {@code domain} is {@code null} in
085   * certain circumstances, or if {@code primordialMetaQualifier} is {@code null} in certain circumstances
086   */
087  public Qualifiers(final Domain domain,
088                    final org.microbean.assign.Qualifiers baseQualifiers,
089                    final AnnotationMirror primordialMetaQualifier) {
090    super();
091    this.baseQualifiers = requireNonNull(baseQualifiers, "baseQualifiers");
092    if (primordialMetaQualifier == null) {
093      final List<? extends AnnotationMirror> as = domain.typeElement("java.lang.annotation.Documented").getAnnotationMirrors();
094      assert as.size() == 3; // @Documented, @Retention, @Target, in that order, all annotated in turn with each other
095      final List<SyntheticAnnotationValue> savs = new ArrayList<>(4);
096      for (final Element e : domain.typeElement("java.lang.annotation.ElementType").getEnclosedElements()) {
097        if (e.getKind() == ENUM_CONSTANT && e instanceof VariableElement ve) {
098          final Name n = e.getSimpleName();
099          if (n.contentEquals("TYPE") || n.contentEquals("METHOD") || n.contentEquals("FIELD") || n.contentEquals("PARAMETER")) {
100            savs.add(new SyntheticAnnotationValue(ve));
101          }
102        }
103      }
104      final AnnotationMirror targetAnnotation =
105        new SyntheticAnnotationMirror(domain.typeElement("java.lang.annotation.Target"), Map.of("value", savs));
106      final List<AnnotationMirror> metaAnnotations =
107        List.of(baseQualifiers.metaQualifier(),
108                as.get(1), // @Retention
109                targetAnnotation, // @Target
110                as.get(0)); // @Documented
111      this.primordialMetaQualifier =
112        primordialMetaQualifier == null ?
113        new SyntheticAnnotationMirror(new SyntheticAnnotationTypeElement(metaAnnotations, "Primordial")) :
114        primordialMetaQualifier;
115    } else {
116      this.primordialMetaQualifier = primordialMetaQualifier;
117    }
118    this.primordialMetaQualifiers = List.of(this.primordialMetaQualifier);
119  }
120
121  @Override // Constable
122  public Optional<? extends ConstantDesc> describeConstable() {
123    return this.baseQualifiers instanceof Constable ? ((Constable)this.baseQualifiers).describeConstable() : Optional.<ConstantDesc>empty()
124      .flatMap(baseQualifiersDesc -> this.primordialMetaQualifier instanceof Constable c ? c.describeConstable() : Optional.<ConstantDesc>empty()
125               .map(primordialQualifierDesc -> DynamicConstantDesc.of(BSM_INVOKE,
126                                                                      ofConstructor(this.getClass().describeConstable().orElseThrow(),
127                                                                                    org.microbean.assign.Qualifiers.class.describeConstable().orElseThrow(),
128                                                                                    AnnotationMirror.class.describeConstable().orElseThrow()),
129                                                                      baseQualifiersDesc,
130                                                                      primordialQualifierDesc)));
131  }
132
133  /**
134   * Returns the non-{@code null}, determinate {@link AnnotationMirror} representing the <dfn>primordial
135   * meta-qualifier</dfn>.
136   *
137   * @return the non-{@code null}, determinate {@link AnnotationMirror} representing the <dfn>primordial
138   * meta-qualifier</dfn>
139   */
140  public final AnnotationMirror primordialMetaQualifier() {
141    return this.primordialMetaQualifier;
142  }
143
144  /**
145   * Returns {@code true} if and only if the supplied {@link AnnotationMirror} is {@linkplain
146   * org.microbean.assign.Qualifiers#sameAnnotation(AnnotationMirror, AnnotationMirror) the same} as the {@linkplain
147   * #primordialMetaQualifier() <dfn>primordial meta-qualifier</dfn>}.
148   *
149   * @param a a non-{@code null} {@link AnnotationMirror}
150   *
151   * @return {@code true} if and only if the supplied {@link AnnotationMirror} is {@linkplain
152   * org.microbean.assign.Qualifiers#sameAnnotation(AnnotationMirror, AnnotationMirror) the same} as the {@linkplain
153   * #primordialMetaQualifier() <dfn>primordial meta-qualifier</dfn>}
154   *
155   * @exception NullPointerException if {@code a} is {@code null}
156   *
157   * @see #primordialMetaQualifier()
158   *
159   * @see org.microbean.assign.Qualifiers#sameAnnotation(AnnotationMirror, AnnotationMirror)
160   */
161  public final boolean primordialMetaQualifier(final AnnotationMirror a) {
162    return this.baseQualifiers.sameAnnotation(this.primordialMetaQualifier, a);
163  }
164
165  /**
166   * Returns a non-{@code null}, determinate, immutable {@link List} housing only the {@linkplain
167   * #primordialMetaQualifier() <dfn>primordial meta-qualifier</dfn>}.
168   *
169   * @return a non-{@code null}, determinate, immutable {@link List} housing only the {@linkplain
170   * #primordialMetaQualifier() <dfn>primordial meta-qualifier</dfn>}
171   */
172  public final List<AnnotationMirror> primordialMetaQualifiers() {
173    return this.primordialMetaQualifiers;
174  }
175
176}