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