001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*- 002 * 003 * Copyright © 2025–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.LinkedHashSet; 019import java.util.List; 020import java.util.Map; 021import java.util.Map.Entry; 022import java.util.Set; 023import java.util.SequencedSet; 024 025import java.util.concurrent.ConcurrentHashMap; 026 027import java.util.function.BiConsumer; 028import java.util.function.Function; 029import java.util.function.Supplier; 030 031import javax.lang.model.element.AnnotationMirror; 032import javax.lang.model.element.Element; 033import javax.lang.model.element.ElementKind; 034import javax.lang.model.element.ExecutableElement; 035import javax.lang.model.element.QualifiedNameable; 036import javax.lang.model.element.TypeElement; 037 038import javax.lang.model.type.ArrayType; 039import javax.lang.model.type.DeclaredType; 040import javax.lang.model.type.IntersectionType; 041import javax.lang.model.type.TypeMirror; 042import javax.lang.model.type.TypeVariable; 043 044import org.microbean.assign.Annotated; 045import org.microbean.assign.Assignment; 046 047import org.microbean.bean.Creation; 048import org.microbean.bean.Destruction; 049import org.microbean.bean.Id; 050import org.microbean.bean.Qualifiers; 051import org.microbean.bean.ReferencesSelector; 052 053import org.microbean.construct.Domain; 054 055import org.microbean.interceptor.InterceptionFunction; 056import org.microbean.interceptor.InterceptorMethod; 057 058import static java.util.Collections.synchronizedMap; 059import static java.util.Collections.unmodifiableList; 060import static java.util.Collections.unmodifiableMap; 061import static java.util.Collections.unmodifiableSequencedSet; 062 063import static java.util.HashMap.newHashMap; 064 065import static java.util.HashSet.newHashSet; 066 067import static java.util.Objects.requireNonNull; 068 069import static org.microbean.interceptor.Interceptions.ofConstruction; 070import static org.microbean.interceptor.Interceptions.ofInvocation; 071import static org.microbean.interceptor.Interceptions.ofLifecycleEvent; 072 073/** 074 * A {@link Producer} that applies various kinds of interceptions to a delegate {@link Producer}. 075 * 076 * @param <I> the contextual instance type 077 * 078 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a> 079 */ 080public class InterceptingProducer<I> extends DelegatingProducer<I> { 081 082 083 /* 084 * Static fields. 085 */ 086 087 088 private static final List<InterceptorMethodType> TYPES = 089 List.of(AroundConstructInterceptorMethodType.INSTANCE, 090 PostConstructInterceptorMethodType.INSTANCE, 091 AroundInvokeInterceptorMethodType.INSTANCE, 092 PreDestroyInterceptorMethodType.INSTANCE); 093 094 private static final Map<Id, Map<ExecutableElement, Set<AnnotationMirror>>> ibs = new ConcurrentHashMap<>(); 095 096 private static final Map<Destruction, BiConsumer<? super Object, Destruction>> bcs = synchronizedMap(newHashMap(100)); 097 098 099 /* 100 * Instance fields. 101 */ 102 103 104 private final Domain domain; 105 106 private final AnnotationMirror anyQualifier; 107 108 private final InterceptorBindings interceptorBindings; 109 110 // i.e. the TypeMirror corresponding to org.microbean.producer.Interceptor.class 111 private final TypeMirror interceptorType; 112 113 private final InterceptionProxier proxier; 114 115 116 /* 117 * Constructors. 118 */ 119 120 121 /** 122 * Creates a new {@link InterceptingProducer}. 123 * 124 * @param domain a non-{@code null} {@link Domain} 125 * 126 * @param qualifiers a non-{@code null} {@link Qualifiers} 127 * 128 * @param interceptorBindings a non-{@code null} {@link InterceptorBindings} 129 * 130 * @param delegate a non-{@code null} {@link Producer} to which ultimate production will be delegated 131 * 132 * @param proxier a non-{@code null} {@link InterceptionProxier} 133 * 134 * @exception NullPointerException if any argument is {@code null} 135 */ 136 public InterceptingProducer(final Domain domain, 137 final Qualifiers qualifiers, 138 final InterceptorBindings interceptorBindings, 139 final Producer<I> delegate, 140 final InterceptionProxier proxier) { 141 super(delegate); 142 this.interceptorType = domain.declaredType(Interceptor.class.getCanonicalName()); 143 this.domain = domain; 144 this.anyQualifier = qualifiers.anyQualifier(); 145 this.interceptorBindings = requireNonNull(interceptorBindings, "interceptorBindings"); 146 this.proxier = requireNonNull(proxier, "proxier"); 147 } 148 149 150 /* 151 * Instance methods. 152 */ 153 154 155 @Override // DelegatingProducer<I> 156 public final SequencedSet<? extends Annotated<? extends Element>> dependencies() { 157 return super.dependencies(); 158 } 159 160 @Override // DelegatingProducer<I> 161 public final void dispose(final I i, final Destruction d) { 162 // Note that the removal here means you can only call dispose() once for a given instance if it has a pre-destroy. I 163 // think that's OK? 164 final BiConsumer<? super Object, Destruction> bc = bcs.remove(d); 165 if (bc != null) { 166 bc.accept(i, d); 167 } 168 super.dispose(i, d); 169 } 170 171 @Override // DelegatingProducer<I> 172 public final I produce(final Creation<I> c) { 173 if (c == null) { 174 return super.produce(c); 175 } 176 final Id id = c.id(); 177 178 final Map<ExecutableElement, Set<AnnotationMirror>> ibsByMethod = 179 ibs.computeIfAbsent(id, this::interceptorBindingsByMethod); 180 if (ibsByMethod.isEmpty()) { 181 // No interceptions. Bail out. 182 return super.produce(c); 183 } 184 185 final Map<ExecutableElement, List<InterceptorMethod>> interceptorMethodsByMethod = newHashMap(13); 186 final Map<InterceptorMethodType, Set<ExecutableElement>> methodsByInterceptorType = newHashMap(5); 187 for (final Entry<ExecutableElement, Set<AnnotationMirror>> e : ibsByMethod.entrySet()) { 188 for (final Interceptor interceptor : interceptors(e.getValue(), c)) { 189 for (final InterceptorMethodType type : TYPES) { 190 final Collection<? extends InterceptorMethod> interceptorMethods = interceptor.interceptorMethods(type); 191 if (interceptorMethods != null && !interceptorMethods.isEmpty()) { 192 final ExecutableElement method = e.getKey(); 193 methodsByInterceptorType.computeIfAbsent(type, t -> newHashSet(7)).add(method); 194 interceptorMethodsByMethod.computeIfAbsent(method, m -> new ArrayList<>(7)).addAll(interceptorMethods); 195 } 196 } 197 } 198 } 199 if (methodsByInterceptorType.isEmpty()) { 200 // No interceptions. Bail out. 201 return super.produce(c); 202 } 203 204 Supplier<I> s = () -> super.produce(c); 205 206 // Any around-constructs? 207 final Set<ExecutableElement> interceptedConstructors = 208 methodsByInterceptorType.remove(AroundConstructInterceptorMethodType.INSTANCE); 209 if (interceptedConstructors != null && !interceptedConstructors.isEmpty()) { 210 assert interceptedConstructors.size() == 1 : "interceptedConstructors: " + interceptedConstructors; 211 final List<InterceptorMethod> constructorInterceptorMethods = 212 interceptorMethodsByMethod.remove(interceptedConstructors.iterator().next()); 213 s = this.aroundConstructsSupplier(id, constructorInterceptorMethods, c); 214 } 215 if (methodsByInterceptorType.isEmpty()) { 216 // No additional interceptions. Bail out. 217 return s.get(); 218 } 219 220 // Any around-invokes? 221 final Set<ExecutableElement> interceptedBusinessMethods = 222 methodsByInterceptorType.remove(AroundInvokeInterceptorMethodType.INSTANCE); 223 if (interceptedBusinessMethods != null && !interceptedBusinessMethods.isEmpty()) { 224 s = this.aroundInvokesSupplier(s, interceptedBusinessMethods, interceptorMethodsByMethod::get, id); 225 } 226 if (methodsByInterceptorType.isEmpty()) { 227 // No additional interceptions. Bail out. 228 return s.get(); 229 } 230 231 // Any post-constructs? 232 final Set<ExecutableElement> postConstructMethods = 233 methodsByInterceptorType.remove(PostConstructInterceptorMethodType.INSTANCE); 234 if (postConstructMethods != null && !postConstructMethods.isEmpty()) { 235 s = postConstructsSupplier(s, postConstructMethods, interceptorMethodsByMethod::remove); 236 } 237 if (methodsByInterceptorType.isEmpty()) { 238 // No additional interceptions. Bail out. 239 return s.get(); 240 } 241 242 // Any pre-destroys? 243 final Set<ExecutableElement> preDestroyMethods = methodsByInterceptorType.remove(PreDestroyInterceptorMethodType.INSTANCE); 244 if (preDestroyMethods != null && !preDestroyMethods.isEmpty()) { 245 final BiConsumer<? super Object, Destruction> bc = 246 preDestroysBiConsumer(preDestroyMethods, interceptorMethodsByMethod::remove); 247 if (bc != null) { 248 bcs.put((Destruction)c, bc); 249 } 250 } 251 252 return s.get(); 253 } 254 255 @SuppressWarnings("unchecked") 256 private final Supplier<I> aroundConstructsSupplier(final Id id, 257 final Collection<? extends InterceptorMethod> constructorInterceptorMethods, 258 final ReferencesSelector rs) { 259 final SequencedSet<? extends Annotated<? extends Element>> pdeps = this.productionDependencies(); 260 final SequencedSet<? extends Annotated<? extends Element>> ideps = this.initializationDependencies(); 261 final InterceptionFunction creationFunction = 262 ofConstruction(constructorInterceptorMethods, 263 (ignored, argumentsArray) -> { 264 final SequencedSet<Assignment<?>> assignments = new LinkedHashSet<>(); 265 int i = 0; 266 for (final Annotated<? extends Element> pdep : pdeps) { 267 assignments.add(new Assignment<>(pdep, argumentsArray[i++])); 268 } 269 for (final Annotated<? extends Element> idep : ideps) { 270 assignments.add(new Assignment<>(idep, rs.reference(idep))); 271 } 272 return this.produce(id, unmodifiableSequencedSet(assignments)); 273 }); 274 return () -> { 275 final Object[] arguments = new Object[pdeps.size()]; 276 int i = 0; 277 for (final Annotated<? extends Element> pdep : pdeps) { 278 arguments[i++] = rs.reference(pdep); 279 } 280 return (I)creationFunction.apply(arguments); 281 }; 282 } 283 284 private final Supplier<I> aroundInvokesSupplier(final Supplier<I> s, 285 final Collection<? extends ExecutableElement> businessMethods, 286 final Function<? super ExecutableElement, List<InterceptorMethod>> f, 287 final Id id) { 288 if (businessMethods.isEmpty()) { 289 return s; 290 } 291 final Map<ExecutableElement, List<InterceptorMethod>> aroundInvokesByMethod = newHashMap(13); 292 for (final ExecutableElement businessMethod : businessMethods) { 293 aroundInvokesByMethod.put(businessMethod, f.apply(businessMethod)); 294 } 295 return aroundInvokesByMethod.isEmpty() ? s : () -> this.proxier.interceptionProxy(id, s, aroundInvokesByMethod); 296 } 297 298 private final Map<ExecutableElement, Set<AnnotationMirror>> interceptorBindingsByMethod(final Collection<? extends ExecutableElement> ees) { 299 if (ees.isEmpty()) { 300 return Map.of(); 301 } 302 // Precondition: ees contains only CONSTRUCTOR and METHOD kinds 303 final Map<ExecutableElement, Set<AnnotationMirror>> m = newHashMap(ees.size()); 304 for (final ExecutableElement ee : ees) { 305 m.put(ee, Set.copyOf(this.interceptorBindings.interceptorBindings(ee.getAnnotationMirrors()))); 306 } 307 return m.isEmpty() ? Map.of() : unmodifiableMap(m); 308 } 309 310 private final Map<ExecutableElement, Set<AnnotationMirror>> interceptorBindingsByMethod(final Id id) { 311 return this.interceptorBindingsByMethod(id.types().get(0)); 312 } 313 314 private final Map<ExecutableElement, Set<AnnotationMirror>> interceptorBindingsByMethod(final TypeMirror t) { 315 return switch (t.getKind()) { 316 case DECLARED -> this.interceptorBindingsByMethod(constructorsAndMethods(this.domain.allMembers((TypeElement)((DeclaredType)t).asElement()))); 317 default -> Map.of(); 318 }; 319 } 320 321 private final Iterable<Interceptor> interceptors(final Collection<? extends AnnotationMirror> bindingsSet, 322 final ReferencesSelector rs) { 323 if (bindingsSet.isEmpty()) { 324 return List.of(); 325 } 326 final List<AnnotationMirror> attributes = new ArrayList<>(bindingsSet.size() + 1); // + 1: reserve space for @Any 327 attributes.addAll(bindingsSet); 328 attributes.add(this.anyQualifier); 329 return rs.references(Annotated.of(this.domain.annotate(attributes, this.interceptorType))); 330 } 331 332 333 /* 334 * Static methods. 335 */ 336 337 338 private static final List<ExecutableElement> constructorsAndMethods(final Collection<? extends Element> elements) { 339 if (elements.isEmpty()) { 340 return List.of(); 341 } 342 final List<ExecutableElement> ees = new ArrayList<>(elements.size()); 343 for (final Element e : elements) { 344 switch (e.getKind()) { 345 case CONSTRUCTOR, METHOD -> ees.add((ExecutableElement)e); 346 } 347 } 348 return ees.isEmpty() ? List.of() : unmodifiableList(ees); 349 } 350 351 private static final <I> Supplier<I> postConstructsSupplier(final Supplier<I> s, 352 final Collection<? extends ExecutableElement> postConstructMethods, 353 final Function<? super ExecutableElement, List<InterceptorMethod>> f) { 354 if (postConstructMethods.isEmpty()) { 355 return s; 356 } 357 final List<InterceptorMethod> interceptorMethods = new ArrayList<>(); 358 for (final ExecutableElement pcm : postConstructMethods) { 359 final List<InterceptorMethod> ims = f.apply(pcm); 360 if (ims != null) { 361 interceptorMethods.addAll(ims); 362 } 363 } 364 if (interceptorMethods.isEmpty()) { 365 return s; 366 } 367 final TargetSupplier<I> targetSupplier = new TargetSupplier<>(); 368 final Runnable invoker = ofLifecycleEvent(interceptorMethods, targetSupplier, null); 369 return () -> { 370 final I i = s.get(); 371 targetSupplier.set(i); // TODO: thread safe? 372 invoker.run(); 373 return i; 374 }; 375 } 376 377 private static final <I> BiConsumer<I, Destruction> preDestroysBiConsumer(final Collection<? extends ExecutableElement> preDestroyMethods, 378 final Function<? super ExecutableElement, List<InterceptorMethod>> f) { 379 if (preDestroyMethods.isEmpty()) { 380 return null; 381 } 382 final List<InterceptorMethod> interceptorMethods = new ArrayList<>(); 383 for (final ExecutableElement pdm : preDestroyMethods) { 384 final List<InterceptorMethod> ims = f.apply(pdm); 385 if (ims != null) { 386 interceptorMethods.addAll(ims); 387 } 388 } 389 if (interceptorMethods.isEmpty()) { 390 return null; 391 } 392 final TargetSupplier<I> targetSupplier = new TargetSupplier<>(); 393 final Runnable invoker = ofLifecycleEvent(interceptorMethods, targetSupplier, null); 394 return (i, d) -> { 395 targetSupplier.set(i); // TODO: thread safe? 396 invoker.run(); 397 }; 398 } 399 400 401 /* 402 * Inner and nested classes. 403 */ 404 405 406 private static final class TargetSupplier<I> implements Supplier<I> { 407 408 private volatile I i; 409 410 private TargetSupplier() { 411 super(); 412 } 413 414 @Override // Supplier<I> 415 public final I get() { 416 return this.i; // volatile read 417 } 418 419 private final void set(final I i) { 420 this.i = i; // volatile write 421 } 422 423 } 424 425}