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.assign;
015
016import java.lang.constant.Constable;
017import java.lang.constant.ConstantDesc;
018import java.lang.constant.DynamicConstantDesc;
019
020import java.util.ArrayList;
021import java.util.Collection;
022import java.util.List;
023import java.util.Optional;
024
025import java.util.function.Predicate;
026
027import javax.lang.model.element.AnnotationMirror;
028import javax.lang.model.element.ExecutableElement;
029import javax.lang.model.element.QualifiedNameable;
030import javax.lang.model.element.TypeElement;
031
032import javax.lang.model.type.ArrayType;
033
034import org.microbean.construct.Domain;
035
036import org.microbean.construct.element.AnnotationMirrors;
037import org.microbean.construct.element.SyntheticAnnotationMirror;
038import org.microbean.construct.element.SyntheticAnnotationTypeElement;
039import org.microbean.construct.element.UniversalElement;
040
041import static java.lang.constant.ConstantDescs.BSM_INVOKE;
042import static java.lang.constant.ConstantDescs.NULL;
043
044import static java.lang.constant.MethodHandleDesc.ofConstructor;
045
046import static java.util.Collections.unmodifiableList;
047
048import static java.util.Objects.requireNonNull;
049
050import static javax.lang.model.element.ElementKind.ANNOTATION_TYPE;
051
052/**
053 * A utility class for working with <dfn>qualifiers</dfn>.
054 *
055 * <p>This class is currently not used by other classes in this package. It may be useful in a variety of dependency
056 * injection systems.</p>
057 *
058 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a>
059 */
060public class Qualifiers implements Constable {
061
062
063  /*
064   * Instance fields.
065   */
066
067
068  private final Predicate<? super ExecutableElement> annotationElementInclusionPredicate;
069
070  private final AnnotationMirror metaQualifier;
071
072  private final List<AnnotationMirror> metaQualifiers;
073
074
075  /*
076   * Constructors.
077   */
078
079
080  /**
081   * Creates a new {@link Qualifiers}.
082   *
083   * @param domain a non-{@code null} {@link Domain}
084   *
085   * @exception NullPointerException if {@code domain} is {@code null}
086   *
087   * @see #Qualifiers(Domain, AnnotationMirror, Predicate)
088   */
089  public Qualifiers(final Domain domain) {
090    this(domain, null, null);
091  }
092
093  /**
094   * Creates a new {@link Qualifiers}.
095   *
096   * @param metaQualifier a non-{@code null} {@link AnnotationMirror} to serve as the {@linkplain #metaQualifier() meta-qualifier}
097   *
098   * @exception NullPointerException if {@code metaQualifier} is {@code null}
099   *
100   * @see #Qualifiers(Domain, AnnotationMirror, Predicate)
101   */
102  public Qualifiers(final AnnotationMirror metaQualifier) {
103    this(null, requireNonNull(metaQualifier, "metaQualifier"), null);
104  }
105
106  /**
107   * Creates a new {@link Qualifiers}.
108   *
109   * @param metaQualifier a non-{@code null} {@link AnnotationMirror} to serve as the {@linkplain #metaQualifier() meta-qualifier}
110   *
111   * @param annotationElementInclusionPredicate a {@link Predicate} that returns {@code true} if a given {@link
112   * ExecutableElement}, representing an annotation element, is to be included in the computation; may be {@code null}
113   * in which case it is as if {@code e -> true} were supplied instead
114   *
115   * @exception NullPointerException if {@code metaQualifier} is {@code null}
116   *
117   * @see #Qualifiers(Domain, AnnotationMirror, Predicate)
118   */
119  public Qualifiers(final AnnotationMirror metaQualifier,
120                    final Predicate<? super ExecutableElement> annotationElementInclusionPredicate) {
121    this(null, requireNonNull(metaQualifier, "metaQualifier"), annotationElementInclusionPredicate);
122  }
123
124  /**
125   * Creates a new {@link Qualifiers}.
126   *
127   * @param domain a {@link Domain}; if {@code null}, then {@code metaQualifier} must not be {@code null}
128   *
129   * @param metaQualifier an {@link AnnotationMirror} to serve as the {@linkplain #metaQualifier() meta-qualifier}; may
130   * (commonly) be {@code null} in which case a synthetic meta-qualifier will be used instead; must not be {@code null}
131   * if {@code domain} is {@code null}
132   *
133   * @param annotationElementInclusionPredicate a {@link Predicate} that returns {@code true} if a given {@link
134   * ExecutableElement}, representing an annotation element, is to be included in the computation; may be {@code null}
135   * in which case it is as if {@code e -> true} were supplied instead
136   *
137   * @exception NullPointerException if {@code domain} is {@code null} in certain situations
138   */
139  public Qualifiers(final Domain domain,
140                    final AnnotationMirror metaQualifier,
141                    final Predicate<? super ExecutableElement> annotationElementInclusionPredicate) {
142    super();
143    if (metaQualifier == null) {
144      final List<? extends AnnotationMirror> as = domain.typeElement("java.lang.annotation.Documented").getAnnotationMirrors();
145      assert as.size() == 3; // @Documented, @Retention, @Target, in that order, all annotated in turn with each other
146      this.metaQualifier =
147        new SyntheticAnnotationMirror(new SyntheticAnnotationTypeElement(List.of(as.get(0), // @Documented
148                                                                                 as.get(1), // @Retention(RUNTIME) (happens fortuitously to be RUNTIME)
149                                                                                 as.get(2)), // @Target(ANNOTATION_TYPE) (happens fortuitously to be ANNOTATION_TYPE)
150                                                                         "Qualifier"));
151    } else {
152      this.metaQualifier = metaQualifier;
153    }
154    this.annotationElementInclusionPredicate = annotationElementInclusionPredicate == null ? Qualifiers::returnTrue : annotationElementInclusionPredicate;
155    this.metaQualifiers = List.of(this.metaQualifier);
156  }
157
158
159  /*
160   * Instance methods.
161   */
162
163  // The contains* methods below do not apply only to qualifiers. That makes them smell a little funky here but it's not
164  // really worth breaking out, I don't think. The whole annotationElementInclusionPredicate thing may belong in
165  // microbean-bean, but it really doesn't *have* to be bean-qualifiers-specific.
166
167  /**
168   * Returns {@code true} if and only if the supplied {@link Collection} of {@link AnnotationMirror}s contains an {@link
169   * AnnotationMirror} that is {@linkplain AnnotationMirrors#sameAnnotation(AnnotationMirror, AnnotationMirror,
170   * Predicate) the same} as the supplied {@link AnnotationMirror}.
171   *
172   * @param c a non-{@code null} {@link Collection} of {@link AnnotationMirror}s
173   *
174   * @param a a non-{@code null} {@link AnnotationMirror}
175   *
176   * @return {@code true} if and only if the supplied {@link Collection} of {@link AnnotationMirror}s contains an {@link
177   * AnnotationMirror} that is {@linkplain AnnotationMirrors#sameAnnotation(AnnotationMirror, AnnotationMirror,
178   * Predicate) the same} as the supplied {@link AnnotationMirror}
179   *
180   * @exception NullPointerException if {@code c} or {@code a} is {@code null}
181   *
182   * @see AnnotationMirrors#sameAnnotation(AnnotationMirror, AnnotationMirror, Predicate)
183   *
184   * @see AnnotationMirrors#contains(Collection, AnnotationMirror, Predicate)
185   *
186   * @see #Qualifiers(Domain, AnnotationMirror, Predicate)
187   */
188  public final boolean contains(final Collection<? extends AnnotationMirror> c, final AnnotationMirror a) {
189    return AnnotationMirrors.contains(c, a, this.annotationElementInclusionPredicate);
190  }
191
192  /**
193   * Returns {@code true} if and only if {@code c0} contains all {@linkplain
194   * AnnotationMirrors#sameAnnotation(AnnotationMirror, AnnotationMirror, Predicate) the same} {@link AnnotationMirror}s
195   * as are found in {@code c1},
196   *
197   * @param c0 a non-{@code null} {@link Collection} of {@link AnnotationMirror}s
198   *
199   * @param c1 a non-{@code null} {@link Collection} of {@link AnnotationMirror}s
200   *
201   * @return {@code true} if and only if {@code c0} contains all {@linkplain
202   * AnnotationMirrors#sameAnnotation(AnnotationMirror, AnnotationMirror, Predicate) the same} {@link AnnotationMirror}s
203   * as are found in {@code c1}
204   *
205   * @exception NullPointerException if either {@code c0} or {@code c1} is {@code null}
206   *
207   * @see AnnotationMirrors#sameAnnotation(AnnotationMirror, AnnotationMirror, Predicate)
208   *
209   * @see AnnotationMirrors#containsAll(Collection, Collection, Predicate)
210   *
211   * @see #Qualifiers(Domain, AnnotationMirror, Predicate)
212   */
213  public final boolean containsAll(final Collection<? extends AnnotationMirror> c0,
214                                   final Collection<? extends AnnotationMirror> c1) {
215    return AnnotationMirrors.containsAll(c0, c1, this.annotationElementInclusionPredicate);
216  }
217
218  /**
219   * Returns a non-{@code null}, determinate {@link Optional} housing a {@link ConstantDesc} describing this {@link
220   * Qualifiers}, or an {@linkplain Optional#isEmpty() empty} {@link Optional} if it cannot be described.
221   *
222   * @return a non-{@code null}, determinate {@link Optional} housing a {@link ConstantDesc} describing this {@link
223   * Qualifiers}, or an {@linkplain Optional#isEmpty() empty} {@link Optional} if it cannot be described
224   */
225  @Override // Constable
226  public Optional<? extends ConstantDesc> describeConstable() {
227    return (this.metaQualifier instanceof Constable c ? c.describeConstable() : Optional.<ConstantDesc>empty())
228      .flatMap(mqDesc -> (this.annotationElementInclusionPredicate == null ?
229                          Optional.of(NULL) :
230                          this.annotationElementInclusionPredicate instanceof Constable c ?
231                          c.describeConstable() :
232                          Optional.<ConstantDesc>empty())
233               .map(pDesc -> DynamicConstantDesc.of(BSM_INVOKE,
234                                                    ofConstructor(this.getClass().describeConstable().orElseThrow(),
235                                                                  Domain.class.describeConstable().orElseThrow(),
236                                                                  AnnotationMirror.class.describeConstable().orElseThrow(),
237                                                                  Predicate.class.describeConstable().orElseThrow()),
238                                                    NULL,
239                                                    mqDesc,
240                                                    pDesc)));
241  }
242
243  /**
244   * Returns a non-{@code null}, determinate {@link AnnotationMirror} that can be used to designate (meta-annotate)
245   * other annotations as <dfn>qualifiers</dfn>.
246   *
247   * @return a non-{@code null}, determinate {@link AnnotationMirror} that can be used to designate (meta-annotate)
248   * other annotations as <dfn>qualifiers</dfn>
249   */
250  public final AnnotationMirror metaQualifier() {
251    return this.metaQualifier;
252  }
253
254  /**
255   * Returns {@code true} if and only if the supplied {@link AnnotationMirror} {@linkplain
256   * #sameAnnotation(AnnotationMirror, AnnotationMirror) is the same annotation as} the supplied {@link
257   * AnnotationMirror}.
258   *
259   * @param a a non-{@code null} {@link AnnotationMirror}
260   *
261   * @return {@code true} if and only if if and only if the supplied {@link AnnotationMirror} {@linkplain
262   * #sameAnnotation(AnnotationMirror, AnnotationMirror) is the same annotation as} the supplied {@link
263   * AnnotationMirror}
264   *
265   * @exception NullPointerException if {@code a} is {@code null}
266   *
267   * @see #sameAnnotation(AnnotationMirror, AnnotationMirror)
268   */
269  public final boolean metaQualifier(final AnnotationMirror a) {
270    return this.sameAnnotation(this.metaQualifier(), a);
271  }
272
273  /**
274   * Returns a non-{@code null}, determinate, immutable {@link List} whose sole element is the {@linkplain
275   * #metaQualifier() meta-qualifier} annotation.
276   *
277   * @return a non-{@code null}, determinate, immutable {@link List} whose sole element is the {@linkplain
278   * #metaQualifier() meta-qualifier} annotation
279   */
280  public final List<AnnotationMirror> metaQualifiers() {
281    return this.metaQualifiers;
282  }
283
284  /**
285   * Returns {@code true} if and only if the supplied {@link AnnotationMirror} has an {@linkplain
286   * AnnotationMirror#getAnnotationType() annotation type} declared by a {@link TypeElement} that is {@linkplain
287   * javax.lang.model.AnnotatedConstruct#getAnnotationMirrors() annotated with} at least one annotation {@linkplain
288   * #metaQualifier(AnnotationMirror) deemed to be the meta-qualifier}.
289   *
290   * @param a a non-{@code null} {@link AnnotationMirror}
291   *
292   * @return {@code true} if and only if the supplied {@link AnnotationMirror} has an {@linkplain
293   * AnnotationMirror#getAnnotationType() annotation type} declared by a {@link TypeElement} that is {@linkplain
294   * javax.lang.model.AnnotatedConstruct#getAnnotationMirrors() annotated with} at least one annotation {@linkplain
295   * #metaQualifier(AnnotationMirror) deemed to be the meta-qualifier}
296   *
297   * @exception NullPointerException if {@code a} is {@code null}
298   */
299  public final boolean qualifier(final AnnotationMirror a) {
300    if (!this.metaQualifier(a)) {
301      final TypeElement annotationInterface = (TypeElement)a.getAnnotationType().asElement();
302      if (annotationInterface.getKind() == ANNOTATION_TYPE) {
303        for (final AnnotationMirror ma : annotationInterface.getAnnotationMirrors()) {
304          if (this.metaQualifier(ma)) {
305            return true;
306          }
307        }
308      }
309    }
310    return false;
311  }
312
313  /**
314   * Returns a non-{@code null}, determinate, immutable {@link List} of {@link AnnotationMirror} instances drawn from
315   * the supplied {@link Collection} that were {@linkplain #qualifier(AnnotationMirror) deemed to be qualifiers}.
316   *
317   * @param as a non-{@code null} {@link Collection} of {@link AnnotationMirror}s
318   *
319   * @return a non-{@code null}, determinate, immutable {@link List} of {@link AnnotationMirror} instances drawn from
320   * the supplied {@link Collection} that were {@linkplain #qualifier(AnnotationMirror) deemed to be qualifiers}
321   *
322   * @exception NullPointerException if {@code as} is {@code null}
323   *
324   * @see #qualifier(AnnotationMirror)
325   */
326  public List<AnnotationMirror> qualifiers(final Collection<? extends AnnotationMirror> as) {
327    if (as.isEmpty()) {
328      return List.of();
329    }
330    final List<AnnotationMirror> l = new ArrayList<>(as.size());
331    for (final AnnotationMirror a : as) {
332      if (this.qualifier(a)) {
333        l.add(a);
334      }
335    }
336    return l.isEmpty() ? List.of() : unmodifiableList(l);
337  }
338
339  /**
340   * Determines whether the two {@link AnnotationMirror}s represent the same (underlying, otherwise opaque) annotation.
341   *
342   * @param am0 an {@link AnnotationMirror}; may be {@code null}
343   *
344   * @param am1 an {@link AnnotationMirror}; may be {@code null}
345   *
346   * @return {@code true} if the supplied {@link AnnotationMirror}s represent the same (underlying, otherwise opaque)
347   * annotation; {@code false} otherwise
348   *
349   * @see AnnotationMirrors#sameAnnotation(AnnotationMirror, AnnotatonMirror, Predicate)
350   *
351   * @see #Qualifiers(Domain, AnnotationMirror, Predicate)
352   */
353  public final boolean sameAnnotation(final AnnotationMirror am0, final AnnotationMirror am1) {
354    return AnnotationMirrors.sameAnnotation(am0, am1, this.annotationElementInclusionPredicate);
355  }
356
357  private static final <X> boolean returnTrue(final X ignored) {
358    return true;
359  }
360
361}