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}