001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
002 *
003 * Copyright © 2024–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.interceptor;
015
016import java.lang.System.Logger;
017
018import java.lang.annotation.Annotation;
019
020import java.lang.invoke.MethodHandle;
021import java.lang.invoke.MethodHandles.Lookup;
022import java.lang.invoke.MethodType;
023import java.lang.invoke.VarHandle;
024
025import java.lang.reflect.Constructor;
026import java.lang.reflect.Method;
027
028import java.util.Arrays;
029import java.util.Collection;
030import java.util.Iterator;
031import java.util.List;
032import java.util.Map;
033import java.util.Objects;
034import java.util.Optional;
035import java.util.Set;
036
037import java.util.concurrent.ConcurrentHashMap;
038import java.util.concurrent.ConcurrentMap;
039
040import java.util.function.BiFunction;
041import java.util.function.Consumer;
042import java.util.function.Supplier;
043
044import jakarta.interceptor.InvocationContext;
045
046import static java.lang.System.getLogger;
047import static java.lang.System.Logger.Level.DEBUG;
048
049import static java.lang.invoke.MethodHandles.lookup;
050import static java.lang.invoke.MethodHandles.privateLookupIn;
051
052/**
053 * A utility class that makes {@link InterceptionFunction}s and {@link Runnable}s that intercept lifecycle events,
054 * constructions, and invocations of methods in accordance with the Jakarta Interceptors specification.
055 *
056 * @author <a href="https://about.me/lairdnelson/" target="_top">Laird Nelson</a>
057 */
058public final class Interceptions {
059
060
061  /*
062   * Static fields.
063   */
064
065
066  private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];
067
068  private static final Property<Object[]> EMPTY_OBJECT_ARRAY_PROPERTY = new Property<>(Interceptions::emptyObjectArray, false);
069
070  private static final Logger LOGGER = getLogger(Interceptions.class.getName());
071
072
073  /*
074   * Instance fields.
075   */
076
077
078  private final ConcurrentMap<String, Object> data;
079
080  private final Supplier<? extends Constructor<?>> constructorBootstrap;
081
082  private final Supplier<? extends Method> methodBootstrap;
083
084  private final Supplier<?> timerBootstrap;
085
086  private final Supplier<? extends Set<Annotation>> interceptorBindingsBootstrap;
087
088
089  /*
090   * Constructors.
091   */
092
093
094  private Interceptions(final Supplier<? extends Constructor<?>> constructorBootstrap,
095                        final Supplier<? extends Method> methodBootstrap,
096                        final Supplier<?> timerBootstrap,
097                        final Supplier<? extends Set<Annotation>> interceptorBindingsBootstrap) {
098    super();
099    this.data = new ConcurrentHashMap<>();
100    this.constructorBootstrap = constructorBootstrap == null ? Interceptions::returnNull : constructorBootstrap;
101    this.methodBootstrap = methodBootstrap == null ? Interceptions::returnNull : methodBootstrap;
102    this.timerBootstrap = timerBootstrap == null ? Interceptions::returnNull : timerBootstrap;
103    this.interceptorBindingsBootstrap = interceptorBindingsBootstrap == null ? Set::of : interceptorBindingsBootstrap;
104  }
105
106
107  /*
108   * Static methods.
109   */
110
111
112  /**
113   * Returns a {@link Runnable} whose {@link Runnable#run() run()} method will invoke all supplied {@link
114   * InterceptorMethod}s in encounter order.
115   *
116   * @param interceptorMethods the {@link InterceptorMethod}s to invoke; may be {@code null} in which case the returned
117   * {@link Runnable}'s {@link Runnable#run() run()} method will do nothing
118   *
119   * @param targetBootstrap a {@link Supplier} that will be called for the initial value to be returned by the first
120   * invocation of {@link InvocationContext#getTarget()}; may be {@code null} in which case the value too will be {@code
121   * null}
122   *
123   * @return a {@link Runnable}; never {@code null}
124   */
125  // Methodless lifecycle event interception. For example, post-construct interceptions by external interceptors only.
126  public static final Runnable ofLifecycleEvent(final Collection<? extends InterceptorMethod> interceptorMethods,
127                                                final Supplier<?> targetBootstrap) {
128    return ofLifecycleEvent(interceptorMethods, targetBootstrap, Set::of);
129  }
130
131  /**
132   * Returns a {@link Runnable} whose {@link Runnable#run() run()} method will invoke all supplied {@link
133   * InterceptorMethod}s in encounter order.
134   *
135   * @param interceptorMethods the {@link InterceptorMethod}s to invoke; may be {@code null} in which case the returned
136   * {@link Runnable}'s {@link Runnable#run() run()} method will do nothing
137   *
138   * @param targetBootstrap a {@link Supplier} that will be called for the initial value to be returned by the first
139   * invocation of {@link InvocationContext#getTarget()}; may be {@code null} in which case the value too will be {@code
140   * null}
141   *
142   * @param interceptorBindingsBootstrap a {@link Supplier} of a {@link Set} of {@link Annotation}s that will be called
143   * for the value to be returned by the first invocation of {@link InvocationContext#getInterceptorBindings()}; may be
144   * {@code null} in which case the value will be an {@linkplain Set#of() empty, immutable <code>Set</code>}
145   *
146   * @return a {@link Runnable}; never {@code null}
147   */
148  // Methodless lifecycle event interception. For example, post-construct interceptions by external interceptors only.
149  public static final Runnable ofLifecycleEvent(final Collection<? extends InterceptorMethod> interceptorMethods,
150                                                final Supplier<?> targetBootstrap,
151                                                final Supplier<? extends Set<Annotation>> interceptorBindingsBootstrap) {
152    return () -> {
153      ff(interceptorMethods,
154         null,
155         targetBootstrap,
156         null, // argumentsValidator
157         false, // setTarget
158         null, // cb
159         null, // mb // TODO: may have to be the last im in the chain if it's defined on the target class (!); weird requirement
160         null, // tb
161         interceptorBindingsBootstrap)
162        .apply(EMPTY_OBJECT_ARRAY);
163    };
164  }
165
166  /**
167   * Returns an {@link InterceptionFunction} whose {@link InterceptionFunction#apply(Object...) apply(Object...)} method
168   * will invoke all supplied {@link InterceptorMethod}s in encounter order before invoking the supplied {@link
169   * Constructor}'s {@link Constructor#newInstance(Object...) newInstance(Object...)} method.
170   *
171   * @param interceptorMethods the {@link InterceptorMethod}s to invoke; may be {@code null} (rather uselessly)
172   *
173   * @param lookup a {@link Lookup}; must not be {@code null}
174   *
175   * @param constructor the {@link Constructor} to invoke; may be {@code null} (rather uselessly)
176   *
177   * @return an {@link InterceptionFunction}; never {@code null}
178   *
179   * @exception NullPointerException if {@code lookup} is {@code null}
180   *
181   * @exception IllegalAccessException if {@linkplain Lookup#unreflectConstructor(Constructor) unreflecting} fails
182   */
183  // Around-construct
184  public static final InterceptionFunction ofConstruction(final Collection<? extends InterceptorMethod> interceptorMethods,
185                                                          final Lookup lookup,
186                                                          final Constructor<?> constructor)
187    throws IllegalAccessException {
188    return ofConstruction(interceptorMethods, lookup, constructor, Set::of);
189  }
190
191  /**
192   * Returns an {@link InterceptionFunction} whose {@link InterceptionFunction#apply(Object...) apply(Object...)} method
193   * will invoke all supplied {@link InterceptorMethod}s in encounter order before invoking the supplied {@link
194   * Constructor}'s {@link Constructor#newInstance(Object...) newInstance(Object...)} method.
195   *
196   * @param interceptorMethods the {@link InterceptorMethod}s to invoke; may be {@code null} (rather uselessly)
197   *
198   * @param lookup a {@link Lookup}; must not be {@code null}
199   *
200   * @param constructor the {@link Constructor} to invoke; may be {@code null} (rather uselessly)
201   *
202   * @param interceptorBindingsBootstrap a {@link Supplier} of a {@link Set} of {@link Annotation}s that will be called
203   * for the value to be returned by the first invocation of {@link InvocationContext#getInterceptorBindings()}; may be
204   * {@code null} in which case the value will be an {@linkplain Set#of() empty, immutable <code>Set</code>}
205   *
206   * @return an {@link InterceptionFunction}; never {@code null}
207   *
208   * @exception NullPointerException if {@code lookup} is {@code null}
209   *
210   * @exception IllegalAccessException if {@linkplain Lookup#unreflectConstructor(Constructor) unreflecting} fails
211   */
212  // Around-construct
213  public static final InterceptionFunction ofConstruction(final Collection<? extends InterceptorMethod> interceptorMethods,
214                                                          final Lookup lookup,
215                                                          final Constructor<?> constructor,
216                                                          final Supplier<? extends Set<Annotation>> interceptorBindingsBootstrap)
217    throws IllegalAccessException {
218    return
219      ff(interceptorMethods,
220         terminalBiFunctionOf(lookup, constructor),
221         null, // targetBootstrap
222         a -> validate(constructor.getParameterTypes(), a),
223         true, // setTarget
224         () -> constructor,
225         null, // mb
226         null, // tb
227         interceptorBindingsBootstrap);
228  }
229
230  /**
231   * Returns an {@link InterceptionFunction} whose {@link InterceptionFunction#apply(Object...) apply(Object...)} method
232   * will invoke all supplied {@link InterceptorMethod}s in encounter order before invoking the supplied {@link
233   * BiFunction}'s {@link BiFunction#apply(Object, Object) apply(Object, Object[])} method with {@code null} (the return
234   * value of {@link InvocationContext#getTarget()}, which will always be {@code null} in this scenario) and the return
235   * value of an invocation of {@link InvocationContext#getParameters()}.
236   *
237   * @param interceptorMethods the {@link InterceptorMethod}s to invoke; may be {@code null} (rather uselessly)
238   *
239   * @param terminalBiFunction a {@link BiFunction} serving as a notional constructor that takes {@code null}, always,
240   * as its first argument, and the return value of an invocation of {@link InvocationContext#getParameters()} as its
241   * second argument, and returns a new instance; may be {@code null} (rather uselessly)
242   *
243   * @return an {@link InterceptionFunction}; never {@code null}
244   */
245  // Around-construct
246  public static final InterceptionFunction ofConstruction(final Collection<? extends InterceptorMethod> interceptorMethods,
247                                                          final BiFunction<? super Object, ? super Object[], ?> terminalBiFunction) {
248    return ofConstruction(interceptorMethods, terminalBiFunction, Set::of);
249  }
250
251  /**
252   * Returns an {@link InterceptionFunction} whose {@link InterceptionFunction#apply(Object...) apply(Object...)} method
253   * will invoke all supplied {@link InterceptorMethod}s in encounter order before invoking the supplied {@link
254   * BiFunction}'s {@link BiFunction#apply(Object, Object) apply(Object Object[])} method with {@code null} (the return
255   * value of {@link InvocationContext#getTarget()}, which will always be {@code null} in this scenario) and the return
256   * value of an invocation of {@link InvocationContext#getParameters()}.
257   *
258   * @param interceptorMethods the {@link InterceptorMethod}s to invoke; may be {@code null} (rather uselessly)
259   *
260   * @param terminalBiFunction a {@link BiFunction} serving as a notional constructor that takes {@code null}, always,
261   * as its first argument, and the return value of an invocation of {@link InvocationContext#getParameters()} as its
262   * second argument, and returns a new instance; may be {@code null} (rather uselessly)
263   *
264   * @param interceptorBindingsBootstrap a {@link Supplier} of a {@link Set} of {@link Annotation}s that will be called
265   * for the value to be returned by the first invocation of {@link InvocationContext#getInterceptorBindings()}; may be
266   * {@code null} in which case the value will be an {@linkplain Set#of() empty, immutable <code>Set</code>}
267   *
268   * @return an {@link InterceptionFunction}; never {@code null}
269   */
270  // Around-construct
271  public static final InterceptionFunction ofConstruction(final Collection<? extends InterceptorMethod> interceptorMethods,
272                                                          final BiFunction<? super Object, ? super Object[], ?> terminalBiFunction,
273                                                          final Supplier<? extends Set<Annotation>> interceptorBindingsBootstrap) {
274    return
275      ff(interceptorMethods,
276         terminalBiFunction,
277         null, // targetBootstrap
278         null, // argumentsValidator
279         true, // setTarget
280         null, // cb
281         null, // mb
282         null, // tb
283         interceptorBindingsBootstrap);
284  }
285
286  /**
287   * Returns an {@link InterceptionFunction} whose {@link InterceptionFunction#apply(Object...) apply(Object...)} method
288   * will invoke all supplied {@link InterceptorMethod}s in encounter order before invoking the supplied {@link
289   * Method}'s {@link Method#invoke(Object, Object...) invoke(Object, Object...)} method with the return value of {@link
290   * InvocationContext#getTarget()}, and with the return value of {@link InvocationContext#getParameters()}.
291   *
292   * @param interceptorMethods the {@link InterceptorMethod}s to invoke; may be {@code null} (rather uselessly)
293   *
294   * @param lookup a {@link Lookup}; must not be {@code null}
295   *
296   * @param method a {@link Method} encapsulating the invocation to be intercepted whose {@link Method#invoke(Object,
297   * Object...)  invoke(Object, Object...)} method takes the return value of {@link InvocationContext#getTarget()} as
298   * its first argument, and the return value of {@link InvocationContext#getParameters()} spread out appropriately as
299   * its trailing arguments; may be {@code null} (rather uselessly)
300   *
301   * @param targetBootstrap a {@link Supplier} that will be called for the initial value to be returned by the first
302   * invocation of {@link InvocationContext#getTarget()}; may be {@code null} in which case the value too will be {@code
303   * null}
304   *
305   * @return an {@link InterceptionFunction}; never {@code null}
306   *
307   * @exception NullPointerException if {@code lookup} is {@code null}
308   *
309   * @exception IllegalAccessException if {@linkplain Lookup#unreflect(Method) unreflecting} fails
310   */
311  // Around-invoke or similar.
312  public static final InterceptionFunction ofInvocation(final Collection<? extends InterceptorMethod> interceptorMethods,
313                                                        final Lookup lookup,
314                                                        final Method method, // not nullable
315                                                        final Supplier<?> targetBootstrap)
316    throws IllegalAccessException {
317    return ofInvocation(interceptorMethods, lookup, method, targetBootstrap, Set::of);
318  }
319
320  /**
321   * Returns an {@link InterceptionFunction} whose {@link InterceptionFunction#apply(Object...) apply(Object...)} method
322   * will invoke all supplied {@link InterceptorMethod}s in encounter order before invoking the supplied {@link
323   * Method}'s {@link Method#invoke(Object, Object...) invoke(Object, Object...)} method with the return value of {@link
324   * InvocationContext#getTarget()}, and with the return value of {@link InvocationContext#getParameters()}.
325   *
326   * @param interceptorMethods the {@link InterceptorMethod}s to invoke; may be {@code null} (rather uselessly)
327   *
328   * @param lookup a {@link Lookup}; must not be {@code null}
329   *
330   * @param method a {@link Method} encapsulating the invocation to be intercepted whose {@link Method#invoke(Object,
331   * Object...)  invoke(Object, Object...)} method takes the return value of {@link InvocationContext#getTarget()} as
332   * its first argument, and the return value of {@link InvocationContext#getParameters()} spread out appropriately as
333   * its trailing arguments; may be {@code null} (rather uselessly)
334   *
335   * @param targetBootstrap a {@link Supplier} that will be called for the initial value to be returned by the first
336   * invocation of {@link InvocationContext#getTarget()}; may be {@code null} in which case the value too will be {@code
337   * null}
338   *
339   * @param interceptorBindingsBootstrap a {@link Supplier} of a {@link Set} of {@link Annotation}s that will be called
340   * for the value to be returned by the first invocation of {@link InvocationContext#getInterceptorBindings()}; may be
341   * {@code null} in which case the value will be an {@linkplain Set#of() empty, immutable <code>Set</code>}
342   *
343   * @return an {@link InterceptionFunction}; never {@code null}
344   *
345   * @exception NullPointerException if {@code lookup} is {@code null}
346   *
347   * @exception IllegalAccessException if {@linkplain Lookup#unreflect(Method) unreflecting} fails
348   */
349  // Around-invoke or similar.
350  public static final InterceptionFunction ofInvocation(final Collection<? extends InterceptorMethod> interceptorMethods,
351                                                        final Lookup lookup,
352                                                        final Method method, // not nullable
353                                                        final Supplier<?> targetBootstrap,
354                                                        final Supplier<? extends Set<Annotation>> interceptorBindingsBootstrap)
355    throws IllegalAccessException {
356    return
357      ff(interceptorMethods,
358         method == null ? null : terminalBiFunctionOf(lookup, method),
359         targetBootstrap,
360         method == null ? null : a -> validate(method.getParameterTypes(), a),
361         false, // setTarget
362         null, // cb,
363         method == null ? null : () -> method,
364         null, // tb
365         interceptorBindingsBootstrap);
366  }
367
368  /**
369   * Returns an {@link InterceptionFunction} whose {@link InterceptionFunction#apply(Object...) apply(Object...)} method
370   * will invoke all supplied {@link InterceptorMethod}s in encounter order before invoking the supplied {@link
371   * BiFunction}'s {@link BiFunction#apply(Object, Object) apply(Object Object[])} method with the return value of
372   * {@link InvocationContext#getTarget()}, and with the return value of {@link InvocationContext#getParameters()}.
373   *
374   * @param interceptorMethods the {@link InterceptorMethod}s to invoke; may be {@code null} (rather uselessly)
375   *
376   * @param terminalBiFunction a {@link BiFunction} encapsulating the invocation to be intercepted that takes the return
377   * value of {@link InvocationContext#getTarget()} as its first argument, and the return value of {@link
378   * InvocationContext#getParameters()} as its second argument; may be {@code null} (rather uselessly)
379   *
380   * @param targetBootstrap a {@link Supplier} that will be called for the initial value to be returned by the first
381   * invocation of {@link InvocationContext#getTarget()}; may be {@code null} in which case the value too will be {@code
382   * null}
383   *
384   * @return an {@link InterceptionFunction}; never {@code null}
385   */
386  // Around-invoke or similar.
387  public static final InterceptionFunction ofInvocation(final Collection<? extends InterceptorMethod> interceptorMethods,
388                                                        final BiFunction<? super Object, ? super Object[], ?> terminalBiFunction,
389                                                        final Supplier<?> targetBootstrap) {
390    return ofInvocation(interceptorMethods, terminalBiFunction, targetBootstrap, Set::of);
391  }
392
393  /**
394   * Returns an {@link InterceptionFunction} whose {@link InterceptionFunction#apply(Object...) apply(Object...)} method
395   * will invoke all supplied {@link InterceptorMethod}s in encounter order before invoking the supplied {@link
396   * BiFunction}'s {@link BiFunction#apply(Object, Object) apply(Object Object[])} method with the return value of
397   * {@link InvocationContext#getTarget()}, and with the return value of {@link InvocationContext#getParameters()}.
398   *
399   * @param interceptorMethods the {@link InterceptorMethod}s to invoke; may be {@code null} (rather uselessly)
400   *
401   * @param terminalBiFunction a {@link BiFunction} encapsulating the invocation to be intercepted that takes the return
402   * value of {@link InvocationContext#getTarget()} as its first argument, and the return value of {@link
403   * InvocationContext#getParameters()} as its second argument; may be {@code null} (rather uselessly)
404   *
405   * @param targetBootstrap a {@link Supplier} that will be called for the initial value to be returned by the first
406   * invocation of {@link InvocationContext#getTarget()}; may be {@code null} in which case the value too will be {@code
407   * null}
408   *
409   * @param interceptorBindingsBootstrap a {@link Supplier} of a {@link Set} of {@link Annotation}s that will be called
410   * for the value to be returned by the first invocation of {@link InvocationContext#getInterceptorBindings()}; may be
411   * {@code null} in which case the value will be an {@linkplain Set#of() empty, immutable <code>Set</code>}
412   *
413   * @return an {@link InterceptionFunction}; never {@code null}
414   */
415  // Around-invoke or similar.
416  public static final InterceptionFunction ofInvocation(final Collection<? extends InterceptorMethod> interceptorMethods,
417                                                        final BiFunction<? super Object, ? super Object[], ?> terminalBiFunction,
418                                                        final Supplier<?> targetBootstrap,
419                                                        final Supplier<? extends Set<Annotation>> interceptorBindingsBootstrap) {
420    return
421      ff(interceptorMethods,
422         terminalBiFunction,
423         targetBootstrap,
424         null, // argumentsValidator
425         false, // setTarget
426         null, // cb,
427         null, // mb,
428         null, // tb,
429         interceptorBindingsBootstrap);
430  }
431
432  // ff for function factory :-)
433  private static InterceptionFunction ff(final Collection<? extends InterceptorMethod> interceptorMethods,
434                                         final BiFunction<? super Object, ? super Object[], ?> tbf,
435                                         final Supplier<?> targetBootstrap,
436                                         final Consumer<? super Object[]> argumentsValidator,
437                                         final boolean setTarget,
438                                         final Supplier<? extends Constructor<?>> cb,
439                                         final Supplier<? extends Method> mb,
440                                         final Supplier<?> tb,
441                                         final Supplier<? extends Set<Annotation>> interceptorBindingsBootstrap) {
442    final Interceptions i;
443    final List<? extends InterceptorMethod> ims;
444    if (tbf == null) {
445      if (interceptorMethods == null || interceptorMethods.isEmpty()) {
446        return Interceptions::returnNull;
447      }
448      ims = List.copyOf(interceptorMethods);
449      // We can get away with hoisting this Property out of the function scope because we know it will never change.
450      final Property<Object> target = new Property<Object>(targetBootstrap, false);
451      i = new Interceptions(cb, mb, tb, interceptorBindingsBootstrap);
452      return a ->
453        i.new State(target)
454          .new Context(ims.iterator())
455          .proceed();
456    } else if (interceptorMethods == null || interceptorMethods.isEmpty()) {
457      final Property<Object> target = new Property<Object>(targetBootstrap, false);
458      return
459        argumentsValidator == null ?
460        Interceptions::returnNull :
461        a -> {
462          argumentsValidator.accept(a);
463          return tbf.apply(target.get(), a);
464        };
465    } else {
466      ims = List.copyOf(interceptorMethods);
467      i = new Interceptions(cb, mb, tb, interceptorBindingsBootstrap);
468      if (setTarget) {
469        return a -> {
470          final State s = i.new State(new Property<>(targetBootstrap, true),
471                                      new Property<>(a == null ? Interceptions::emptyObjectArray : () -> a,
472                                                     argumentsValidator,
473                                                     true));
474          final State.Context c = s
475            .new Context(ims.iterator(),
476                         (t, a2) -> {
477                           s.target(tbf.apply(t, a2));
478                           return s.target();
479            });
480          final Object v = c.proceed();
481          final Object t = c.getTarget();
482          if (v != t && LOGGER.isLoggable(DEBUG)) {
483            LOGGER.log(DEBUG, "around-construct proceed() return value: " + v + "; returning getTarget() return value: " + t);
484          }
485          return t;
486        };
487      }
488      // We can get away with hoisting this Property out of the function scope because we know it will never change.
489      final Property<Object> target = new Property<Object>(targetBootstrap, false);
490      return a -> {
491        return
492          i.new State(target, new Property<Object[]>(a == null ? Interceptions::emptyObjectArray : () -> a,
493                                                     argumentsValidator,
494                                                     true))
495          .new Context(ims.iterator(), tbf)
496            .proceed();
497      };
498    }
499  }
500
501  /**
502   * Creates and returns a {@link BiFunction} encapsulating the supplied {@link Constructor}.
503   *
504   * @param lookup a {@link Lookup}; must not be {@code null}
505   *
506   * @param c a {@link Constructor}; must not be {@code null}
507   *
508   * @return a {@link BiFunction} encapsulating the supplied {@link Constructor}; never {@code null}
509   *
510   * @exception NullPointerException if any argumebnt is {@code null}
511   *
512   * @exception IllegalAccessException if {@linkplain Lookup#unreflectConstructor(Constructor) unreflecting} fails
513   */
514  public static final BiFunction<Object, Object[], Object> terminalBiFunctionOf(final Lookup lookup, final Constructor<?> c)
515    throws IllegalAccessException {
516    return terminalBiFunctionOf(privateLookupIn(c.getDeclaringClass(), lookup).unreflectConstructor(c));
517  }
518
519  /**
520   * Creates and returns a {@link BiFunction} encapsulating the supplied {@link Method}.
521   *
522   * @param lookup a {@link Lookup}; must not be {@code null}
523   *
524   * @param m a {@link Method}; must not be {@code null}
525   *
526   * @return a {@link BiFunction} encapsulating the supplied {@link Method}; never {@code null}
527   *
528   * @exception NullPointerException if {@code m} is {@code null}
529   *
530   * @exception IllegalAccessException if {@linkplain Lookup#unreflect(Method) unreflecting} fails
531   */
532  public static final BiFunction<Object, Object[], Object> terminalBiFunctionOf(final Lookup lookup, final Method m)
533    throws IllegalAccessException {
534    return terminalBiFunctionOf(privateLookupIn(m.getDeclaringClass(), lookup).unreflect(m));
535  }
536
537  /**
538   * Creates and returns a {@link BiFunction} encapsulating the supplied {@link MethodHandle}.
539   *
540   * @param mh a {@link MethodHandle}; must not be {@code null}
541   *
542   * @return a {@link BiFunction} encapsulating the supplied {@link MethodHandle}; never {@code null}
543   *
544   * @exception NullPointerException if {@code mh} is {@code null}
545   */
546  public static final BiFunction<Object, Object[], Object> terminalBiFunctionOf(MethodHandle mh) {
547    mh = mh.asType(mh.type().changeReturnType(Object.class));
548    final MethodType mt = mh.type();
549    final int pc = mt.parameterCount();
550    final MethodHandle terminalBiFunction;
551    if (pc == 0) {
552      terminalBiFunction = mh;
553      return (t, a) -> {
554        try {
555          return terminalBiFunction.invokeExact();
556        } catch (final RuntimeException | Error e) {
557          throw e;
558        } catch (final InterruptedException e) {
559          Thread.currentThread().interrupt();
560          throw new IllegalStateException(e.getMessage(), e);
561        } catch (final Throwable e) {
562          throw new IllegalStateException(e.getMessage(), e);
563        }
564      };
565    } else if (pc == 1) {
566      if (mt.parameterType(0) == Object[].class) {
567        terminalBiFunction = mh; // no need to spread
568        return (t, a) -> {
569          try {
570            return terminalBiFunction.invokeExact(a);
571          } catch (final RuntimeException | Error e) {
572            throw e;
573          } catch (final InterruptedException e) {
574            Thread.currentThread().interrupt();
575            throw new IllegalStateException(e.getMessage(), e);
576          } catch (final Throwable e) {
577            throw new IllegalStateException(e.getMessage(), e);
578          }
579        };
580      }
581      terminalBiFunction = mh.asType(mt.changeParameterType(0, Object.class));
582      return (t, a) -> {
583        try {
584          return terminalBiFunction.invokeExact(t);
585        } catch (final RuntimeException | Error e) {
586          throw e;
587        } catch (final InterruptedException e) {
588          Thread.currentThread().interrupt();
589          throw new IllegalStateException(e.getMessage(), e);
590        } catch (final Throwable e) {
591          throw new IllegalStateException(e.getMessage(), e);
592        }
593      };
594    }
595    terminalBiFunction = mh.asType((mt.changeParameterType(0, Object.class))).asSpreader(Object[].class, pc - 1);
596    return (t, a) -> {
597      try {
598        return terminalBiFunction.invokeExact(t, a);
599      } catch (final RuntimeException | Error e) {
600        throw e;
601      } catch (final InterruptedException e) {
602        Thread.currentThread().interrupt();
603        throw new IllegalStateException(e.getMessage(), e);
604      } catch (final Throwable e) {
605        throw new IllegalStateException(e.getMessage(), e);
606      }
607    };
608  }
609
610  /**
611   * A convenience method that ensures that every element of the supplied {@code arguments} array can be assigned to a
612   * reference bearing the corresponding {@link Class} drawn from the supplied {@code parameterTypes} array.
613   *
614   * <p>Boxing, unboxing and widening conversions are taken into consideration.</p>
615   *
616   * <p>This method implements the logic implied, but nowhere actually specified, by the contract of {@link
617   * InvocationContext#setParameters(Object[])}.</p>
618   *
619   * @param parameterTypes an array of {@link Class} instances; may be {@code null}; must not contain {@code null}
620   * elements or {@code void.class}; must have a length equal to that of the supplied {@code arguments} array
621   *
622   * @param arguments an array of {@link Object}s; may be {@code null}; must have a length equal to that of the supplied
623   * {@code parameterTypes} array
624   *
625   * @exception IllegalArgumentException if validation fails, i.e. if the length of {@code parameterTypes} is not equal
626   * to the length of {@code arguments}, or if an element of {@code parameterTypes} is {@code null} or {@code
627   * void.class}, or if not every element of the supplied {@code arguments} array can be assigned to a reference bearing
628   * the corresponding {@link Class} drawn from the supplied {@code parameterTypes} array
629   */
630  public static final void validate(final Class<?>[] parameterTypes, final Object[] arguments) {
631    final int parameterTypesLength = parameterTypes == null ? 0 : parameterTypes.length;
632    final int argumentsLength = arguments == null ? 0 : arguments.length;
633    if (argumentsLength != parameterTypesLength) {
634      throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) +
635                                         "; arguments: " + Arrays.toString(arguments));
636    }
637    for (int i = 0; i < argumentsLength; i++) {
638      final Class<?> parameterType = parameterTypes[i];
639      if (parameterType == null || parameterType == void.class) {
640        throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) +
641                                           "; arguments: " + Arrays.toString(arguments) +
642                                           "; parameter type: " + parameterType);
643      }
644      final Object argument = arguments[i];
645      if (argument == null) {
646        if (parameterType.isPrimitive() || parameterType != Void.class) {
647          throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) +
648                                             "; arguments: " + Arrays.toString(arguments) +
649                                             "; parameter type: " + parameterType.getName() +
650                                             "; argument: null");
651        }
652      } else {
653        final Class<?> argumentType = argument.getClass();
654        if (parameterType != argumentType) {
655          if (parameterType == boolean.class) {
656            if (argumentType != Boolean.class) {
657              throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) +
658                                                 "; arguments: " + Arrays.toString(arguments) +
659                                                 "; parameter type: boolean" +
660                                                 "; argument type: " + argumentType.getName());
661            }
662          } else if (parameterType == Boolean.class) {
663            if (argumentType != boolean.class) {
664              throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) +
665                                                 "; arguments: " + Arrays.toString(arguments) +
666                                                 "; parameter type: java.lang.Boolean" +
667                                                 "; argument type: " + argumentType.getName());
668            }
669          } else if (parameterType == byte.class) {
670            if (argumentType != Byte.class) {
671              throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) +
672                                                 "; arguments: " + Arrays.toString(arguments) +
673                                                 "; parameter type: byte" +
674                                                 "; argument type: " + argumentType.getName());
675            }
676          } else if (parameterType == Byte.class) {
677            if (argumentType != byte.class) {
678              throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) +
679                                                 "; arguments: " + Arrays.toString(arguments) +
680                                                 "; parameter type: java.lang.Byte" +
681                                                 "; argument type: " + argumentType.getName());
682            }
683          } else if (parameterType == char.class) {
684            if (argumentType != Character.class) {
685              throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) +
686                                                 "; arguments: " + Arrays.toString(arguments) +
687                                                 "; parameter type: char" +
688                                                 "; argument type: " + argumentType.getName());
689            }
690          } else if (parameterType == Character.class) {
691            if (argumentType != char.class) {
692              throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) +
693                                                 "; arguments: " + Arrays.toString(arguments) +
694                                                 "; parameter type: java.lang.Character" +
695                                                 "; argument type: " + argumentType.getName());
696            }
697          } else if (parameterType == double.class) {
698            if (argumentType != byte.class &&
699                argumentType != char.class &&
700                argumentType != Double.class &&
701                argumentType != float.class &&
702                argumentType != int.class &&
703                argumentType != long.class &&
704                argumentType != short.class) {
705              throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) +
706                                                 "; arguments: " + Arrays.toString(arguments) +
707                                                 "; parameter type: double" +
708                                                 "; argument type: " + argumentType.getName());
709            }
710          } else if (parameterType == Double.class) {
711            if (argumentType != double.class) {
712              throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) +
713                                                 "; arguments: " + Arrays.toString(arguments) +
714                                                 "; parameter type: java.lang.Double" +
715                                                 "; argument type: " + argumentType.getName());
716            }
717          } else if (parameterType == float.class) {
718            if (argumentType != byte.class &&
719                argumentType != char.class &&
720                argumentType != Float.class &&
721                argumentType != int.class &&
722                argumentType != long.class &&
723                argumentType != short.class) {
724              throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) +
725                                                 "; arguments: " + Arrays.toString(arguments) +
726                                                 "; parameter type: float" +
727                                                 "; argument type: " + argumentType.getName());
728            }
729          } else if (parameterType == Float.class) {
730            if (argumentType != float.class) {
731              throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) +
732                                                 "; arguments: " + Arrays.toString(arguments) +
733                                                 "; parameter type: java.lang.Float" +
734                                                 "; argument type: " + argumentType.getName());
735            }
736          } else if (parameterType == int.class) {
737            if (argumentType != byte.class &&
738                argumentType != char.class &&
739                argumentType != Integer.class &&
740                argumentType != short.class) {
741              throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) +
742                                                 "; arguments: " + Arrays.toString(arguments) +
743                                                 "; parameter type: int; argument type: " + argumentType.getName());
744            }
745          } else if (parameterType == Integer.class) {
746            if (argumentType != int.class) {
747              throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) +
748                                                 "; arguments: " + Arrays.toString(arguments) +
749                                                 "; parameter type: java.lang.Integer" +
750                                                 "; argument type: " + argumentType.getName());
751            }
752          } else if (parameterType == long.class) {
753            if (argumentType != byte.class &&
754                argumentType != char.class &&
755                argumentType != int.class &&
756                argumentType != Long.class &&
757                argumentType != short.class) {
758              throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) +
759                                                 "; arguments: " + Arrays.toString(arguments) +
760                                                 "; parameter type: long" +
761                                                 "; argument type: " + argumentType.getName());
762            }
763          } else if (parameterType == Long.class) {
764            if (argumentType != long.class) {
765              throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) +
766                                                 "; arguments: " + Arrays.toString(arguments) +
767                                                 "; parameter type: java.lang.Long" +
768                                                 "; argument type: " + argumentType.getName());
769            }
770          } else if (parameterType == short.class) {
771            if (argumentType != byte.class &&
772                argumentType != Short.class) {
773              throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) +
774                                                 "; arguments: " + Arrays.toString(arguments) +
775                                                 "; parameter type: byte" +
776                                                 "; argument type: " + argumentType.getName());
777            }
778          } else if (parameterType == Short.class) {
779            if (argumentType != short.class) {
780              throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) +
781                                                 "; arguments: " + Arrays.toString(arguments) +
782                                                 "; parameter type: java.lang.Short" +
783                                                 "; argument type: " + argumentType.getName());
784            }
785          } else if (parameterType == Void.class || !parameterType.isAssignableFrom(argumentType)) {
786            throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) +
787                                               "; arguments: " + Arrays.toString(arguments) +
788                                               "; parameter type: " + parameterType.getName() +
789                                               "; argument type: " + argumentType.getName());
790          }
791        }
792      }
793    }
794  }
795
796  private static final Object[] emptyObjectArray() {
797    return EMPTY_OBJECT_ARRAY;
798  }
799
800  private static final <T> T returnNull() {
801    return null;
802  }
803
804  private static final <T, U> T returnNull(final U ignored) {
805    return null;
806  }
807
808  private static final <T> void throwIllegalStateException(final T ignored) {
809    throw new IllegalStateException();
810
811  }
812
813
814  /*
815   * Inner and nested classes.
816   */
817
818
819  private final class State {
820
821
822    /*
823     * Instance fields.
824     */
825
826
827    private final Property<Object> target;
828
829    private final Property<Object[]> arguments;
830
831
832    /*
833     * Constructors.
834     */
835
836
837    private State(final Property<Object> target) {
838      this(target, null);
839    }
840
841    private State(final Property<Object> target,
842                  final Property<Object[]> arguments) {
843      super();
844      this.target = Objects.requireNonNull(target, "target");
845      this.arguments = arguments == null ? EMPTY_OBJECT_ARRAY_PROPERTY : arguments;
846    }
847
848
849    /*
850     * Instance methods.
851     */
852
853
854    private final Object target() {
855      return this.target.get();
856    }
857
858    private final void target(final Object target) {
859      this.target.accept(target);
860    }
861
862    private final Object[] arguments() {
863      final Object[] a = this.arguments.get();
864      return a == null ? EMPTY_OBJECT_ARRAY : a.clone();
865    }
866
867    private final void arguments(final Object[] a) {
868      this.arguments.accept(a == null ? EMPTY_OBJECT_ARRAY : a.clone());
869    }
870
871
872    /*
873     * Inner and nested classes.
874     */
875
876
877    private final class Context implements InvocationContext {
878
879
880      /*
881       * Instance fields.
882       */
883
884
885      private final Supplier<?> proceeder;
886
887
888      /*
889       * Constructors.
890       */
891
892
893      private Context(final Iterator<? extends InterceptorMethod> iterator) {
894        this(iterator, (Supplier<?>)Interceptions::returnNull);
895      }
896
897      private Context(final Iterator<? extends InterceptorMethod> iterator,
898                      final BiFunction<? super Object, ? super Object[], ?> tbf) {
899        this(iterator, () -> tbf.apply(target(), arguments()));
900      }
901
902      private Context(final Iterator<? extends InterceptorMethod> iterator,
903                      final Supplier<?> f) {
904        super();
905        if (iterator == null || !iterator.hasNext()) {
906          this.proceeder = f == null ? Interceptions::returnNull : f;
907        } else {
908          final InterceptorMethod im = Objects.requireNonNull(iterator.next());
909          final Context c = new Context(iterator, f);
910          this.proceeder = () -> {
911            try {
912              return im.intercept(c);
913            } catch (final RuntimeException e) {
914              throw e;
915            } catch (final InterruptedException e) {
916              Thread.currentThread().interrupt();
917              throw new InterceptorException(e.getMessage(), e);
918            } catch (final Exception e) {
919              throw new InterceptorException(e.getMessage(), e);
920            }
921          };
922        }
923      }
924
925
926      /*
927       * Instance methods.
928       */
929
930
931      @Override // InvocationContext
932      public final Constructor<?> getConstructor() {
933        return constructorBootstrap.get();
934      }
935
936      @Override // InvocationContext
937      public final Map<String, Object> getContextData() {
938        return data;
939      }
940
941      @Override // InvocationContext
942      public final Set<Annotation> getInterceptorBindings() {
943        return interceptorBindingsBootstrap.get();
944      }
945
946      @Override // InvocationContext
947      public final Method getMethod() {
948        return methodBootstrap.get();
949      }
950
951      @Override // InvocationContext
952      public final Object[] getParameters() {
953        return arguments();
954      }
955
956      @Override // InvocationContext
957      public final Object getTarget() {
958        return target();
959      }
960
961      @Override // InvocationContext
962      public final Object getTimer() {
963        return timerBootstrap.get();
964      }
965
966      @Override // InvocationContext
967      public final Object proceed() {
968        return this.proceeder.get();
969      }
970
971      @Override // InvocationContext
972      public final void setParameters(final Object[] arguments) {
973        arguments(arguments);
974      }
975
976    }
977
978  }
979
980  private static final class Property<T> implements Consumer<T>, Supplier<T> {
981
982
983    /*
984     * Static fields.
985     */
986
987
988    private static final VarHandle VALUE;
989
990    static {
991      try {
992        VALUE = lookup().findVarHandle(Property.class, "value", Optional.class);
993      } catch (final IllegalAccessException | NoSuchFieldException e) {
994        throw (ExceptionInInitializerError)new ExceptionInInitializerError(e.getMessage()).initCause(e);
995      }
996    }
997
998
999    /*
1000     * Instance fields.
1001     */
1002
1003
1004    // set only through/using VALUE
1005    private volatile Optional<T> value;
1006
1007    private final Supplier<T> reader;
1008
1009    private final Consumer<? super T> writer;
1010
1011
1012    /*
1013     * Constructors.
1014     */
1015
1016
1017    private Property(final Supplier<? extends T> bootstrap, final boolean mutable) {
1018      this(bootstrap, null, mutable);
1019    }
1020
1021    private Property(final Supplier<? extends T> bootstrap, final Consumer<? super T> validator, final boolean mutable) {
1022      super();
1023      if (bootstrap == null) {
1024        if (mutable) {
1025          VALUE.set(this, Optional.empty()); // no need for volatile semantics here
1026          this.reader = () -> ((Optional<T>)VALUE.getVolatile(this)).orElse(null);
1027          if (validator == null) {
1028            this.writer = v -> {
1029              VALUE.setVolatile(this, Optional.ofNullable(v));
1030            };
1031          } else {
1032            validator.accept(null); // make sure the Optional.empty() assignment above was OK
1033            this.writer = v -> {
1034              validator.accept(v);
1035              VALUE.setVolatile(this, Optional.ofNullable(v));
1036            };
1037          }
1038        } else {
1039          this.reader = Interceptions::returnNull;
1040          this.writer = Interceptions::throwIllegalStateException;
1041        }
1042      } else if (mutable) {
1043        if (validator == null) {
1044          this.reader = () -> {
1045            Optional<T> o = (Optional<T>)VALUE.getVolatile(this);
1046            if (o == null) {
1047              o = Optional.ofNullable(bootstrap.get());
1048              if (!VALUE.compareAndSet(this, null, o)) {
1049                o = (Optional<T>)VALUE.getVolatile(this);
1050              }
1051            }
1052            return o.orElse(null);
1053          };
1054          this.writer = v -> {
1055            VALUE.setVolatile(this, Optional.ofNullable(v));
1056          };
1057        } else {
1058          this.reader = () -> {
1059            Optional<T> o = (Optional<T>)VALUE.getVolatile(this);
1060            if (o == null) {
1061              final T v = bootstrap.get();
1062              validator.accept(v);
1063              o = Optional.ofNullable(v);
1064              if (!VALUE.compareAndSet(this, null, o)) {
1065                o = (Optional<T>)VALUE.getVolatile(this);
1066              }
1067            }
1068            return o.orElse(null);
1069          };
1070          this.writer = v -> {
1071            validator.accept(v);
1072            VALUE.setVolatile(this, Optional.ofNullable(v));
1073          };
1074        }
1075      } else { // !mutable
1076        this.writer = Interceptions::throwIllegalStateException;
1077        if (validator == null) {
1078          this.reader = () -> {
1079            Optional<T> o = (Optional<T>)VALUE.getVolatile(this);
1080            if (o == null) {
1081              o = Optional.ofNullable(bootstrap.get());
1082              if (!VALUE.compareAndSet(this, null, o)) {
1083                o = (Optional<T>)VALUE.getVolatile(this);
1084              }
1085            }
1086            return o.orElse(null);
1087          };
1088        } else {
1089          this.reader = () -> {
1090            Optional<T> o = (Optional<T>)VALUE.getVolatile(this);
1091            if (o == null) {
1092              final T v = bootstrap.get();
1093              validator.accept(v);
1094              o = Optional.ofNullable(v);
1095              if (!VALUE.compareAndSet(this, null, o)) {
1096                o = (Optional<T>)VALUE.getVolatile(this);
1097              }
1098            }
1099            return o.orElse(null);
1100          };
1101        }
1102      }
1103    }
1104
1105
1106    /*
1107     * Instance methods.
1108     */
1109
1110
1111    @Override // Supplier<T>
1112    public final T get() {
1113      return this.reader.get();
1114    }
1115
1116    @Override // Consumer<T>
1117    public final void accept(final T value) {
1118      this.writer.accept(value);
1119    }
1120
1121    @Override
1122    public final String toString() {
1123      return String.valueOf(this.get());
1124    }
1125
1126  }
1127
1128}