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}