001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
002 *
003 * Copyright © 2023–2026 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.ArrayList;
017import java.util.Collection;
018import java.util.List;
019import java.util.Map;
020
021import java.util.function.Predicate;
022
023import javax.lang.model.element.AnnotationMirror;
024import javax.lang.model.element.Element;
025import javax.lang.model.element.ExecutableElement;
026import javax.lang.model.element.QualifiedNameable;
027import javax.lang.model.element.TypeElement;
028
029import org.microbean.construct.Domain;
030
031import org.microbean.construct.element.SyntheticAnnotationMirror;
032import org.microbean.construct.element.SyntheticAnnotationTypeElement;
033
034import static java.util.Collections.unmodifiableList;
035
036import static java.util.Objects.requireNonNull;
037
038import static javax.lang.model.element.ElementKind.ANNOTATION_TYPE;
039
040import static org.microbean.construct.element.AnnotationMirrors.sameAnnotation;
041import static org.microbean.construct.element.AnnotationMirrors.streamBreadthFirst;
042
043/**
044 * A utility class providing methods that work with <dfn>interceptor bindings</dfn>.
045 *
046 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a>
047 */
048public class InterceptorBindings {
049
050  private final Predicate<? super ExecutableElement> annotationElementInclusionPredicate;
051
052  private final Domain domain;
053
054  private final AnnotationMirror metaInterceptorBinding;
055
056  private final List<AnnotationMirror> metaInterceptorBindings;
057
058  private final AnnotationMirror anyInterceptorBinding;
059
060  private final List<AnnotationMirror> anyInterceptorBindings;
061  
062  /**
063   * Creates a new {@link InterceptorBindings}.
064   *
065   * @param domain a {@link Domain}; must not be {@code null}
066   *
067   * @exception NullPointerException if {@code domain} is {@code null}
068   *
069   * @see #InterceptorBindings(Domain, AnnotationMirror, AnnotationMirror, Predicate)
070   */
071  public InterceptorBindings(final Domain domain) {
072    this(domain, null, null, null);
073  }
074
075  /**
076   * Creates a new {@link InterceptorBindings}.
077   *
078   * @param domain a {@link Domain}; must not be {@code null}
079   *
080   * @param metaInterceptorBinding an {@link AnnotationMirror} to serve as the {@linkplain #metaInterceptorBinding()
081   * meta-interceptor binding}; may (commonly) be {@code null} in which case a synthetic meta-interceptor binding will
082   * be used instead
083   *
084   * @param anyInterceptorBinding an {@link AnnotationMirror} indicating that any interceptor binding should match; may
085   * be {@code null}
086   *
087   * @param annotationElementInclusionPredicate a {@link Predicate} that returns {@code true} if a given {@link
088   * ExecutableElement}, representing an annotation element, is to be included in any comparison operation; may be
089   * {@code null} in which case it is as if {@code ()-> true} were supplied instead
090   *
091   * @exception NullPointerException if {@code domain} is {@code null}
092   */
093  public InterceptorBindings(final Domain domain,
094                             final AnnotationMirror metaInterceptorBinding,
095                             final AnnotationMirror anyInterceptorBinding,
096                             final Predicate<? super ExecutableElement> annotationElementInclusionPredicate) {
097    super();
098    if (metaInterceptorBinding == null || anyInterceptorBinding == null) {
099      final List<? extends AnnotationMirror> as = domain.typeElement("java.lang.annotation.Documented").getAnnotationMirrors();
100      assert as.size() == 3; // @Documented, @Retention, @Target, in that order, all annotated in turn with each other
101      final AnnotationMirror documentedAnnotation = as.get(0);
102      final AnnotationMirror retentionAnnotation = as.get(1);
103      final AnnotationMirror targetAnnotation = as.get(2);
104      this.metaInterceptorBinding =
105        metaInterceptorBinding == null ?
106        new SyntheticAnnotationMirror(new SyntheticAnnotationTypeElement(List.of(documentedAnnotation,
107                                                                                 retentionAnnotation, // happens fortuitously to be RUNTIME
108                                                                                 targetAnnotation), // happens fortuitously to be ANNOTATION_TYPE
109                                                                         "InterceptorBinding")) :
110        metaInterceptorBinding;
111      this.anyInterceptorBinding =
112        anyInterceptorBinding == null ?
113        // TODO: meh, documented, retention, target, etc.
114        new SyntheticAnnotationMirror(new SyntheticAnnotationTypeElement(List.of(this.metaInterceptorBinding), "Any")) :
115        anyInterceptorBinding;
116    } else {
117      this.metaInterceptorBinding = metaInterceptorBinding;
118      this.anyInterceptorBinding = anyInterceptorBinding;
119    }
120    this.domain = domain; // may not be needed
121    this.annotationElementInclusionPredicate = annotationElementInclusionPredicate == null ? InterceptorBindings::returnTrue : annotationElementInclusionPredicate;
122    this.metaInterceptorBindings = List.of(this.metaInterceptorBinding);
123    this.anyInterceptorBindings = List.of(this.anyInterceptorBinding);
124  }
125
126  /**
127   * Returns a non-{@code null}, determinate {@link AnnotationMirror} representing the <dfn>any interceptor
128   * binding</dfn>.
129   *
130   * @return a non-{@code null}, determinate {@link AnnotationMirror} representing the <dfn>any interceptor
131   * binding</dfn>
132   */
133  public final AnnotationMirror anyInterceptorBinding() {
134    return this.anyInterceptorBinding;
135  }
136
137  /**
138   * Returns {@code true} if and only if the supplied {@link AnnotationMirror} is {@linkplain
139   * org.microbean.assign.Qualifiers#sameAnnotation(AnnotationMirror, AnnotationMirror) the same} as the {@linkplain
140   * #anyInterceptorBinding() <dfn>any interceptor binding</dfn>}.
141   *
142   * @param a a non-{@code null} {@link AnnotationMirror}
143   *
144   * @return {@code true} if and only if the supplied {@link AnnotationMirror} is {@linkplain
145   * org.microbean.assign.Qualifiers#sameAnnotation(AnnotationMirror, AnnotationMirror) the same} as the {@linkplain
146   * #anyInterceptorBinding() <dfn>any interceptor binding</dfn>}
147   *
148   * @exception NullPointerException if {@code a} is {@code null}
149   *
150   * @see #anyInterceptorBinding()
151   *
152   * @see org.microbean.construct.element.AnnotationMirrors#sameAnnotation(AnnotationMirror, AnnotationMirror,
153   * Predicate)
154   */
155  public final boolean anyInterceptorBinding(final AnnotationMirror a) {
156    return sameAnnotation(this.anyInterceptorBinding, a, this.annotationElementInclusionPredicate);
157  }
158
159  /**
160   * Returns a non-{@code null}, determinate, immutable {@link List} housing only the {@linkplain
161   * #anyInterceptorBinding() <dfn>any interceptor binding</dfn>}.
162   *
163   * @return a non-{@code null}, determinate, immutable {@link List} housing only the {@linkplain
164   * #anyInterceptorBinding() <dfn>any interceptor binding</dfn>}
165   */
166  public final List<AnnotationMirror> anyInterceptorBindings() {
167    return this.anyInterceptorBindings;
168  }
169  
170  /**
171   * Returns a non-{@code null}, determinate {@link AnnotationMirror} representing the (meta-) interceptor binding.
172   *
173   * @return a non-{@code null}, determinate {@link AnnotationMirror} representing the (meta-) interceptor binding
174   */
175  public AnnotationMirror metaInterceptorBinding() {
176    return this.metaInterceptorBinding;
177  }
178
179  /**
180   * Returns {@code true} if and only if the supplied {@link AnnotationMirror} has an {@linkplain
181   * AnnotationMirror#getAnnotationType() annotation type} declared by a {@link TypeElement} that is {@linkplain
182   * javax.lang.model.AnnotatedConstruct#getAnnotationMirrors() annotated with} at least one annotation {@linkplain
183   * #metaInterceptorBinding(AnnotationMirror) deemed to be the meta-interceptor binding}.
184   *
185   * @param a a non-{@code null} {@link AnnotationMirror}
186   *
187   * @return {@code true} if and only if the supplied {@link AnnotationMirror} has an {@linkplain
188   * AnnotationMirror#getAnnotationType() annotation type} declared by a {@link TypeElement} that is {@linkplain
189   * javax.lang.model.AnnotatedConstruct#getAnnotationMirrors() annotated with} at least one annotation {@linkplain
190   * #metaInterceptorBinding(AnnotationMirror) deemed to be the meta-interceptor binding}
191   *
192   * @exception NullPointerException if {@code a} is {@code null}
193   */
194  public final boolean metaInterceptorBinding(final AnnotationMirror a) {
195    return sameAnnotation(this.metaInterceptorBinding(), a, this.annotationElementInclusionPredicate);
196  }
197
198  /**
199   * Returns a non-{@code null}, determinate, immutable {@link List} whose sole element is the {@linkplain
200   * #metaInterceptorBinding() meta-interceptor binding} annotation.
201   *
202   * @return a non-{@code null}, determinate, immutable {@link List} whose sole element is the {@linkplain
203   * #metaInterceptorBinding() meta-interceptor binding} annotation
204   */
205  public final List<AnnotationMirror> metaInterceptorBindings() {
206    return this.metaInterceptorBindings;
207  }
208
209  /**
210   * Returns {@code true} if and only if the supplied {@link AnnotationMirror} has an {@linkplain
211   * AnnotationMirror#getAnnotationType() annotation type} declared by a {@link TypeElement} that is {@linkplain
212   * javax.lang.model.AnnotatedConstruct#getAnnotationMirrors() annotated with} at least one annotation {@linkplain
213   * #metaInterceptorBinding(AnnotationMirror) deemed to be the meta-interceptor binding}.
214   *
215   * @param a a non-{@code null} {@link AnnotationMirror}
216   *
217   * @return {@code true} if and only if the supplied {@link AnnotationMirror} has an {@linkplain
218   * AnnotationMirror#getAnnotationType() annotation type} declared by a {@link TypeElement} that is {@linkplain
219   * javax.lang.model.AnnotatedConstruct#getAnnotationMirrors() annotated with} at least one annotation {@linkplain
220   * #metaInterceptorBinding(AnnotationMirror) deemed to be the meta-interceptor binding}
221   *
222   * @exception NullPointerException if {@code a} is {@code null}
223   */
224  public final boolean interceptorBinding(final AnnotationMirror a) {
225    if (!this.metaInterceptorBinding(a)) {
226      final Element annotationInterface = a.getAnnotationType().asElement();
227      if (annotationInterface.getKind() == ANNOTATION_TYPE) {
228        for (final AnnotationMirror ma : annotationInterface.getAnnotationMirrors()) {
229          if (this.metaInterceptorBinding(ma)) {
230            return true;
231          }
232        }
233      }
234    }
235    return false;
236  }
237
238  /**
239   * Returns a non-{@code null}, determinate, immutable {@link List} of {@link AnnotationMirror} instances drawn from
240   * the supplied {@link Collection} that are {@linkplain #interceptorBinding(AnnotationMirror) deemed to be
241   * interceptor bindings}.
242   *
243   * <p>In this implementation, cycles are avoided and comparisons are accomplished with the {@link
244   * org.microbean.construct.element.AnnotationMirrors#sameAnnotation(AnnotationMirror, AnnotationMirror, Predicate)}
245   * method.</p>
246   *
247   * <p>This implementation eliminates duplicates as calculated via the {@link
248   * org.microbean.construct.element.AnnotationMirrors#sameAnnotation(AnnotationMirror, AnnotationMirror, Predicate)}
249   * method.</p>
250   *
251   * <p>This implementation considers interceptor bindings to be <dfn>transitive</dfn>. Consequently, the returned
252   * {@link List} may be greater in {@linkplain List#size() size} than the supplied {@link Collection}.</p>
253   *
254   * @param as a non-{@code null}, determinate {@link Collection} of {@link AnnotationMirror}s
255   *
256   * @return a non-{@code null}, determinate, immutable {@link List} of {@link AnnotationMirror} instances drawn from
257   * the supplied {@link Collection} that were {@linkplain #interceptorBinding(AnnotationMirror) deemed to be
258   * interceptor bindings}
259   *
260   * @exception NullPointerException if {@code as} is {@code null}
261   *
262   * @see #interceptorBinding(AnnotationMirror)
263   *
264   * @see org.microbean.construct.element.AnnotationMirrors#sameAnnotation(AnnotationMirror, AnnotationMirror,
265   * Predicate)
266   */
267  public List<AnnotationMirror> interceptorBindings(final Collection<? extends AnnotationMirror> as) {
268    if (as.isEmpty()) {
269      return List.of();
270    }
271    final List<AnnotationMirror> seen = new ArrayList<>(as.size()); // size is arbitrary
272    return
273      streamBreadthFirst(as)
274      .filter(a0 -> {
275          for (final AnnotationMirror a1 : seen) {
276            if (sameAnnotation(a0, a1, this.annotationElementInclusionPredicate)) {
277              return false; // we included it already
278            }
279          }
280          return this.interceptorBinding(a0) && seen.add(a0);
281        })
282      .toList();
283  }
284
285  private static final <X> boolean returnTrue(final X ignored) {
286    return true;
287  }
288
289}