001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
002 *
003 * Copyright © 2022–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.invoke.MethodHandle;
017import java.lang.invoke.MethodHandles.Lookup;
018import java.lang.invoke.MethodType;
019import java.lang.invoke.VarHandle;
020
021import java.lang.reflect.Constructor;
022import java.lang.reflect.Method;
023
024import java.util.Arrays;
025import java.util.List;
026import java.util.Map;
027import java.util.Objects;
028
029import java.util.concurrent.Callable;
030import java.util.concurrent.ConcurrentHashMap;
031import java.util.concurrent.ConcurrentMap;
032
033import java.util.function.Consumer;
034import java.util.function.Function;
035import java.util.function.Supplier;
036
037import jakarta.interceptor.InvocationContext;
038
039import static java.lang.invoke.MethodHandles.lookup;
040import static java.lang.invoke.MethodHandles.privateLookupIn;
041
042import static org.microbean.interceptor.LowLevelOperation.invokeUnchecked;
043
044/**
045 * A {@link Callable} {@link InvocationContext} implementation representing the interception of a constructor, method,
046 * or lifecycle event.
047 *
048 * @author <a href="https://about.me/lairdnelson/" target="_top">Laird Nelson</a>
049 *
050 * @see #proceed()
051 *
052 * @deprecated See {@link Interceptions}.
053 */
054@Deprecated(forRemoval = true)
055public class Chain implements Callable<Object>, InvocationContext {
056
057
058  /*
059   * Static fields.
060   */
061
062
063  private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];
064
065  private static final Lookup lookup = lookup();
066
067  private static final VarHandle ARGUMENTS;
068
069  private static final VarHandle TARGET;
070
071  static {
072    try {
073      ARGUMENTS = lookup.findVarHandle(Chain.class, "arguments", Object[].class);
074      TARGET = lookup.findVarHandle(Chain.class, "target", Object.class);
075    } catch (final IllegalAccessException | NoSuchFieldException e) {
076      throw (ExceptionInInitializerError)new ExceptionInInitializerError(e.getMessage()).initCause(e);
077    }
078  }
079
080
081  /*
082   * Instance fields.
083   */
084
085
086  private final ConcurrentMap<String, Object> contextData;
087
088  private final Supplier<? extends Constructor<?>> constructorSupplier;
089
090  private final Supplier<? extends Method> methodSupplier;
091
092  private final Supplier<?> timerSupplier;
093
094  private final Supplier<?> targetSupplier;
095
096  private final Supplier<? extends Object[]> argumentsSupplier;
097
098  private final Consumer<? super Object[]> setParametersImplementation;
099
100  private final Callable<?> proceedImplementation;
101
102  private final Chain chain;
103
104  private volatile Object[] arguments;
105
106  private volatile Object target;
107
108
109  /*
110   * Constructors.
111   */
112
113  /*
114   * Lifecycle method/callback.
115   */
116
117  /**
118   * Creates a new {@link Chain} primarily for testing purposes.
119   *
120   * <p>The resulting {@link Chain} will return {@code null} from the following methods:</p>
121   *
122   * <ul>
123   *
124   * <li>{@link #call()}</li>
125   *
126   * <li>{@link #getConstructor()}</li>
127   *
128   * <li>{@link #getMethod()}</li>
129   *
130   * <li>{@link #getTimer()}</li>
131   *
132   * <li>{@link #getTarget()}</li>
133   *
134   * <li>{@link #proceed()}</li>
135   *
136   * </ul>
137   */
138  public Chain() {
139    super();
140    this.contextData = new ConcurrentHashMap<>();
141    this.constructorSupplier = Chain::returnNull;
142    this.methodSupplier = Chain::returnNull;
143    this.timerSupplier = Chain::returnNull;
144    this.targetSupplier = Chain::returnNull;
145    this.chain = this;
146    this.proceedImplementation = Chain::returnNull;
147    this.argumentsSupplier = Chain::emptyObjectArray;
148    this.setParametersImplementation = Chain::throwIllegalStateException;
149    this.arguments = EMPTY_OBJECT_ARRAY;
150  }
151
152  /**
153   * Creates a new {@link Chain} for lifecycle method interceptions.
154   *
155   * <p>The resulting {@link Chain} will return {@code null} from the following methods:</p>
156   *
157   * <ul>
158   *
159   * <li>{@link #getConstructor()}</li>
160   *
161   * <li>{@link #getMethod()}</li>
162   *
163   * <li>{@link #getTimer()}</li>
164   *
165   * </ul>
166   *
167   * @param interceptorMethods a {@link List} of {@link InterceptorMethod}s; may, rather uselessly, be {@code null}
168   *
169   * @param targetSupplier a {@link Supplier} of the target instance; may, rather uselessly, be {@code null}
170   */
171  // For lifecycle events like PostConstruct, PreDestroy; no terminal function in these cases
172  public Chain(final List<? extends InterceptorMethod> interceptorMethods,
173               final Supplier<?> targetSupplier) {
174    this(interceptorMethods,
175         null, // terminal function; null on purpose
176         false, // set target
177         new ConcurrentHashMap<>(),
178         Chain::returnNull, // constructor supplier
179         Chain::returnNull, // method supplier
180         targetSupplier,
181         Chain::emptyObjectArray, // arguments supplier
182         Chain::sink, // arguments validator
183         Chain::returnNull, // timer supplier
184         null); // chain
185  }
186
187  /*
188   * Around-construct.
189   */
190
191  /**
192   * Creates a new {@link Chain} for around-construct interceptions.
193   *
194   * <p>The resulting {@link Chain} will return {@code null} from the following methods:</p>
195   *
196   * <ul>
197   *
198   * <li>{@link #getMethod()}</li>
199   *
200   * <li>{@link #getTarget()}</li>
201   *
202   * <li>{@link #getTimer()}</li>
203   *
204   * </ul>
205   *
206   * @param interceptorMethods a {@link List} of {@link InterceptorMethod}s; may, rather uselessly, be {@code null}
207   *
208   * @param terminalConstructor the {@link Constructor} being intercepted; must not be {@code null}
209   *
210   * @exception NullPointerException if {@code terminalConstructor} is {@code null}
211   *
212   * @exception IllegalAccessException if {@linkplain Lookup#unreflectConstructor(Constructor) unreflecting} fails
213   */
214  public Chain(final List<? extends InterceptorMethod> interceptorMethods,
215               final Constructor<?> terminalConstructor)
216    throws IllegalAccessException {
217    this(interceptorMethods,
218         terminalFunctionOf(terminalConstructor),
219         true, // set target
220         new ConcurrentHashMap<>(),
221         () -> terminalConstructor,
222         Chain::returnNull, // method supplier
223         Chain::returnNull, // targetSupplier (initial target supplier)
224         Chain::emptyObjectArray, // arguments supplier
225         args -> validate(terminalConstructor.getParameterTypes(), args),
226         Chain::returnNull, // timer supplier
227         null); // chain
228  }
229
230  /**
231   * Creates a new {@link Chain} for around-construct interceptions.
232   *
233   * <p>The resulting {@link Chain} will return {@code null} from the following methods:</p>
234   *
235   * <ul>
236   *
237   * <li>{@link #getMethod()}</li>
238   *
239   * <li>{@link #getTimer()}</li>
240   *
241   * </ul>
242   *
243   * @param interceptorMethods a {@link List} of {@link InterceptorMethod}s; may, rather uselessly, be {@code null}
244   *
245   * @param terminalConstructor the {@link Constructor} being intercepted; must not be {@code null}
246   *
247   * @param argumentsSupplier a {@link Supplier} supplying the arguments for the {@link Constructor}; may be {@code
248   * null}
249   *
250   * @exception NullPointerException if {@code terminalConstructor} is {@code null}
251   *
252   * @exception IllegalAccessException if {@linkplain Lookup#unreflectConstructor(Constructor) unreflecting} fails
253   */
254  public Chain(final List<? extends InterceptorMethod> interceptorMethods,
255               final Constructor<?> terminalConstructor,
256               final Supplier<? extends Object[]> argumentsSupplier)
257    throws IllegalAccessException {
258    this(interceptorMethods,
259         terminalFunctionOf(terminalConstructor),
260         true, // set target
261         new ConcurrentHashMap<>(),
262         () -> terminalConstructor,
263         Chain::returnNull, // method supplier
264         Chain::returnNull, // targetSupplier (initial target supplier)
265         argumentsSupplier,
266         args -> validate(terminalConstructor.getParameterTypes(), args),
267         Chain::returnNull, // timer supplier
268         null); // chain
269  }
270
271  /**
272   * Creates a new {@link Chain} for around-construct interceptions.
273   *
274   * <p>The resulting {@link Chain} will return {@code null} from the following methods:</p>
275   *
276   * <ul>
277   *
278   * <li>{@link #getConstructor()}</li>
279   *
280   * <li>{@link #getMethod()}</li>
281   *
282   * <li>{@link #getTimer()}</li>
283   *
284   * </ul>
285   *
286   * @param interceptorMethods a {@link List} of {@link InterceptorMethod}s; may, rather uselessly, be {@code null}
287   *
288   * @param terminalFunction the terminal {@link Function} to intercept; must not be {@code null}
289   *
290   * @param argumentsSupplier a {@link Supplier} supplying the arguments for the terminal {@link Function}; may be
291   * {@code null}
292   *
293   * @exception NullPointerException if {@code terminalFunction} is {@code null}
294   */
295  public Chain(final List<? extends InterceptorMethod> interceptorMethods,
296               final Function<? super Object[], ?> terminalFunction,
297               final Supplier<? extends Object[]> argumentsSupplier) {
298    this(interceptorMethods,
299         Objects.requireNonNull(terminalFunction, "terminalFunction"),
300         true, // set target
301         new ConcurrentHashMap<>(),
302         Chain::returnNull, // constructor supplier
303         Chain::returnNull, // method supplier
304         Chain::returnNull, // target supplier (won't ever be consulted/invoked; target is set by terminalFunction directly)
305         argumentsSupplier,
306         Chain::sink, // arguments validator
307         Chain::returnNull, // timer supplier
308         null); // chain
309  }
310
311  /*
312   * Around-invoke.
313   */
314
315  /**
316   * Creates a new {@link Chain} for around-invoke interceptions.
317   *
318   * <p>The resulting {@link Chain} will return {@code null} from the following methods:</p>
319   *
320   * <ul>
321   *
322   * <li>{@link #getConstructor()}</li>
323   *
324   * <li>{@link #getTimer()}</li>
325   *
326   * </ul>
327   *
328   * @param interceptorMethods a {@link List} of {@link InterceptorMethod}s; may, rather uselessly, be {@code null}
329   *
330   * @param targetSupplier a {@link Supplier} of the target instance; may, rather uselessly, be {@code null}
331   *
332   * @param terminalMethod the {@link Method} to intercept; must not be {@code null}
333   *
334   * @exception NullPointerException if {@code terminalMethod} is {@code null}
335   *
336   * @exception IllegalAccessException if {@linkplain Lookup#unreflect(Method) unreflecting} fails
337   */
338  public Chain(final List<? extends InterceptorMethod> interceptorMethods,
339               final Supplier<?> targetSupplier,
340               final Method terminalMethod)
341    throws IllegalAccessException {
342    this(interceptorMethods,
343         terminalFunctionOf(terminalMethod, targetSupplier),
344         false, // don't set target
345         new ConcurrentHashMap<>(),
346         Chain::returnNull, // constructor supplier
347         () -> terminalMethod,
348         targetSupplier,
349         Chain::emptyObjectArray, // arguments supplier
350         args -> validate(terminalMethod.getParameterTypes(), args),
351         Chain::returnNull, // timer supplier
352         null); // chain
353  }
354
355  /**
356   * Creates a new {@link Chain} for around-invoke interceptions.
357   *
358   * <p>The resulting {@link Chain} will return {@code null} from the following methods:</p>
359   *
360   * <ul>
361   *
362   * <li>{@link #getConstructor()}</li>
363   *
364   * <li>{@link #getTimer()}</li>
365   *
366   * </ul>
367   *
368   * @param interceptorMethods a {@link List} of {@link InterceptorMethod}s; may, rather uselessly, be {@code null}
369   *
370   * @param targetSupplier a {@link Supplier} of the target instance; may, rather uselessly, be {@code null}
371   *
372   * @param terminalMethod the {@link Method} to intercept; must not be {@code null}
373   *
374   * @param argumentsSupplier a {@link Supplier} supplying the arguments for the {@link Method}; may be {@code null}
375   *
376   * @exception NullPointerException if {@code terminalMethod} is {@code null}
377   *
378   * @exception IllegalAccessException if {@linkplain Lookup#unreflect(Method) unreflecting} fails
379   */
380  public Chain(final List<? extends InterceptorMethod> interceptorMethods,
381               final Supplier<?> targetSupplier,
382               final Method terminalMethod,
383               final Supplier<? extends Object[]> argumentsSupplier)
384    throws IllegalAccessException {
385    this(interceptorMethods,
386         terminalFunctionOf(terminalMethod, targetSupplier),
387         false, // don't set target
388         new ConcurrentHashMap<>(),
389         Chain::returnNull, // constructor supplier
390         () -> terminalMethod,
391         targetSupplier,
392         argumentsSupplier,
393         args -> validate(terminalMethod.getParameterTypes(), args),
394         Chain::returnNull, // timer supplier
395         null); // chain
396  }
397
398  /*
399   * Around-construct or around-invoke (but not lifecycle method).
400   */
401
402  /**
403   * Creates a new {@link Chain} for around-construct or around-invoke interceptions.
404   *
405   * <p>The resulting {@link Chain} will return {@code null} from the following methods:</p>
406   *
407   * <ul>
408   *
409   * <li>{@link #getConstructor()}</li>
410   *
411   * <li>{@link #getMethod()}</li>
412   *
413   * <li>{@link #getTimer()}</li>
414   *
415   * </ul>
416   *
417   * @param interceptorMethods a {@link List} of {@link InterceptorMethod}s; may, rather uselessly, be {@code null}
418   *
419   * @param targetSupplier a {@link Supplier} of the target instance; may, rather uselessly, be {@code null}
420   *
421   * @param terminalFunction the terminal {@link Function} to intercept; must not be {@code null}
422   *
423   * @param setTarget whether the supplied {@code terminalFunction} is effectively a constructor
424   *
425   * @param argumentsSupplier a {@link Supplier} supplying the arguments for the terminal {@link Function}; may be
426   * {@code null}
427   *
428   * @exception NullPointerException if {@code terminalFunction} is {@code null}
429   */
430  public Chain(final List<? extends InterceptorMethod> interceptorMethods,
431               final Supplier<?> targetSupplier,
432               final Function<? super Object[], ?> terminalFunction,
433               final boolean setTarget, // is the terminal function effectively a constructor?
434               final Supplier<? extends Object[]> argumentsSupplier) {
435    this(interceptorMethods,
436         Objects.requireNonNull(terminalFunction, "terminalFunction"),
437         setTarget,
438         new ConcurrentHashMap<>(),
439         Chain::returnNull, // constructor supplier
440         Chain::returnNull, // method supplier
441         targetSupplier,
442         argumentsSupplier,
443         Chain::sink, // arguments validator
444         Chain::returnNull, // timer supplier
445         null); // chain
446  }
447
448  /*
449   * Kitchen sink constructor.
450   */
451
452  // Everything is nullable.
453  private Chain(List<? extends InterceptorMethod> interceptorMethods,
454                final Function<? super Object[], ?> terminalFunction,
455                final boolean setTarget,
456                final ConcurrentMap<String, Object> contextData,
457                final Supplier<? extends Constructor<?>> constructorSupplier,
458                final Supplier<? extends Method> methodSupplier,
459                final Supplier<?> targetSupplier,
460                final Supplier<? extends Object[]> argumentsSupplier,
461                final Consumer<? super Object[]> argumentsValidator,
462                final Supplier<?> timerSupplier,
463                final Chain parent) {
464    super();
465    this.contextData = contextData == null ? new ConcurrentHashMap<>() : contextData;
466    this.constructorSupplier = constructorSupplier == null ? Chain::returnNull : constructorSupplier;
467    this.methodSupplier = methodSupplier == null ? Chain::returnNull : methodSupplier;
468    this.argumentsSupplier = argumentsSupplier == null ? Chain::emptyObjectArray : argumentsSupplier;
469    this.timerSupplier = timerSupplier == null ? Chain::returnNull : timerSupplier;
470    this.targetSupplier = targetSupplier == null ? Chain::returnNull : targetSupplier;
471    this.chain = parent == null ? this : parent;
472    if (interceptorMethods == null || interceptorMethods.isEmpty()) {
473      Objects.requireNonNull(terminalFunction, "terminalFunction");
474      this.setParametersImplementation =
475        argumentsValidator == null ?
476        args -> this.setArguments(Chain::sink, args) :
477        args -> this.setArguments(argumentsValidator, args);
478      if (setTarget) {
479        this.proceedImplementation = () -> {
480          final Object t = terminalFunction.apply(this.getParameters());
481          TARGET.setVolatile(this.chain, t); // volatile write
482          return t;
483        };
484      } else {
485        this.proceedImplementation = () -> terminalFunction.apply(this.getParameters());
486      }
487    } else {
488      if (terminalFunction == null) {
489        this.setParametersImplementation = Chain::throwIllegalStateException;
490      } else if (argumentsValidator == null) {
491        this.setParametersImplementation = args -> this.setArguments(Chain::sink, args);
492      } else {
493        this.setParametersImplementation = args -> this.setArguments(argumentsValidator, args);
494      }
495      interceptorMethods = List.copyOf(interceptorMethods);
496      final int size = interceptorMethods.size();
497      final InterceptorMethod im = interceptorMethods.get(0);
498      // TODO: OK to create Chain here, or do we need to create a new one inside the proceedImplementation? Consider
499      // setParameters(), setTarget(Object). See also: https://www.eclipse.org/lists/interceptors-dev/msg00056.html
500      final Chain chain = new Chain(size == 1 ? List.of() : interceptorMethods.subList(1, size),
501                                    terminalFunction,
502                                    setTarget,
503                                    this.contextData,
504                                    this.constructorSupplier,
505                                    this.methodSupplier,
506                                    this.targetSupplier,
507                                    this.argumentsSupplier,
508                                    argumentsValidator,
509                                    this.timerSupplier,
510                                    this.chain);
511      
512      this.proceedImplementation = () -> im.intercept(chain);
513    }
514  }
515
516
517  /*
518   * Instance methods.
519   */
520
521
522  /**
523   * Returns the {@link Constructor} being intercepted, if available, or {@code null}.
524   *
525   * @return the {@link Constructor} being intercepted, if available, or {@code null}
526   */
527  @Override
528  public final Constructor<?> getConstructor() {
529    return this.constructorSupplier.get();
530  }
531
532  /**
533   * Returns the context data {@link Map} shared by the current invocation.
534   *
535   * @return the context data {@link Map} shared by the current invocation; never {@code null}
536   *
537   * @see InvocationContext#getContextData()
538   */
539  @Override
540  public final Map<String, Object> getContextData() {
541    return this.contextData;
542  }
543
544  /**
545   * Returns the {@link Method} being intercepted, if available, or {@code null}.
546   *
547   * @return the {@link Method} being intercepted, if available, or {@code null}
548   */
549  // OK to return null in many cases; see
550  // https://jakarta.ee/specifications/interceptors/2.1/jakarta-interceptors-spec-2.1#invocation_context
551  @Override
552  public final Method getMethod() {
553    return this.methodSupplier.get();
554  }
555
556  /**
557   * Returns any arguments in effect for the current interception.
558   *
559   * <p>For historical reasons, the Jakarta Interceptors specification refers, incorrectly, to arguments as
560   * "parameters".</p>
561   *
562   * @return any arguments in effect for the current interception; never {@code null}
563   *
564   * @exception IllegalStateException if invoked within a lifecycle callback method that is not an {@link
565   * jakarta.interceptor.AroundConstruct AroundConstruct} callback
566   *
567   * @see #setParameters(Object[])
568   */
569  @Override
570  public final Object[] getParameters() {
571    // Cloning etc. is not necessary; this whole API is stupid
572    final Object[] arguments = this.chain.arguments; // volatile read
573    if (arguments == null) {
574      try {
575        this.setParameters(this.argumentsSupplier.get());
576      } catch (final IllegalArgumentException e) {
577        throw new IllegalStateException(e.getMessage(), e);
578      }
579      return this.chain.arguments; // volatile read
580    }
581    return arguments;
582  }
583
584  /**
585   * Returns the target instance, if available, or {@code null}.
586   *
587   * @return the target instance, if available, or {@code null}
588   */
589  @Override
590  public final Object getTarget() {
591    Object target = this.chain.target; // volatile read
592    if (target == null) {
593      target = this.targetSupplier.get();
594      if (target != null && TARGET.compareAndSet(this.chain, null, target)) { // volatile write
595        target = this.chain.target; // volatile read
596      }
597    }
598    return target;
599  }
600
601  /**
602   * Sets the target instance to be the supplied {@code target}.
603   *
604   * @param target the target instance; must not be {@code null}
605   *
606   * @exception NullPointerException if {@code target} is {@code null}
607   *
608   * @see #getTarget()
609   *
610   * @deprecated This really shouldn't be needed as part of the public API.
611   */
612  @Deprecated // should not be needed?
613  public final void setTarget(final Object target) {
614    TARGET.setVolatile(this.chain, Objects.requireNonNull(target, "target")); // volatile write
615  }
616
617  /**
618   * Returns the timer, if available, or {@code null}.
619   *
620   * @return the timer, if available, or {@code null}
621   */
622  @Override
623  public final Object getTimer() {
624    return this.timerSupplier.get();
625  }
626
627  /**
628   * Invokes the {@link #proceed()} method and returns its result.
629   *
630   * @return the result of invoking the {@link #proceed()} method, which may be {@code null}
631   *
632   * @exception Exception if an error occurs
633   *
634   * @see #proceed()
635   */
636  @Override // Callable<Object>
637  public final Object call() throws Exception {
638    return this.proceed();
639  }
640
641  /**
642   * Applies the next interception in this {@link Chain}, or calls the terminal function, and returns the result of the
643   * interception, which may be {@code null}.
644   *
645   * <p>Overrides must not call {@link #call()} or an infinite loop will result.</p>
646   *
647   * @return the result of proceeding, which may be {@code null}
648   *
649   * @exception Exception if an error occurs
650   */
651  @Override
652  public Object proceed() throws Exception {
653    return this.proceedImplementation.call();
654  }
655
656  /**
657   * Sets arguments to be in effect for the current interception.
658   *
659   * <p>For historical reasons, the Jakarta Interceptors specification refers, incorrectly, to arguments as
660   * "parameters".</p>
661   *
662   * @param arguments the arguments; may be {@code null}
663   *
664   * @exception IllegalArgumentException if the arguments are invalid
665   *
666   * @exception IllegalStateException if invoked within a lifecycle callback method that is not an {@link
667   * jakarta.interceptor.AroundConstruct AroundConstruct} callback
668   *
669   * @see #getParameters()
670   */
671  @Override
672  public final void setParameters(final Object[] arguments) {
673    this.setParametersImplementation.accept(arguments);
674  }
675
676  private final void setArguments(final Consumer<? super Object[]> argumentsValidator, final Object[] arguments) {
677    if (arguments == null) {
678      argumentsValidator.accept(EMPTY_OBJECT_ARRAY);
679      this.chain.arguments = EMPTY_OBJECT_ARRAY; // volatile write
680    } else {
681      // Cloning etc. is not necessary; this whole API is stupid
682      argumentsValidator.accept(arguments);
683      this.chain.arguments = arguments; // volatile write
684    }
685  }
686
687
688  /*
689   * Static methods.
690   */
691
692
693  /**
694   * Creates and returns a {@link Function} encapsulating the supplied {@link Constructor}.
695   *
696   * @param c a {@link Constructor}; must not be {@code null}
697   *
698   * @return a {@link Function} encapsulating the supplied {@link Constructor}; never {@code null}
699   *
700   * @exception NullPointerException if {@code c} is {@code null}
701   *
702   * @exception IllegalAccessException if {@linkplain Lookup#unreflectConstructor(Constructor) unreflecting} fails
703   */
704  public static final Function<Object[], Object> terminalFunctionOf(final Constructor<?> c) throws IllegalAccessException {
705    return terminalFunctionOf(privateLookupIn(c.getDeclaringClass(), Chain.lookup).unreflectConstructor(c), null);
706  }
707
708  /**
709   * Creates and returns a {@link Function} encapsulating the supplied {@code static} {@link Method}.
710   *
711   * @param staticMethod a {@code static} {@link Method}; must not be {@code null}
712   *
713   * @return a {@link Function} encapsulating the supplied {@code static} {@link Method}; never {@code null}
714   *
715   * @exception NullPointerException if {@code staticMethod} is {@code null}
716   *
717   * @exception IllegalAccessException if {@linkplain Lookup#unreflect(Method) unreflecting} fails
718   */
719  public static final Function<Object[], Object> terminalFunctionOf(final Method staticMethod) throws IllegalAccessException {
720    return terminalFunctionOf(staticMethod, null);
721  }
722
723  /**
724   * Creates and returns a {@link Function} encapsulating the supplied {@link Method}.
725   *
726   * @param m a {@link Method}; must not be {@code null}. If {@code m} is a virtual method, then the supplied {@code
727   * receiverSupplier} will be used to supply its receiver
728   *
729   * @param receiverSupplier a {@link Supplier} of the receiver for the supplied {@link Method}; may be {@code null} in
730   * which case the supplied {@link Method} must be {@code static}
731   *
732   * @return a {@link Function} encapsulating the supplied {@link Method}; never {@code null}
733   *
734   * @exception NullPointerException if {@code m} is {@code null}
735   *
736   * @exception IllegalAccessException if {@linkplain Lookup#unreflect(Method) unreflecting} fails
737   */
738  public static final Function<Object[], Object> terminalFunctionOf(final Method m, final Supplier<?> receiverSupplier)
739    throws IllegalAccessException {
740    return terminalFunctionOf(privateLookupIn(m.getDeclaringClass(), Chain.lookup).unreflect(m), receiverSupplier);
741  }
742
743  /**
744   * Creates and returns a {@link Function} encapsulating the supplied {@link MethodHandle}.
745   *
746   * @param receiverlessMethodHandle a {@link MethodHandle}; must not be {@code null}; must be receiverless or
747   * {@linkplain MethodHandle#bindTo(Object) bound} to a receiver already.
748   *
749   * @return a {@link Function} encapsulating the supplied {@link MethodHandle}; never {@code null}
750   *
751   * @exception NullPointerException if {@code receiverlessMethodHandle} is {@code null}
752   */
753  public static final Function<Object[], Object> terminalFunctionOf(final MethodHandle receiverlessMethodHandle) {
754    return terminalFunctionOf(receiverlessMethodHandle, null);
755  }
756
757  /**
758   * Creates and returns a {@link Function} encapsulating the supplied {@link MethodHandle}.
759   *
760   * @param mh a {@link MethodHandle}; must not be {@code null}. If {@code mh} is a {@link MethodHandle} requiring a
761   * receiver, then the supplied {@code receiverSupplier}, if non-{@code null}, will be used to supply its receiver
762   *
763   * @param receiverSupplier a {@link Supplier} of the receiver for the supplied {@link MethodHandle}; may be {@code
764   * null} in which case the supplied {@link MethodHandle} must be receiverless
765   *
766   * @return a {@link Function} encapsulating the supplied {@link MethodHandle}; never {@code null}
767   *
768   * @exception NullPointerException if {@code mh} is {@code null}
769   */
770  public static final Function<Object[], Object> terminalFunctionOf(MethodHandle mh, final Supplier<?> receiverSupplier) {
771    mh = mh.asType(mh.type().changeReturnType(Object.class));
772    MethodType mt = mh.type();
773    final int pc = mt.parameterCount();
774
775    final MethodHandle terminalFunction;
776
777    if (receiverSupplier == null) {
778      // Static
779      switch (pc) {
780      case 0:
781        terminalFunction = mh;
782        return ps -> invokeUnchecked(() -> terminalFunction.invokeExact());
783      default:
784        terminalFunction = mh.asSpreader(Object[].class, pc);
785        return ps -> invokeUnchecked(() -> terminalFunction.invokeExact(ps));
786      }
787    }
788
789    // Virtual
790    mh = mh.asType(mt.changeParameterType(0, Object.class));
791    mt = mh.type();
792
793    switch (pc) {
794    case 1:
795      terminalFunction = mh;
796      return ps -> invokeUnchecked(() -> terminalFunction.invokeExact(receiverSupplier.get()));
797    default:
798      terminalFunction = mh.asSpreader(Object[].class, pc - 1);
799      return ps -> invokeUnchecked(() -> terminalFunction.invokeExact(receiverSupplier.get(), ps));
800    }
801  }
802
803  private static final <T> T returnNull() {
804    return null;
805  }
806
807  private static final <X, Y> X returnNull(final Y ignored) {
808    return null;
809  }
810
811  private static final Object[] emptyObjectArray() {
812    return EMPTY_OBJECT_ARRAY;
813  }
814
815  private static final <T> void sink(T ignored) {
816
817  }
818
819  private static final <X> void throwIllegalStateException(final X ignored) {
820    throw new IllegalStateException();
821  }
822
823  /**
824   * A convenience method that ensures that every element of the supplied {@code arguments} array can be assigned to a
825   * reference bearing the corresponding {@link Class} drawn from the supplied {@code parameterTypes} array.
826   *
827   * <p>Boxing, unboxing and widening conversions are taken into consideration.</p>
828   *
829   * <p>This method implements the logic implied, but nowhere actually specified, by the contract of {@link
830   * InvocationContext#setParameters(Object[])}.</p>
831   *
832   * @param parameterTypes an array of {@link Class} instances; may be {@code null}; must not contain {@code null}
833   * elements or {@code void.class}; must have a length equal to that of the supplied {@code arguments} array
834   *
835   * @param arguments an array of {@link Object}s; may be {@code null}; must have a length equal to that of the supplied
836   * {@code parameterTypes} array
837   *
838   * @exception IllegalArgumentException if not every element of the supplied {@code arguments} array can be assigned to
839   * a reference bearing the corresponding {@link Class} drawn from the supplied {@code parameterTypes} array
840   *
841   * @see #setParameters(Object[])
842   */
843  public static final void validate(final Class<?>[] parameterTypes, final Object[] arguments) {
844    final int parameterTypesLength = parameterTypes == null ? 0 : parameterTypes.length;
845    final int argumentsLength = arguments == null ? 0 : arguments.length;
846    if (argumentsLength != parameterTypesLength) {
847      throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) +
848                                         "; arguments: " + Arrays.toString(arguments));
849    }
850    for (int i = 0; i < argumentsLength; i++) {
851      final Class<?> parameterType = parameterTypes[i];
852      if (parameterType == null || parameterType == void.class) {
853        throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) +
854                                           "; arguments: " + Arrays.toString(arguments) +
855                                           "; parameter type: " + parameterType);
856      }
857      final Object argument = arguments[i];
858      if (argument == null) {
859        if (parameterType.isPrimitive() || parameterType != Void.class) {
860          throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) +
861                                             "; arguments: " + Arrays.toString(arguments) +
862                                             "; parameter type: " + parameterType.getName() +
863                                             "; argument: null");
864        }
865      } else {
866        final Class<?> argumentType = argument.getClass();
867        if (parameterType != argumentType) {
868          if (parameterType == boolean.class) {
869            if (argumentType != Boolean.class) {
870              throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) +
871                                                 "; arguments: " + Arrays.toString(arguments) +
872                                                 "; parameter type: boolean" +
873                                                 "; argument type: " + argumentType.getName());
874            }
875          } else if (parameterType == Boolean.class) {
876            if (argumentType != boolean.class) {
877              throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) +
878                                                 "; arguments: " + Arrays.toString(arguments) +
879                                                 "; parameter type: java.lang.Boolean" +
880                                                 "; argument type: " + argumentType.getName());
881            }
882          } else if (parameterType == byte.class) {
883            if (argumentType != Byte.class) {
884              throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) +
885                                                 "; arguments: " + Arrays.toString(arguments) +
886                                                 "; parameter type: byte" +
887                                                 "; argument type: " + argumentType.getName());
888            }
889          } else if (parameterType == Byte.class) {
890            if (argumentType != byte.class) {
891              throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) +
892                                                 "; arguments: " + Arrays.toString(arguments) +
893                                                 "; parameter type: java.lang.Byte" +
894                                                 "; argument type: " + argumentType.getName());
895            }
896          } else if (parameterType == char.class) {
897            if (argumentType != Character.class) {
898              throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) +
899                                                 "; arguments: " + Arrays.toString(arguments) +
900                                                 "; parameter type: char" +
901                                                 "; argument type: " + argumentType.getName());
902            }
903          } else if (parameterType == Character.class) {
904            if (argumentType != char.class) {
905              throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) +
906                                                 "; arguments: " + Arrays.toString(arguments) +
907                                                 "; parameter type: java.lang.Character" +
908                                                 "; argument type: " + argumentType.getName());
909            }
910          } else if (parameterType == double.class) {
911            if (argumentType != byte.class &&
912                argumentType != char.class &&
913                argumentType != Double.class &&
914                argumentType != float.class &&
915                argumentType != int.class &&
916                argumentType != long.class &&
917                argumentType != short.class) {
918              throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) +
919                                                 "; arguments: " + Arrays.toString(arguments) +
920                                                 "; parameter type: double" +
921                                                 "; argument type: " + argumentType.getName());
922            }
923          } else if (parameterType == Double.class) {
924            if (argumentType != double.class) {
925              throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) +
926                                                 "; arguments: " + Arrays.toString(arguments) +
927                                                 "; parameter type: java.lang.Double" +
928                                                 "; argument type: " + argumentType.getName());
929            }
930          } else if (parameterType == float.class) {
931            if (argumentType != byte.class &&
932                argumentType != char.class &&
933                argumentType != Float.class &&
934                argumentType != int.class &&
935                argumentType != long.class &&
936                argumentType != short.class) {
937              throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) +
938                                                 "; arguments: " + Arrays.toString(arguments) +
939                                                 "; parameter type: float" +
940                                                 "; argument type: " + argumentType.getName());
941            }
942          } else if (parameterType == Float.class) {
943            if (argumentType != float.class) {
944              throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) +
945                                                 "; arguments: " + Arrays.toString(arguments) +
946                                                 "; parameter type: java.lang.Float" +
947                                                 "; argument type: " + argumentType.getName());
948            }
949          } else if (parameterType == int.class) {
950            if (argumentType != byte.class &&
951                argumentType != char.class &&
952                argumentType != Integer.class &&
953                argumentType != short.class) {
954              throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) +
955                                                 "; arguments: " + Arrays.toString(arguments) +
956                                                 "; parameter type: int; argument type: " + argumentType.getName());
957            }
958          } else if (parameterType == Integer.class) {
959            if (argumentType != int.class) {
960              throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) +
961                                                 "; arguments: " + Arrays.toString(arguments) +
962                                                 "; parameter type: java.lang.Integer" +
963                                                 "; argument type: " + argumentType.getName());
964            }
965          } else if (parameterType == long.class) {
966            if (argumentType != byte.class &&
967                argumentType != char.class &&
968                argumentType != int.class &&
969                argumentType != Long.class &&
970                argumentType != short.class) {
971              throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) +
972                                                 "; arguments: " + Arrays.toString(arguments) +
973                                                 "; parameter type: long" +
974                                                 "; argument type: " + argumentType.getName());
975            }
976          } else if (parameterType == Long.class) {
977            if (argumentType != long.class) {
978              throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) +
979                                                 "; arguments: " + Arrays.toString(arguments) +
980                                                 "; parameter type: java.lang.Long" +
981                                                 "; argument type: " + argumentType.getName());
982            }
983          } else if (parameterType == short.class) {
984            if (argumentType != byte.class &&
985                argumentType != Short.class) {
986              throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) +
987                                                 "; arguments: " + Arrays.toString(arguments) +
988                                                 "; parameter type: byte" +
989                                                 "; argument type: " + argumentType.getName());
990            }
991          } else if (parameterType == Short.class) {
992            if (argumentType != short.class) {
993              throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) +
994                                                 "; arguments: " + Arrays.toString(arguments) +
995                                                 "; parameter type: java.lang.Short" +
996                                                 "; argument type: " + argumentType.getName());
997            }
998          } else if (parameterType == Void.class || !parameterType.isAssignableFrom(argumentType)) {
999            throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) +
1000                                               "; arguments: " + Arrays.toString(arguments) +
1001                                               "; parameter type: " + parameterType.getName() +
1002                                               "; argument type: " + argumentType.getName());
1003          }
1004        }
1005      }
1006    }
1007  }
1008
1009}