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.bean;
015
016import java.lang.constant.ClassDesc;
017import java.lang.constant.Constable;
018import java.lang.constant.ConstantDesc;
019import java.lang.constant.DynamicConstantDesc;
020
021import java.util.ArrayList;
022import java.util.Collection;
023import java.util.List;
024import java.util.Map;
025import java.util.Optional;
026
027import java.util.function.Predicate;
028
029import javax.lang.model.element.AnnotationMirror;
030import javax.lang.model.element.Element;
031import javax.lang.model.element.ExecutableElement;
032import javax.lang.model.element.Name;
033import javax.lang.model.element.QualifiedNameable;
034import javax.lang.model.element.TypeElement;
035import javax.lang.model.element.VariableElement;
036
037import org.microbean.construct.Domain;
038
039import org.microbean.construct.element.SyntheticAnnotationMirror;
040import org.microbean.construct.element.SyntheticAnnotationTypeElement;
041import org.microbean.construct.element.SyntheticAnnotationValue;
042import org.microbean.construct.element.SyntheticName;
043import org.microbean.construct.element.UniversalElement;
044
045import static java.lang.constant.ConstantDescs.BSM_INVOKE;
046import static java.lang.constant.ConstantDescs.NULL;
047
048import static java.lang.constant.MethodHandleDesc.ofConstructor;
049
050import static java.util.Collections.unmodifiableList;
051
052import static java.util.Objects.requireNonNull;
053
054import static javax.lang.model.element.ElementKind.ANNOTATION_TYPE;
055import static javax.lang.model.element.ElementKind.ENUM_CONSTANT;
056
057/**
058 * A utility class for working with <dfn>qualifiers</dfn>.
059 *
060 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a>
061 *
062 * @see org.microbean.assign.Qualifiers
063 */
064// Experimental. First foray into replacing Attributes with (synthetic) AnnotationMirrors.
065public class Qualifiers implements Constable {
066
067
068  /*
069   * Instance fields.
070   */
071
072
073  private final org.microbean.assign.Qualifiers aq;
074
075  private final AnnotationMirror anyQualifier;
076
077  private final List<AnnotationMirror> anyQualifiers;
078
079  private final AnnotationMirror defaultQualifier;
080
081  private final List<AnnotationMirror> defaultQualifiers;
082
083  private final List<AnnotationMirror> anyAndDefaultQualifiers;
084
085
086  /*
087   * Constructors.
088   */
089
090
091  /**
092   * Creates a new {@link Qualifiers}.
093   *
094   * @param d a non-{@code null} {@link Domain}
095   *
096   * @param aq a non-{@code null} {@link org.microbean.assign.Qualifiers}
097   *
098   * @exception NullPointerException if {@code aq} is {@code null}
099   *
100   * @see #Qualifiers(Domain, org.microbean.assign.Qualifiers, AnnotationMirror, AnnotationMirror)
101   */
102  public Qualifiers(final Domain d, final org.microbean.assign.Qualifiers aq) {
103    this(d, aq, null, null);
104  }
105
106  /**
107   * Creates a new {@link Qualifiers}.
108   *
109   * @param aq a non-{@code null} {@link org.microbean.assign.Qualifiers}
110   *
111   * @param anyQualifier a non-{@code null} {@link AnnotationMirror} representing the <dfn>any qualifier</dfn>
112   *
113   * @param defaultQualifier a non-{@code null} {@link AnnotationMirror} representing the <dfn>default qualifier</dfn>
114   *
115   * @exception NullPointerException if any argument is {@code null}
116   *
117   * @see org.microbean.assign.Qualifiers
118   */
119  public Qualifiers(final org.microbean.assign.Qualifiers aq,
120                    final AnnotationMirror anyQualifier,
121                    final AnnotationMirror defaultQualifier) {
122    this(null, aq, requireNonNull(anyQualifier, "anyQualifier"), requireNonNull(defaultQualifier, "defaultQualifier"));
123  }
124
125  /**
126   * Creates a new {@link Qualifiers}.
127   *
128   * @param d a {@link Domain}; may be {@code null} if both {@code anyQualifier} and {@code defaultQualifier} are
129   * non-{@code null}
130   *
131   * @param aq a non-{@code null} {@link org.microbean.assign.Qualifiers}
132   *
133   * @param anyQualifier a (possibly {@code null}) {@link AnnotationMirror} representing the <dfn>any qualifier</dfn>;
134   * if {@code null}, then {@code d} must be non-{@code null}
135   *
136   * @param defaultQualifier a (possibly {@code null}) {@link AnnotationMirror} representing the <dfn>default
137   * qualifier</dfn>; if {@code null}, then {@code d} must be non-{@code null}
138   *
139   * @exception NullPointerException if {@code aq} is {@code null} or if {@code d} is {@code null} in certain situations
140   *
141   * @see org.microbean.assign.Qualifiers
142   */
143  public Qualifiers(final Domain d,
144                    final org.microbean.assign.Qualifiers aq,
145                    final AnnotationMirror anyQualifier,
146                    final AnnotationMirror defaultQualifier) {
147    super();
148    this.aq = requireNonNull(aq, "aq");
149    if (anyQualifier == null || defaultQualifier == null) {
150      final List<? extends AnnotationMirror> as = d.typeElement("java.lang.annotation.Documented").getAnnotationMirrors();
151      assert as.size() == 3; // @Documented, @Retention, @Target, in that order, all annotated in turn with each other
152      final List<SyntheticAnnotationValue> savs = new ArrayList<>(4);
153      for (final Element e : d.typeElement("java.lang.annotation.ElementType").getEnclosedElements()) {
154        if (e.getKind() == ENUM_CONSTANT && e instanceof VariableElement ve) {
155          final Name n = e.getSimpleName();
156          if (n.contentEquals("TYPE") || n.contentEquals("METHOD") || n.contentEquals("FIELD") || n.contentEquals("PARAMETER")) {
157            savs.add(new SyntheticAnnotationValue(ve));
158          }
159        }
160      }
161      final AnnotationMirror targetAnnotation =
162        new SyntheticAnnotationMirror(d.typeElement("java.lang.annotation.Target"), Map.of("value", savs));
163      final List<AnnotationMirror> metaAnnotations =
164        List.of(aq.metaQualifier(),
165                as.get(1), // @Retention
166                targetAnnotation, // @Target
167                as.get(0)); // @Documented
168      this.anyQualifier =
169        anyQualifier == null ?
170        new SyntheticAnnotationMirror(new SyntheticAnnotationTypeElement(metaAnnotations, "Any")) :
171        anyQualifier;
172      this.defaultQualifier =
173        defaultQualifier == null ?
174        new SyntheticAnnotationMirror(new SyntheticAnnotationTypeElement(metaAnnotations, "Default")) :
175        defaultQualifier;
176    } else {
177      this.anyQualifier = anyQualifier;
178      this.defaultQualifier = defaultQualifier;
179    }
180    this.anyQualifiers = List.of(this.anyQualifier);
181    this.defaultQualifiers = List.of(this.defaultQualifier);
182    this.anyAndDefaultQualifiers = List.of(this.anyQualifier, this.defaultQualifier);
183  }
184
185
186  /*
187   * Instance methods.
188   */
189
190
191  /**
192   * Returns a non-{@code null}, determinate, immutable {@link List} housing only the {@linkplain #anyQualifier()
193   * <dfn>any qualifier</dfn>} and {@link #defaultQualifier() <dfn>default qualifier</dfn>}.
194   *
195   * @return a non-{@code null}, determinate, immutable {@link List} housing only the {@linkplain #anyQualifier()
196   * <dfn>any qualifier</dfn>} and {@linkplain #defaultQualifier() <dfn>default qualifier</dfn>}
197   */
198  public final List<AnnotationMirror> anyAndDefaultQualifiers() {
199    return this.anyAndDefaultQualifiers;
200  }
201
202  /**
203   * Returns the non-{@code null}, determinate {@link AnnotationMirror} representing the <dfn>any qualifier</dfn>.
204   *
205   * @return the non-{@code null}, determinate {@link AnnotationMirror} representing the <dfn>any qualifier</dfn>
206   */
207  public final AnnotationMirror anyQualifier() {
208    return this.anyQualifier;
209  }
210
211  /**
212   * Returns a non-{@code null}, determinate, immutable {@link List} housing only the {@linkplain #anyQualifier()
213   * <dfn>any qualifier</dfn>}.
214   *
215   * @return a non-{@code null}, determinate, immutable {@link List} housing only the {@linkplain #anyQualifier()
216   * <dfn>any qualifier</dfn>}
217   */
218  public final List<AnnotationMirror> anyQualifiers() {
219    return this.anyQualifiers;
220  }
221
222  /**
223   * Returns {@code true} if and only if the supplied {@link AnnotationMirror} is {@linkplain
224   * org.microbean.assign.Qualifiers#sameAnnotation(AnnotationMirror, AnnotationMirror) the same} as the {@linkplain
225   * #anyQualifier() <dfn>any qualifier</dfn>}.
226   *
227   * @param a a non-{@code null} {@link AnnotationMirror}
228   *
229   * @return {@code true} if and only if the supplied {@link AnnotationMirror} is {@linkplain
230   * org.microbean.assign.Qualifiers#sameAnnotation(AnnotationMirror, AnnotationMirror) the same} as the {@linkplain
231   * #anyQualifier() <dfn>any qualifier</dfn>}
232   *
233   * @exception NullPointerException if {@code a} is {@code null}
234   *
235   * @see #anyQualifier()
236   *
237   * @see org.microbean.assign.Qualifiers#sameAnnotation(AnnotationMirror, AnnotationMirror)
238   */
239  public final boolean anyQualifier(final AnnotationMirror a) {
240    return this.aq.sameAnnotation(this.anyQualifier, a);
241  }
242
243  /**
244   * Returns the non-{@code null}, determinate {@link AnnotationMirror} representing the <dfn>default qualifier</dfn>.
245   *
246   * @return the non-{@code null}, determinate {@link AnnotationMirror} representing the <dfn>default qualifier</dfn>
247   */
248  public final AnnotationMirror defaultQualifier() {
249    return this.defaultQualifier;
250  }
251
252  /**
253   * Returns a non-{@code null}, determinate, immutable {@link List} housing only the {@linkplain #defaultQualifier()
254   * <dfn>default qualifier</dfn>}.
255   *
256   * @return a non-{@code null}, determinate, immutable {@link List} housing only the {@linkplain #defaultQualifier()
257   * <dfn>default qualifier</dfn>}
258   */
259  public final List<AnnotationMirror> defaultQualifiers() {
260    return this.defaultQualifiers;
261  }
262
263  /**
264   * Returns {@code true} if and only if the supplied {@link AnnotationMirror} is {@linkplain
265   * org.microbean.assign.Qualifiers#sameAnnotation(AnnotationMirror, AnnotationMirror) the same} as the {@linkplain
266   * #defaultQualifier() <dfn>default qualifier</dfn>}.
267   *
268   * @param a a non-{@code null} {@link AnnotationMirror}
269   *
270   * @return {@code true} if and only if the supplied {@link AnnotationMirror} is {@linkplain
271   * org.microbean.assign.Qualifiers#sameAnnotation(AnnotationMirror, AnnotationMirror) the same} as the {@linkplain
272   * #defaultQualifier() <dfn>default qualifier</dfn>}
273   *
274   * @exception NullPointerException if {@code a} is {@code null}
275   *
276   * @see #defaultQualifier()
277   *
278   * @see org.microbean.assign.Qualifiers#sameAnnotation(AnnotationMirror, AnnotationMirror)
279   */
280  public final boolean defaultQualifier(final AnnotationMirror a) {
281    return this.aq.sameAnnotation(this.defaultQualifier, a);
282  }
283
284  @Override // Constable
285  public Optional<? extends ConstantDesc> describeConstable() {
286    final ClassDesc cdAnnotationMirror = AnnotationMirror.class.describeConstable().orElseThrow();
287    return this.aq.describeConstable()
288      .flatMap(aqDesc -> (this.anyQualifier instanceof Constable c ? c.describeConstable() : Optional.<ConstantDesc>empty())
289               .flatMap(anyQualifierDesc -> (this.defaultQualifier instanceof Constable c ? c.describeConstable() : Optional.<ConstantDesc>empty())
290                        .map(defaultQualifierDesc -> DynamicConstantDesc.of(BSM_INVOKE,
291                                                                            ofConstructor(this.getClass().describeConstable().orElseThrow(),
292                                                                                          Domain.class.describeConstable().orElseThrow(),
293                                                                                          org.microbean.assign.Qualifiers.class.describeConstable().orElseThrow(),
294                                                                                          cdAnnotationMirror,
295                                                                                          cdAnnotationMirror),
296                                                                            NULL,
297                                                                            aqDesc,
298                                                                            anyQualifierDesc,
299                                                                            defaultQualifierDesc))));
300  }
301
302}