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}