001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
002 *
003 * Copyright © 2024–2025 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 org.microbean.assign.Matcher;
019
020import org.microbean.attributes.Attributes;
021
022import static java.util.Objects.requireNonNull;
023
024/**
025 * A {@link Matcher} encapsulating <a
026 * href="https://jakarta.ee/specifications/cdi/4.0/jakarta-cdi-spec-4.0#observertypesafe_resolution">CDI-compatible bean
027 * qualifier matching rules</a>.
028 *
029 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a>
030 *
031 * @see #test(Collection, Collection)
032 */
033public class BeanQualifiersMatcher implements Matcher<Collection<? extends Attributes>, Collection<? extends Attributes>> {
034
035
036  /*
037   * Instance fields.
038   */
039
040
041  private final Qualifiers qualifiers;
042  
043  
044  /*
045   * Constructors.
046   */
047
048
049  /**
050   * Creates a new {@link BeanQualifiersMatcher}.
051   *
052   * @param qualifiers a {@link Qualifiers}; must not be {@code null}
053   *
054   * @exception NullPointerException if {@code qualifiers} is {@code null}
055   */
056  public BeanQualifiersMatcher(final Qualifiers qualifiers) {
057    super();
058    this.qualifiers = requireNonNull(qualifiers, "qualifiers");
059  }
060
061
062  /*
063   * Instance methods.
064   */
065
066
067  /**
068   * Returns {@code true} if and only if either (a) the collection of {@linkplain
069   * org.microbean.assign.Qualifiers#qualifiers(Collection) qualifiers present} in {@code receiverAttributes} is
070   * {@linkplain Collection#isEmpty() empty} and either the collection of {@linkplain
071   * org.microbean.assign.Qualifiers#qualifiers(Collection) qualifiers present} in {@code payloadAttributes} is also
072   * empty or contains the {@linkplain org.microbean.bean.Qualifiers#defaultQualifier() default qualifier}, or (b) if
073   * the collection of {@linkplain org.microbean.assign.Qualifiers#qualifiers(Collection) qualifiers present} in {@code
074   * payloadAttributes} {@linkplain Collection#isEmpty() is empty} or the return value of {@link
075   * org.microbean.bean.Qualifiers#anyAndDefaultQualifiers()} {@linkplain Collection#containsAll(Collection) contains
076   * all} of the {@linkplain org.microbean.assign.Qualifiers#qualifiers(Collection) qualifiers present} in {@code
077   * receiverAttributes}, or (c) if the collection of {@linkplain org.microbean.assign.Qualifiers#qualifiers(Collection)
078   * qualifiers present} in {@code payloadAttributes} {@linkplain Collection#containsAll(Collection) contains all} of
079   * the {@linkplain org.microbean.assign.Qualifiers#qualifiers(Collection) qualifiers present} in {@code
080   * receiverAttributes}.
081   *
082   * @param receiverAttributes a {@link Collection} of {@link Attributes}s; must not be {@code null}
083   *
084   * @param payloadAttributes a {@link Collection} of {@link Attributes}s; must not be {@code null}
085   *
086   * @return {@code true} if and only if either (a) the collection of {@linkplain
087   * org.microbean.assign.Qualifiers#qualifiers(Collection) qualifiers present} in {@code receiverAttributes} is
088   * {@linkplain Collection#isEmpty() empty} and either the collection of {@linkplain
089   * org.microbean.assign.Qualifiers#qualifiers(Collection) qualifiers present} in {@code payloadAttributes} is also
090   * empty or contains the {@linkplain org.microbean.bean.Qualifiers#defaultQualifier() default qualifier}, or (b) if
091   * the collection of {@linkplain org.microbean.bean.Qualifiers#qualifiers(Collection) qualifiers present} in {@code
092   * payloadAttributes} {@linkplain Collection#isEmpty() is empty} or the return value of {@link
093   * org.microbean.bean.Qualifiers#anyAndDefaultQualifiers()} {@linkplain Collection#containsAll(Collection) contains
094   * all} of the {@linkplain org.microbean.assign.Qualifiers#qualifiers(Collection) qualifiers present} in {@code
095   * receiverAttributes}, or (c) if the collection of {@linkplain org.microbean.assign.Qualifiers#qualifiers(Collection)
096   * qualifiers present} in {@code payloadAttributes} {@linkplain Collection#containsAll(Collection) contains all} of
097   * the {@linkplain org.microbean.assign.Qualifiers#qualifiers(Collection) qualifiers present} in {@code
098   * receiverAttributes}
099   *
100   * @exception NullPointerException if either {@code receiverAttributes} or {@code payloadAttributes} is {@code null}
101   */
102  @Override // Matcher<Collection<? extends Attributes>, Collection<? extends Attributes>>
103  public final boolean test(final Collection<? extends Attributes> receiverAttributes,
104                            final Collection<? extends Attributes> payloadAttributes) {
105    final Collection<? extends Attributes> receiverQualifiers = this.qualifiers(receiverAttributes);
106    Collection<? extends Attributes> payloadQualifiers = this.qualifiers(payloadAttributes);
107    // https://jakarta.ee/specifications/cdi/4.0/jakarta-cdi-spec-4.0#performing_typesafe_resolution
108    // "A bean is assignable to a given injection point if...the bean has all the required qualifiers. If no required
109    // qualifiers were explicitly specified, the container assumes the required qualifier @Default."
110    //
111    // https://jakarta.ee/specifications/cdi/4.0/jakarta-cdi-spec-4.0#builtin_qualifiers
112    // "Every bean has the built-in qualifier @Any, even if it does not explicitly declare this qualifier. If a bean
113    // does not explicitly declare a qualifier other than @Named or @Any, the bean has exactly one additional qualifier,
114    // of type @Default. This is called the default qualifier."
115    //
116    // https://jakarta.ee/specifications/cdi/4.0/jakarta-cdi-spec-4.0#injection_point_default_qualifier
117    // "If an injection point declares no qualifier, the injection point has exactly one qualifier, the default
118    // qualifier @Default."
119    return
120      receiverQualifiers.isEmpty() ? payloadQualifiers.isEmpty() || payloadQualifiers.contains(this.qualifiers.defaultQualifier()) :
121      (payloadQualifiers.isEmpty() ? this.qualifiers.anyAndDefaultQualifiers() : payloadQualifiers).containsAll(receiverQualifiers);
122  }
123
124  /**
125   * Returns an unmodifiable {@link Collection} consisting only of those {@link Attributes}s in the supplied {@link
126   * Collection} that are deemed to be qualifiers.
127   *
128   * <p>The default implementation of this method returns the value of an invocation of the {@link
129   * Qualifiers#qualifiers(Collection)} method.</p>
130   *
131   * <p>This method may be removed in favor of a compositional approach in future revisions of this class.</p>
132   *
133   * @param as a {@link Collection} of {@link Attributes}s; must not be {@code null}
134   *
135   * @return an unmodifiable {@link Collection} consisting only of those {@link Attributes}s in the supplied {@link
136   * Collection} that are deemed to be qualifiers; never {@code null}
137   *
138   * @exception NullPointerException if {@code as} is {@code null}
139   *
140   * @deprecated Pass a different {@link Qualifiers} to the {@link #BeanQualifiersMatcher(Qualifiers)} constructor.
141   */
142  @Deprecated(forRemoval = true)
143  protected Collection<? extends Attributes> qualifiers(final Collection<? extends Attributes> as) {
144    return this.qualifiers.qualifiers(as);
145  }
146
147}