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.qualifier.NamedAttributeMap;
021
022import static org.microbean.assign.Qualifiers.anyAndDefaultQualifiers;
023import static org.microbean.assign.Qualifiers.defaultQualifier;
024import static org.microbean.assign.Qualifiers.qualifiers;
025
026/**
027 * A {@link Matcher} encapsulating <a
028 * href="https://jakarta.ee/specifications/cdi/4.0/jakarta-cdi-spec-4.0#observertypesafe_resolution">CDI-compatible bean
029 * qualifier matching rules</a>.
030 *
031 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a>
032 *
033 * @see #test(Collection, Collection)
034 */
035public final class BeanQualifiersMatcher implements Matcher<Collection<? extends NamedAttributeMap<?>>, Collection<? extends NamedAttributeMap<?>>> {
036
037
038  /*
039   * Constructors.
040   */
041
042
043  /**
044   * Creates a new {@link BeanQualifiersMatcher}.
045   */
046  public BeanQualifiersMatcher() {
047    super();
048  }
049
050
051  /*
052   * Instance methods.
053   */
054
055
056  /**
057   * Returns {@code true} if and only if either (a) the collection of {@linkplain
058   * org.microbean.assign.Qualifiers#qualifiers(Collection) qualifiers present} in {@code receiverAttributes} is
059   * {@linkplain Collection#isEmpty() empty} and either the collection of {@linkplain
060   * org.microbean.assign.Qualifiers#qualifiers(Collection) qualifiers present} in {@code payloadAttributes} is also
061   * empty or contains the {@linkplain org.microbean.assign.Qualifiers#defaultQualifier() default qualifier}, or (b) if
062   * the collection of {@linkplain org.microbean.assign.Qualifiers#qualifiers(Collection) qualifiers present} in {@code
063   * payloadAttributes} {@linkplain Collection#isEmpty() is empty} or the return value of {@link
064   * org.microbean.assign.Qualifiers#anyAndDefaultQualifiers()} {@linkplain Collection#containsAll(Collection) contains
065   * all} of the {@linkplain org.microbean.assign.Qualifiers#qualifiers(Collection) qualifiers present} in {@code
066   * receiverAttributes}, or (c) if the collection of {@linkplain org.microbean.assign.Qualifiers#qualifiers(Collection)
067   * qualifiers present} in {@code payloadAttributes} {@linkplain Collection#containsAll(Collection) contains all} of
068   * the {@linkplain org.microbean.assign.Qualifiers#qualifiers(Collection) qualifiers present} in {@code
069   * receiverAttributes}.
070   *
071   * @param receiverAttributes a {@link Collection} of {@link NamedAttributeMap}s; must not be {@code null}
072   *
073   * @param payloadAttributes a {@link Collection} of {@link NamedAttributeMap}s; must not be {@code null}
074   *
075   * @return {@code true} if and only if either (a) the collection of {@linkplain
076   * org.microbean.assign.Qualifiers#qualifiers(Collection) qualifiers present} in {@code receiverAttributes} is
077   * {@linkplain Collection#isEmpty() empty} and either the collection of {@linkplain
078   * org.microbean.assign.Qualifiers#qualifiers(Collection) qualifiers present} in {@code payloadAttributes} is also
079   * empty or contains the {@linkplain org.microbean.assign.Qualifiers#defaultQualifier() default qualifier}, or (b) if
080   * the collection of {@linkplain org.microbean.assign.Qualifiers#qualifiers(Collection) qualifiers present} in {@code
081   * payloadAttributes} {@linkplain Collection#isEmpty() is empty} or the return value of {@link
082   * org.microbean.assign.Qualifiers#anyAndDefaultQualifiers()} {@linkplain Collection#containsAll(Collection) contains
083   * all} of the {@linkplain org.microbean.assign.Qualifiers#qualifiers(Collection) qualifiers present} in {@code
084   * receiverAttributes}, or (c) if the collection of {@linkplain org.microbean.assign.Qualifiers#qualifiers(Collection)
085   * qualifiers present} in {@code payloadAttributes} {@linkplain Collection#containsAll(Collection) contains all} of
086   * the {@linkplain org.microbean.assign.Qualifiers#qualifiers(Collection) qualifiers present} in {@code
087   * receiverAttributes}
088   *
089   * @exception NullPointerException if either {@code receiverAttributes} or {@code payloadAttributes} is {@code null}
090   */
091  @Override // Matcher<Collection<? extends NamedAttributeMap<?>>, Collection<? extends NamedAttributeMap<?>>>
092  public final boolean test(final Collection<? extends NamedAttributeMap<?>> receiverAttributes,
093                            final Collection<? extends NamedAttributeMap<?>> payloadAttributes) {
094    final Collection<? extends NamedAttributeMap<?>> receiverQualifiers = qualifiers(receiverAttributes);
095    Collection<? extends NamedAttributeMap<?>> payloadQualifiers = qualifiers(payloadAttributes);
096    // https://jakarta.ee/specifications/cdi/4.0/jakarta-cdi-spec-4.0#performing_typesafe_resolution
097    // "A bean is assignable to a given injection point if...the bean has all the required qualifiers. If no required
098    // qualifiers were explicitly specified, the container assumes the required qualifier @Default."
099    //
100    // https://jakarta.ee/specifications/cdi/4.0/jakarta-cdi-spec-4.0#builtin_qualifiers
101    // "Every bean has the built-in qualifier @Any, even if it does not explicitly declare this qualifier. If a bean
102    // does not explicitly declare a qualifier other than @Named or @Any, the bean has exactly one additional qualifier,
103    // of type @Default. This is called the default qualifier."
104    //
105    // https://jakarta.ee/specifications/cdi/4.0/jakarta-cdi-spec-4.0#injection_point_default_qualifier
106    // "If an injection point declares no qualifier, the injection point has exactly one qualifier, the default
107    // qualifier @Default."
108    return
109      receiverQualifiers.isEmpty() ? payloadQualifiers.isEmpty() || payloadQualifiers.contains(defaultQualifier()) :
110      (payloadQualifiers.isEmpty() ? anyAndDefaultQualifiers() : payloadQualifiers).containsAll(receiverQualifiers);
111  }
112
113}