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}