001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
002 *
003 * Copyright © 2024–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.util.Collection;
017
018import java.util.function.Predicate;
019
020import javax.lang.model.element.AnnotationMirror;
021import javax.lang.model.element.ExecutableElement;
022
023import org.microbean.assign.Matcher;
024
025import static java.util.Objects.requireNonNull;
026
027import static org.microbean.construct.element.AnnotationMirrors.sameAnnotation;
028import static org.microbean.construct.element.AnnotationMirrors.contains;
029import static org.microbean.construct.element.AnnotationMirrors.containsAll;
030
031/**
032 * A {@link Matcher} encapsulating <a
033 * href="https://jakarta.ee/specifications/cdi/4.1/jakarta-cdi-spec-4.0#observertypesafe_resolution">CDI-compatible bean
034 * qualifier matching rules</a>.
035 *
036 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a>
037 *
038 * @see #test(Collection, Collection)
039 */
040public class BeanQualifiersMatcher
041  implements Matcher<Collection<? extends AnnotationMirror>, Collection<? extends AnnotationMirror>> {
042
043
044  /*
045   * Instance fields.
046   */
047
048
049  private final org.microbean.assign.Qualifiers aq;
050
051  private final Qualifiers bq;
052
053
054  /*
055   * Constructors.
056   */
057
058
059  /**
060   * Creates a new {@link BeanQualifiersMatcher}.
061   *
062   * @param aq a (@link org.microbean.assign.Qualifiers}; must not be {@code null}
063   *
064   * @param bq a {@link Qualifiers}; must not be {@code null}
065   *
066   * @exception NullPointerException if any argument is {@code null}
067   */
068  public BeanQualifiersMatcher(final org.microbean.assign.Qualifiers aq,
069                               final Qualifiers bq) {
070    super();
071    this.aq = requireNonNull(aq, "aq");
072    this.bq = requireNonNull(bq, "bq");
073  }
074
075
076  /*
077   * Instance methods.
078   */
079
080
081  /*
082   * Returns the non-{@code null}, determinate {@link Predicate} {@linkplain
083   * org.microbean.assign.Qualifiers#Qualifiers(Domain, AnnotationMirror, Predicate) supplied (indirectly) at
084   * construction time} that returns {@code true} if a given {@link ExecutableElement}, representing an annotation
085   * element, is to be included in any comparison operation.
086   *
087   * @return the non-{@code null}, determinate {@link Predicate} {@linkplain
088   * org.microbean.assign.Qualifiers#Qualifiers(Domain, AnnotationMirror, Predicate) supplied (indirectly) at
089   * construction time} that returns {@code true} if a given {@link ExecutableElement}, representing an annotation
090   * element, is to be included in any comparison operation
091   *
092   * @see #BeanQualifiersMatcher(Qualifiers)
093   *
094   * @see org.microbean.assign.Qualifiers#annotationElementInclusionPredicate()
095   */
096  // public final Predicate<? super ExecutableElement> annotationElementInclusionPredicate() {
097  //   return this.qualifiers.annotationElementInclusionPredicate();
098  // }
099
100  @Override // Matcher<Collection<? extends AnnotationMirror>, Collection<? extends AnnotationMirror>>
101  public final boolean test(final Collection<? extends AnnotationMirror> receiverAnnotations,
102                            final Collection<? extends AnnotationMirror> payloadAnnotations) {
103    final Collection<? extends AnnotationMirror> receiverQualifiers = this.aq.qualifiers(receiverAnnotations);
104    final Collection<? extends AnnotationMirror> payloadQualifiers = this.aq.qualifiers(payloadAnnotations);
105    // https://jakarta.ee/specifications/cdi/4.1/jakarta-cdi-spec-4.1#performing_typesafe_resolution "A bean is
106    // assignable to a given injection point if...the bean [payload] has all the required [receiver] qualifiers. If no
107    // required qualifiers were explicitly specified, the container assumes the required qualifier @Default."
108    //
109    // https://jakarta.ee/specifications/cdi/4.1/jakarta-cdi-spec-4.1#builtin_qualifiers "Every bean has the built-in
110    // qualifier @Any, even if it does not explicitly declare this qualifier. If a bean does not explicitly declare a
111    // qualifier other than @Named or @Any, the bean has exactly one additional qualifier, of type @Default. This is
112    // called the default qualifier."
113    //
114    // https://jakarta.ee/specifications/cdi/4.1/jakarta-cdi-spec-4.1#injection_point_default_qualifier
115    // "If an injection point declares no qualifier, the injection point has exactly one qualifier, the default
116    // qualifier @Default."
117    return
118      receiverQualifiers.isEmpty() ?
119      payloadQualifiers.isEmpty() || this.aq.contains(payloadQualifiers, this.bq.defaultQualifier()) :
120      this.aq.containsAll(payloadQualifiers.isEmpty() ? this.bq.anyAndDefaultQualifiers() : payloadQualifiers,
121                          receiverQualifiers);
122  }
123
124}