All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.microbean.interceptor.Chain Maven / Gradle / Ivy

There is a newer version: 0.4.0
Show newest version
/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
 *
 * Copyright © 2022–2024 microBean™.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations under the License.
 */
package org.microbean.interceptor;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.invoke.MethodType;
import java.lang.invoke.VarHandle;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;

import jakarta.interceptor.InvocationContext;

import static java.lang.invoke.MethodHandles.lookup;
import static java.lang.invoke.MethodHandles.privateLookupIn;

import static org.microbean.interceptor.LowLevelOperation.invokeUnchecked;

/**
 * A {@link Callable} {@link InvocationContext} implementation representing the interception of a constructor, method,
 * or lifecycle event.
 *
 * @author Laird Nelson
 *
 * @see #proceed()
 */
public class Chain implements Callable, InvocationContext {


  /*
   * Static fields.
   */


  private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];

  private static final Lookup lookup = lookup();

  private static final VarHandle ARGUMENTS;

  private static final VarHandle TARGET;

  static {
    try {
      ARGUMENTS = lookup.findVarHandle(Chain.class, "arguments", Object[].class);
      TARGET = lookup.findVarHandle(Chain.class, "target", Object.class);
    } catch (final IllegalAccessException | NoSuchFieldException e) {
      throw (ExceptionInInitializerError)new ExceptionInInitializerError(e.getMessage()).initCause(e);
    }
  }


  /*
   * Instance fields.
   */


  private final ConcurrentMap contextData;

  private final Supplier> constructorSupplier;

  private final Supplier methodSupplier;

  private final Supplier timerSupplier;

  private final Supplier targetSupplier;

  private final Supplier argumentsSupplier;

  private final Consumer setParametersImplementation;

  private final Callable proceedImplementation;

  private final Chain targetHost;

  private volatile Object[] arguments;

  private volatile Object target;


  /*
   * Constructors.
   */


  /**
   * Creates a new {@link Chain} primarily for testing purposes.
   *
   * 

The resulting {@link Chain} will return {@code null} from the following methods:

* *
    * *
  • {@link #call()}
  • * *
  • {@link #getConstructor()}
  • * *
  • {@link #getMethod()}
  • * *
  • {@link #getTimer()}
  • * *
  • {@link #getTarget()}
  • * *
  • {@link #proceed()}
  • * *
*/ public Chain() { super(); this.contextData = new ConcurrentHashMap<>(); this.constructorSupplier = Chain::returnNull; this.methodSupplier = Chain::returnNull; this.timerSupplier = Chain::returnNull; this.targetSupplier = Chain::returnNull; this.targetHost = this; this.proceedImplementation = Chain::returnNull; this.argumentsSupplier = Chain::emptyObjectArray; this.setParametersImplementation = Chain::throwIllegalStateException; this.arguments = EMPTY_OBJECT_ARRAY; } /** * Creates a new {@link Chain} for lifecycle method interceptions. * *

The resulting {@link Chain} will return {@code null} from the following methods:

* *
    * *
  • {@link #getConstructor()}
  • * *
  • {@link #getMethod()}
  • * *
  • {@link #getTimer()}
  • * *
* * @param interceptorMethods a {@link List} of {@link InterceptorMethod}s; may, rather uselessly, be {@code null} * * @param targetSupplier a {@link Supplier} of the target instance; may, rather uselessly, be {@code null} */ // For lifecycle events like PostConstruct, PreDestroy; no terminal function in these cases public Chain(final List interceptorMethods, final Supplier targetSupplier) { this(interceptorMethods, // Chain::returnNull, // terminal function null, // terminal function; null on purpose false, // set target new ConcurrentHashMap<>(), Chain::returnNull, // constructor supplier Chain::returnNull, // method supplier targetSupplier, Chain::emptyObjectArray, // arguments supplier Chain::sink, // arguments validator Chain::returnNull, // timer supplier null); // targetHost } /** * Creates a new {@link Chain} for around-construct interceptions. * *

The resulting {@link Chain} will return {@code null} from the following methods:

