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