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;
017import java.util.List;
018
019import org.microbean.assign.Matcher;
020
021import org.microbean.attributes.Attributes;
022
023import static org.microbean.bean.InterceptorBindings.anyInterceptorBinding;
024
025/**
026 * A {@link Matcher} encapsulating <a
027 * href="https://jakarta.ee/specifications/cdi/4.0/jakarta-cdi-spec-4.0#interceptors">CDI-compatible interceptor binding
028 * matching rules</a>.
029 *
030 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a>
031 *
032 * @see #test(Collection, Collection)
033 */
034public class InterceptorBindingsMatcher implements Matcher<Collection<? extends Attributes>, Collection<? extends Attributes>> {
035
036  /**
037   * Creates a new {@link InterceptorBindingsMatcher}.
038   */
039  public InterceptorBindingsMatcher() {
040    super();
041  }
042
043  /**
044   * Returns {@code true} if and only if either (a) both the collection of {@linkplain
045   * InterceptorBindings#interceptorBindings(Collection) interceptor bindings present} in {@code receiverAttributes} and
046   * the collection of {@linkplain InterceptorBindings#interceptorBindings(Collection) interceptor bindings present} in
047   * {@code payloadAttributes} are {@linkplain Collection#isEmpty() empty}, or (b) if the collection of {@linkplain
048   * InterceptorBindings#interceptorBindings(Collection) interceptor bindings present} in {@code payloadAttributes} has
049   * only one element and that element {@linkplain InterceptorBindings#anyInterceptorBinding(Attributes) is the
050   * <dfn>any</dfn> interceptor binding}, or (c) the sizes of the collection of {@linkplain
051   * InterceptorBindings#interceptorBindings(Collection) interceptor bindings present} in {@code receiverAttributes} and
052   * the collection of {@linkplain InterceptorBindings#interceptorBindings(Collection) interceptor bindings present} in
053   * {@code payloadAttributes} are the same and the collection of {@linkplain
054   * InterceptorBindings#interceptorBindings(Collection) interceptor bindings present} in {@code receiverAttributes}
055   * {@linkplain Collection#containsAll(Collection) contains all} the collection of {@linkplain
056   * InterceptorBindings#interceptorBindings(Collection) interceptor bindings present} in {@code payloadAttributes} and
057   * the collection of {@linkplain InterceptorBindings#interceptorBindings(Collection) interceptor bindings present} in
058   * {@code payloadAttributes} {@linkplain Collection#containsAll(Collection) contains all} the collection of
059   * {@linkplain InterceptorBindings#interceptorBindings(Collection) interceptor bindings present} in {@code
060   * receiverAttributes}.
061   *
062   * @param receiverAttributes a {@link Collection} of {@link Attributes}s; must not be {@code null}
063   *
064   * @param payloadAttributes a {@link Collection} of {@link Attributes}s; must not be {@code null}
065   *
066   * @return {@code true} if and only if either (a) both the collection of {@linkplain
067   * InterceptorBindings#interceptorBindings(Collection) interceptor bindings present} in {@code receiverAttributes} and
068   * the collection of {@linkplain InterceptorBindings#interceptorBindings(Collection) interceptor bindings present} in
069   * {@code payloadAttributes} are {@linkplain Collection#isEmpty() empty}, or (b) if the collection of {@linkplain
070   * InterceptorBindings#interceptorBindings(Collection) interceptor bindings present} in {@code payloadAttributes} has
071   * only one element and that element {@linkplain InterceptorBindings#anyInterceptorBinding(Attributes) is the
072   * <dfn>any</dfn> interceptor binding}, or (c) the sizes of the collection of {@linkplain
073   * InterceptorBindings#interceptorBindings(Collection) interceptor bindings present} in {@code receiverAttributes} and
074   * the collection of {@linkplain InterceptorBindings#interceptorBindings(Collection) interceptor bindings present} in
075   * {@code payloadAttributes} are the same and the collection of {@linkplain
076   * InterceptorBindings#interceptorBindings(Collection) interceptor bindings present} in {@code receiverAttributes}
077   * {@linkplain Collection#containsAll(Collection) contains all} the collection of {@linkplain
078   * InterceptorBindings#interceptorBindings(Collection) interceptor bindings present} in {@code payloadAttributes} and
079   * the collection of {@linkplain InterceptorBindings#interceptorBindings(Collection) interceptor bindings present} in
080   * {@code payloadAttributes} {@linkplain Collection#containsAll(Collection) contains all} the collection of
081   * {@linkplain InterceptorBindings#interceptorBindings(Collection) interceptor bindings present} in {@code
082   * receiverAttributes}
083   *
084   * @exception NullPointerException if either {@code receiverAttributes} or {@code payloadAttributes} is {@code null}
085   */
086  @Override // Matcher<Collection<? extends Attributes>, Collection<? extends Attributes>>
087  public final boolean test(final Collection<? extends Attributes> receiverAttributes,
088                            final Collection<? extends Attributes> payloadAttributes) {
089    final Collection<? extends Attributes> payloadBindings = interceptorBindings(payloadAttributes);
090    if (payloadBindings.isEmpty()) {
091      return interceptorBindings(receiverAttributes).isEmpty();
092    } else if (payloadBindings.size() == 1 && anyInterceptorBinding(payloadBindings.iterator().next())) {
093      return true;
094    }
095    final Collection<? extends Attributes> receiverBindings = interceptorBindings(receiverAttributes);
096    return
097      receiverBindings.size() == payloadBindings.size() &&
098      receiverBindings.containsAll(payloadBindings) &&
099      payloadBindings.containsAll(receiverBindings);
100  }
101
102  /**
103   * Given a {@link Collection} of {@link Attributes}s, returns an immutable {@link Collection} consisting of those
104   * {@link Attributes} instances that are deemed to be interceptor bindings.
105   *
106   * <p>The default implementation of this method returns the value of an invocation of the {@link
107   * InterceptorBindings#interceptorBindings(Collection)} method.</p>
108   *
109   * @param as a {@link Collection}; must not be {@code null}
110   *
111   * @return a {@link List} of interceptor bindings; never {@code null}
112   *
113   * @exception NullPointerException if {@code as} is {@code null}
114   */
115  protected Collection<? extends Attributes> interceptorBindings(final Collection<? extends Attributes> as) {
116    return InterceptorBindings.interceptorBindings(as);
117  }
118
119}