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