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