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}