* *
    * *
  • {@link #getMethod()}
  • * *
  • {@link #getTarget()}
  • * *
  • {@link #getTimer()}
  • * *
* * @param interceptorMethods a {@link List} of {@link InterceptorMethod}s; may, rather uselessly, be {@code null} * * @param terminalConstructor the {@link Constructor} being intercepted; must not be {@code null} * * @exception NullPointerException if {@code terminalConstructor} is {@code null} * * @exception IllegalAccessException if {@linkplain Lookup#unreflectConstructor(Constructor) unreflecting} fails */ public Chain(final List interceptorMethods, final Constructor terminalConstructor) throws IllegalAccessException { this(interceptorMethods, terminalFunctionOf(terminalConstructor), true, // set target new ConcurrentHashMap<>(), () -> terminalConstructor, Chain::returnNull, // method supplier Chain::returnNull, // targetSupplier (initial target supplier) Chain::emptyObjectArray, // arguments supplier args -> validate(terminalConstructor.getParameterTypes(), args), Chain::returnNull, // timer supplier null); // targetHost } /** * Creates a new {@link Chain} for around-construct interceptions. * *

The resulting {@link Chain} will return {@code null} from the following methods:

* *
    * *
  • {@link #getMethod()}
  • * *
  • {@link #getTimer()}
  • * *
* * @param interceptorMethods a {@link List} of {@link InterceptorMethod}s; may, rather uselessly, be {@code null} * * @param terminalConstructor the {@link Constructor} being intercepted; must not be {@code null} * * @param argumentsSupplier a {@link Supplier} supplying the arguments for the {@link Constructor}; may be {@code * null} * * @exception NullPointerException if {@code terminalConstructor} is {@code null} * * @exception IllegalAccessException if {@linkplain Lookup#unreflectConstructor(Constructor) unreflecting} fails */ public Chain(final List interceptorMethods, final Constructor terminalConstructor, final Supplier argumentsSupplier) throws IllegalAccessException { this(interceptorMethods, terminalFunctionOf(terminalConstructor), true, // set target new ConcurrentHashMap<>(), () -> terminalConstructor, Chain::returnNull, // method supplier Chain::returnNull, // targetSupplier (initial target supplier) argumentsSupplier, args -> validate(terminalConstructor.getParameterTypes(), args), Chain::returnNull, // timer supplier null); // targetHost } /** * Creates a new {@link Chain} for around-invoke interceptions. * *

The resulting {@link Chain} will return {@code null} from the following methods:

* *
    * *
  • {@link #getConstructor()}
  • * *
  • {@link #getTimer()}
  • * *
* * @param interceptorMethods a {@link List} of {@link InterceptorMethod}s; may, rather uselessly, be {@code null} * * @param targetSupplier a {@link Supplier} of the target instance; may, rather uselessly, be {@code null} * * @param terminalMethod the {@link Method} to intercept; must not be {@code null} * * @exception NullPointerException if {@code terminalMethod} is {@code null} * * @exception IllegalAccessException if {@linkplain Lookup#unreflect(Method) unreflecting} fails */ public Chain(final List interceptorMethods, final Supplier targetSupplier, final Method terminalMethod) throws IllegalAccessException { this(interceptorMethods, terminalFunctionOf(terminalMethod, targetSupplier), false, // don't set target new ConcurrentHashMap<>(), Chain::returnNull, // constructor supplier () -> terminalMethod, targetSupplier, Chain::emptyObjectArray, // arguments supplier args -> validate(terminalMethod.getParameterTypes(), args), Chain::returnNull, // timer supplier null); // targetHost } /** * Creates a new {@link Chain} for around-invoke interceptions. * *

The resulting {@link Chain} will return {@code null} from the following methods:

* *
    * *
  • {@link #getConstructor()}
  • * *
  • {@link #getTimer()}
  • * *
* * @param interceptorMethods a {@link List} of {@link InterceptorMethod}s; may, rather uselessly, be {@code null} * * @param targetSupplier a {@link Supplier} of the target instance; may, rather uselessly, be {@code null} * * @param terminalMethod the {@link Method} to intercept; must not be {@code null} * * @param argumentsSupplier a {@link Supplier} supplying the arguments for the {@link Method}; may be {@code null} * * @exception NullPointerException if {@code terminalMethod} is {@code null} * * @exception IllegalAccessException if {@linkplain Lookup#unreflect(Method) unreflecting} fails */ public Chain(final List interceptorMethods, final Supplier targetSupplier, final Method terminalMethod, final Supplier argumentsSupplier) throws IllegalAccessException { this(interceptorMethods, terminalFunctionOf(terminalMethod, targetSupplier), false, // don't set target new ConcurrentHashMap<>(), Chain::returnNull, // constructor supplier () -> terminalMethod, targetSupplier, argumentsSupplier, // Chain::sink, // arguments validator args -> validate(terminalMethod.getParameterTypes(), args), Chain::returnNull, // timer supplier null); // targetHost } /** * Creates a new {@link Chain} for around-construct interceptions. * *

The resulting {@link Chain} will return {@code null} from the following methods:

* *
    * *
  • {@link #getConstructor()}
  • * *
  • {@link #getMethod()}
  • * *
  • {@link #getTimer()}
  • * *
* * @param interceptorMethods a {@link List} of {@link InterceptorMethod}s; may, rather uselessly, be {@code null} * * @param terminalFunction the terminal {@link Function} to intercept; must not be {@code null} * * @param argumentsSupplier a {@link Supplier} supplying the arguments for the terminal {@link Function}; may be * {@code null} * * @exception NullPointerException if {@code terminalFunction} is {@code null} */ public Chain(final List interceptorMethods, final Function terminalFunction, final Supplier argumentsSupplier) { this(interceptorMethods, Objects.requireNonNull(terminalFunction, "terminalFunction"), true, // set target new ConcurrentHashMap<>(), Chain::returnNull, // constructor supplier Chain::returnNull, // method supplier Chain::returnNull, // target supplier (won't ever be consulted/invoked; target is set by terminalFunction directly) argumentsSupplier, Chain::sink, // arguments validator Chain::returnNull, // timer supplier null); // targetHost } /** * Creates a new {@link Chain} for around-construct or around-invoke interceptions. * *

The resulting {@link Chain} will return {@code null} from the following methods:

* *
    * *
  • {@link #getConstructor()}
  • * *
  • {@link #getMethod()}
  • * *
  • {@link #getTimer()}
  • * *
* * @param interceptorMethods a {@link List} of {@link InterceptorMethod}s; may, rather uselessly, be {@code null} * * @param targetSupplier a {@link Supplier} of the target instance; may, rather uselessly, be {@code null} * * @param terminalFunction the terminal {@link Function} to intercept; must not be {@code null} * * @param setTarget whether the supplied {@code terminalFunction} is effectively a constructor * * @param argumentsSupplier a {@link Supplier} supplying the arguments for the terminal {@link Function}; may be * {@code null} * * @exception NullPointerException if {@code terminalFunction} is {@code null} */ public Chain(final List interceptorMethods, final Supplier targetSupplier, final Function terminalFunction, final boolean setTarget, // is the terminal function effectively a constructor? final Supplier argumentsSupplier) { this(interceptorMethods, Objects.requireNonNull(terminalFunction, "terminalFunction"), setTarget, new ConcurrentHashMap<>(), Chain::returnNull, // constructor supplier Chain::returnNull, // method supplier targetSupplier, argumentsSupplier, Chain::sink, // arguments validator Chain::returnNull, // timer supplier null); // targetHost } // Everything is nullable. private Chain(List interceptorMethods, final Function terminalFunction, final boolean setTarget, final ConcurrentMap contextData, final Supplier> constructorSupplier, final Supplier methodSupplier, final Supplier targetSupplier, final Supplier argumentsSupplier, final Consumer argumentsValidator, final Supplier timerSupplier, final Chain targetHost) { super(); this.contextData = contextData == null ? new ConcurrentHashMap<>() : contextData; this.constructorSupplier = constructorSupplier == null ? Chain::returnNull : constructorSupplier; this.methodSupplier = methodSupplier == null ? Chain::returnNull : methodSupplier; this.argumentsSupplier = argumentsSupplier == null ? Chain::emptyObjectArray : argumentsSupplier; this.timerSupplier = timerSupplier == null ? Chain::returnNull : timerSupplier; this.targetSupplier = targetSupplier == null ? Chain::returnNull : targetSupplier; this.targetHost = targetHost == null ? this : targetHost; if (interceptorMethods == null || interceptorMethods.isEmpty()) { Objects.requireNonNull(terminalFunction, "terminalFunction"); if (argumentsValidator == null) { this.setParametersImplementation = args -> this.setArguments(Chain::sink, args); } else { this.setParametersImplementation = args -> this.setArguments(argumentsValidator, args); } if (setTarget) { this.proceedImplementation = () -> { final Object t = terminalFunction.apply(this.getParameters()); TARGET.setVolatile(this.targetHost, t); // volatile write return t; }; } else { this.proceedImplementation = () -> terminalFunction.apply(this.getParameters()); } } else { if (terminalFunction == null) { this.setParametersImplementation = Chain::throwIllegalStateException; } else if (argumentsValidator == null) { this.setParametersImplementation = args -> this.setArguments(Chain::sink, args); } else { this.setParametersImplementation = args -> this.setArguments(argumentsValidator, args); } interceptorMethods = List.copyOf(interceptorMethods); final InterceptorMethod im = interceptorMethods.get(0); final int size = interceptorMethods.size(); final List ims = size == 1 ? List.of() : interceptorMethods.subList(1, size); this.proceedImplementation = () -> im.intercept(new Chain(ims, terminalFunction, setTarget, this.contextData, this::getConstructor, this::getMethod, this.targetSupplier, this.argumentsSupplier, argumentsValidator, this::getTimer, this.targetHost)); } } /* * Instance methods. */ /** * Returns the {@link Constructor} being intercepted, if available, or {@code null}. * * @return the {@link Constructor} being intercepted, if available, or {@code null} */ @Override public final Constructor getConstructor() { return this.constructorSupplier.get(); } /** * Returns the context data {@link Map} shared by the current invocation. * * @return the context data {@link Map} shared by the current invocation; never {@code null} * * @see InvocationContext#getContextData() */ @Override public final Map getContextData() { return this.contextData; } /** * Returns the {@link Method} being intercepted, if available, or {@code null}. * * @return the {@link Method} being intercepted, if available, or {@code null} */ // OK to return null in many cases; see // https://jakarta.ee/specifications/interceptors/2.1/jakarta-interceptors-spec-2.1#invocation_context @Override public final Method getMethod() { return this.methodSupplier.get(); } /** * Returns any arguments in effect for the current interception. * *

For historical reasons, the Jakarta Interceptors specification refers, incorrectly, to arguments as * "parameters".

* * @return any arguments in effect for the current interception; never {@code null} * * @exception IllegalStateException if invoked within a lifecycle callback method that is not an {@link * jakarta.interceptor.AroundConstruct AroundConstruct} callback * * @see #setParameters(Object[]) */ @Override public final Object[] getParameters() { // Cloning etc. is not necessary; this whole API is stupid final Object[] arguments = this.arguments; // volatile read if (arguments == null) { try { this.setParameters(this.argumentsSupplier.get()); } catch (final IllegalArgumentException e) { throw new IllegalStateException(e.getMessage(), e); } return this.arguments; // volatile read } return arguments; } /** * Returns the target instance, if available, or {@code null}. * * @return the target instance, if available, or {@code null} */ @Override public final Object getTarget() { Object target = this.targetHost.target; // volatile read if (target == null) { target = this.targetSupplier.get(); if (target != null && TARGET.compareAndSet(this.targetHost, null, target)) { // volatile write target = this.targetHost.target; // volatile read } } return target; } /** * Sets the target instance to be the supplied {@code target}. * * @param target the target instance; must not be {@code null} * * @exception NullPointerException if {@code target} is {@code null} * * @see #getTarget() */ public final void setTarget(final Object target) { TARGET.setVolatile(this.targetHost, Objects.requireNonNull(target, "target")); // volatile write } /** * Returns the timer, if available, or {@code null}. * * @return the timer, if available, or {@code null} */ @Override public final Object getTimer() { return this.timerSupplier.get(); } /** * Invokes the {@link #proceed()} method and returns its result. * * @return the result of invoking the {@link #proceed()} method, which may be {@code null} * * @exception Exception if an error occurs * * @see #proceed() */ @Override // Callable public final Object call() throws Exception { return this.proceed(); } /** * Applies the next interception in this {@link Chain}, or calls the terminal function, and returns the result of the * interception, which may be {@code null}. * *

Overrides must not call {@link #call()} or an infinite loop will result.

* * @return the result of proceeding, which may be {@code null} * * @exception Exception if an error occurs */ @Override public Object proceed() throws Exception { return this.proceedImplementation.call(); } /** * Sets arguments to be in effect for the current interception. * *

For historical reasons, the Jakarta Interceptors specification refers, incorrectly, to arguments as * "parameters".

* * @param arguments the arguments; may be {@code null} * * @exception IllegalArgumentException if the arguments are invalid * * @exception IllegalStateException if invoked within a lifecycle callback method that is not an {@link * jakarta.interceptor.AroundConstruct AroundConstruct} callback * * @see #getParameters() */ @Override public final void setParameters(final Object[] arguments) { this.setParametersImplementation.accept(arguments); } private final void setArguments(final Consumer argumentsValidator, final Object[] arguments) { if (arguments == null) { argumentsValidator.accept(EMPTY_OBJECT_ARRAY); this.arguments = EMPTY_OBJECT_ARRAY; // volatile write } else { // Cloning etc. is not necessary; this whole API is stupid argumentsValidator.accept(arguments); this.arguments = arguments; // volatile write } } /* * Static methods. */ /** * Creates and returns a {@link Function} encapsulating the supplied {@link Constructor}. * * @param c a {@link Constructor}; must not be {@code null} * * @return a {@link Function} encapsulating the supplied {@link Constructor}; never {@code null} * * @exception NullPointerException if {@code c} is {@code null} * * @exception IllegalAccessException if {@linkplain Lookup#unreflectConstructor(Constructor) unreflecting} fails */ public static final Function terminalFunctionOf(final Constructor c) throws IllegalAccessException { return terminalFunctionOf(privateLookupIn(c.getDeclaringClass(), Chain.lookup).unreflectConstructor(c), null); } /** * Creates and returns a {@link Function} encapsulating the supplied {@code static} {@link Method}. * * @param staticMethod a {@code static} {@link Method}; must not be {@code null} * * @return a {@link Function} encapsulating the supplied {@code static} {@link Method}; never {@code null} * * @exception NullPointerException if {@code staticMethod} is {@code null} * * @exception IllegalAccessException if {@linkplain Lookup#unreflect(Method) unreflecting} fails */ public static final Function terminalFunctionOf(final Method staticMethod) throws IllegalAccessException { return terminalFunctionOf(staticMethod, null); } /** * Creates and returns a {@link Function} encapsulating the supplied {@link Method}. * * @param m a {@link Method}; must not be {@code null}. If {@code m} is a virtual method, then the supplied {@code * receiverSupplier} will be used to supply its receiver * * @param receiverSupplier a {@link Supplier} of the receiver for the supplied {@link Method}; may be {@code null} in * which case the supplied {@link Method} must be {@code static} * * @return a {@link Function} encapsulating the supplied {@link Method}; never {@code null} * * @exception NullPointerException if {@code m} is {@code null} * * @exception IllegalAccessException if {@linkplain Lookup#unreflect(Method) unreflecting} fails */ public static final Function terminalFunctionOf(final Method m, final Supplier receiverSupplier) throws IllegalAccessException { return terminalFunctionOf(privateLookupIn(m.getDeclaringClass(), Chain.lookup).unreflect(m), receiverSupplier); } /** * Creates and returns a {@link Function} encapsulating the supplied {@link MethodHandle}. * * @param receiverlessMethodHandle a {@link MethodHandle}; must not be {@code null}; must be receiverless or * {@linkplain MethodHandle#bindTo(Object) bound} to a receiver already. * * @return a {@link Function} encapsulating the supplied {@link MethodHandle}; never {@code null} * * @exception NullPointerException if {@code receiverlessMethodHandle} is {@code null} */ public static final Function terminalFunctionOf(final MethodHandle receiverlessMethodHandle) { return terminalFunctionOf(receiverlessMethodHandle, null); } /** * Creates and returns a {@link Function} encapsulating the supplied {@link MethodHandle}. * * @param mh a {@link MethodHandle}; must not be {@code null}. If {@code mh} is a {@link MethodHandle} requiring a * receiver, then the supplied {@code receiverSupplier}, if non-{@code null}, will be used to supply its receiver * * @param receiverSupplier a {@link Supplier} of the receiver for the supplied {@link MethodHandle}; may be {@code * null} in which case the supplied {@link MethodHandle} must be receiverless * * @return a {@link Function} encapsulating the supplied {@link MethodHandle}; never {@code null} * * @exception NullPointerException if {@code mh} is {@code null} */ public static final Function terminalFunctionOf(MethodHandle mh, final Supplier receiverSupplier) { mh = mh.asType(mh.type().changeReturnType(Object.class)); MethodType mt = mh.type(); final int pc = mt.parameterCount(); final MethodHandle terminalFunction; if (receiverSupplier == null) { // Static switch (pc) { case 0: terminalFunction = mh; return ps -> invokeUnchecked(() -> terminalFunction.invokeExact()); default: terminalFunction = mh.asSpreader(Object[].class, pc); return ps -> invokeUnchecked(() -> terminalFunction.invokeExact(ps)); } } // Virtual mh = mh.asType(mt.changeParameterType(0, Object.class)); mt = mh.type(); switch (pc) { case 1: terminalFunction = mh; return ps -> invokeUnchecked(() -> terminalFunction.invokeExact(receiverSupplier.get())); default: terminalFunction = mh.asSpreader(Object[].class, pc - 1); return ps -> invokeUnchecked(() -> terminalFunction.invokeExact(receiverSupplier.get(), ps)); } } private static final T returnNull() { return null; } private static final X returnNull(final Y ignored) { return null; } private static final Object[] emptyObjectArray() { return EMPTY_OBJECT_ARRAY; } private static final void sink(T ignored) { } private static final void throwIllegalStateException(final X ignored) { throw new IllegalStateException(); } /** * A convenience method that ensures that every element of the supplied {@code arguments} array can be assigned to a * reference bearing the corresponding {@link Class} drawn from the supplied {@code parameterTypes} array. * *

Boxing, unboxing and widening conversions are taken into consideration.

* *

This method implements the logic implied, but nowhere actually specified, by the contract of {@link * InvocationContext#setParameters(Object[])}.

* * @param parameterTypes an array of {@link Class} instances; may be {@code null}; must not contain {@code null} * elements or {@code void.class}; must have a length equal to that of the supplied {@code arguments} array * * @param arguments an array of {@link Object}s; may be {@code null}; must have a length equal to that of the supplied * {@code parameterTypes} array * * @exception IllegalArgumentException if not every element of the supplied {@code arguments} array can be assigned to * a reference bearing the corresponding {@link Class} drawn from the supplied {@code parameterTypes} array * * @see #setParameters(Object[]) */ public static final void validate(final Class[] parameterTypes, final Object[] arguments) { final int parameterTypesLength = parameterTypes == null ? 0 : parameterTypes.length; final int argumentsLength = arguments == null ? 0 : arguments.length; if (argumentsLength != parameterTypesLength) { throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) + "; arguments: " + Arrays.toString(arguments)); } for (int i = 0; i < argumentsLength; i++) { final Class parameterType = parameterTypes[i]; if (parameterType == null || parameterType == void.class) { throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) + "; arguments: " + Arrays.toString(arguments) + "; parameter type: " + parameterType); } final Object argument = arguments[i]; if (argument == null) { if (parameterType.isPrimitive() || parameterType != Void.class) { throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) + "; arguments: " + Arrays.toString(arguments) + "; parameter type: " + parameterType.getName() + "; argument: null"); } } else { final Class argumentType = argument.getClass(); if (parameterType != argumentType) { if (parameterType == boolean.class) { if (argumentType != Boolean.class) { throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) + "; arguments: " + Arrays.toString(arguments) + "; parameter type: boolean" + "; argument type: " + argumentType.getName()); } } else if (parameterType == Boolean.class) { if (argumentType != boolean.class) { throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) + "; arguments: " + Arrays.toString(arguments) + "; parameter type: java.lang.Boolean" + "; argument type: " + argumentType.getName()); } } else if (parameterType == byte.class) { if (argumentType != Byte.class) { throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) + "; arguments: " + Arrays.toString(arguments) + "; parameter type: byte" + "; argument type: " + argumentType.getName()); } } else if (parameterType == Byte.class) { if (argumentType != byte.class) { throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) + "; arguments: " + Arrays.toString(arguments) + "; parameter type: java.lang.Byte" + "; argument type: " + argumentType.getName()); } } else if (parameterType == char.class) { if (argumentType != Character.class) { throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) + "; arguments: " + Arrays.toString(arguments) + "; parameter type: char" + "; argument type: " + argumentType.getName()); } } else if (parameterType == Character.class) { if (argumentType != char.class) { throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) + "; arguments: " + Arrays.toString(arguments) + "; parameter type: java.lang.Character" + "; argument type: " + argumentType.getName()); } } else if (parameterType == double.class) { if (argumentType != byte.class && argumentType != char.class && argumentType != Double.class && argumentType != float.class && argumentType != int.class && argumentType != long.class && argumentType != short.class) { throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) + "; arguments: " + Arrays.toString(arguments) + "; parameter type: double" + "; argument type: " + argumentType.getName()); } } else if (parameterType == Double.class) { if (argumentType != double.class) { throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) + "; arguments: " + Arrays.toString(arguments) + "; parameter type: java.lang.Double" + "; argument type: " + argumentType.getName()); } } else if (parameterType == float.class) { if (argumentType != byte.class && argumentType != char.class && argumentType != Float.class && argumentType != int.class && argumentType != long.class && argumentType != short.class) { throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) + "; arguments: " + Arrays.toString(arguments) + "; parameter type: float" + "; argument type: " + argumentType.getName()); } } else if (parameterType == Float.class) { if (argumentType != float.class) { throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) + "; arguments: " + Arrays.toString(arguments) + "; parameter type: java.lang.Float" + "; argument type: " + argumentType.getName()); } } else if (parameterType == int.class) { if (argumentType != byte.class && argumentType != char.class && argumentType != Integer.class && argumentType != short.class) { throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) + "; arguments: " + Arrays.toString(arguments) + "; parameter type: int; argument type: " + argumentType.getName()); } } else if (parameterType == Integer.class) { if (argumentType != int.class) { throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) + "; arguments: " + Arrays.toString(arguments) + "; parameter type: java.lang.Integer" + "; argument type: " + argumentType.getName()); } } else if (parameterType == long.class) { if (argumentType != byte.class && argumentType != char.class && argumentType != int.class && argumentType != Long.class && argumentType != short.class) { throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) + "; arguments: " + Arrays.toString(arguments) + "; parameter type: long" + "; argument type: " + argumentType.getName()); } } else if (parameterType == Long.class) { if (argumentType != long.class) { throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) + "; arguments: " + Arrays.toString(arguments) + "; parameter type: java.lang.Long" + "; argument type: " + argumentType.getName()); } } else if (parameterType == short.class) { if (argumentType != byte.class && argumentType != Short.class) { throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) + "; arguments: " + Arrays.toString(arguments) + "; parameter type: byte" + "; argument type: " + argumentType.getName()); } } else if (parameterType == Short.class) { if (argumentType != short.class) { throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) + "; arguments: " + Arrays.toString(arguments) + "; parameter type: java.lang.Short" + "; argument type: " + argumentType.getName()); } } else if (parameterType == Void.class || !parameterType.isAssignableFrom(argumentType)) { throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) + "; arguments: " + Arrays.toString(arguments) + "; parameter type: " + parameterType.getName() + "; argument type: " + argumentType.getName()); } } } } } }