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

org.cp.elements.lang.reflect.MethodInvocation Maven / Gradle / Ivy

/*
 * Copyright 2011-Present Author or Authors.
 *
 * 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.cp.elements.lang.reflect;

import static org.cp.elements.lang.ElementsExceptionsFactory.newMethodInvocationException;
import static org.cp.elements.lang.LangExtensions.assertThat;
import static org.cp.elements.lang.RuntimeExceptionsFactory.newIllegalArgumentException;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Optional;

import org.cp.elements.lang.Assert;
import org.cp.elements.lang.ClassUtils;
import org.cp.elements.lang.annotation.NotNull;
import org.cp.elements.lang.annotation.NullSafe;
import org.cp.elements.lang.annotation.Nullable;
import org.cp.elements.util.ArrayUtils;

/**
 * The {@link MethodInvocation} class encapsulates all the necessary information to invoke a {@link Method}
 * on a given target {@link Object}.
 *
 * @author John Blum
 * @see java.lang.reflect.Method
 * @since 1.0.0
 */
@SuppressWarnings("unused")
public class MethodInvocation {

  /**
   * Factory method used to construct a new instance of {@link MethodInvocation} initialized with
   * the given {@link Method} and array of {@link Object arguments} passed to the {@link Method}
   * during invocation.
   *
   * The {@link Method} is expected to be a {@link java.lang.reflect.Modifier#STATIC},
   * {@link Class} member {@link Method}.
   *
   * @param method {@link Method} to invoke.
   * @param args array of {@link Object arguments} to pass to the {@link Method} during invocation.
   * @return an instance of {@link MethodInvocation} encapsulating all the necessary details
   * to invoke the {@link java.lang.reflect.Modifier#STATIC} {@link Method}.
   * @see #newMethodInvocation(Object, Method, Object...)
   * @see java.lang.reflect.Method
   */
  public static @NotNull MethodInvocation newMethodInvocation(@NotNull Method method, Object... args) {
    return newMethodInvocation(null, method, args);
  }

  /**
   * Factory method used to construct a new instance of {@link MethodInvocation} initialized with
   * the given target {@link Object}, instance {@link Method} and array of {@link Object arguments}
   * passed to the {@link Method} during invocation.
   *
   * The {@link Method} represents a {@link java.lang.reflect.Modifier#STATIC non-static} instance {@link Method}
   * that will be invoked on the given target {@link Object}.
   *
   * @param target {@link Object} on which the instance {@link Method} will be invoked.
   * @param method {@link Method} to invoke.
   * @param args array of {@link Object arguments} to pass to the {@link Method} during invocation.
   * @return an instance of {@link MethodInvocation} encapsulating all the necessary details
   * to invoke the {@link Method} on the given {@link Object target}.
   * @see #MethodInvocation(Object, Method, Object...)
   * @see java.lang.reflect.Method
   * @see java.lang.Object
   */
  public static @NotNull MethodInvocation newMethodInvocation(Object target, @NotNull Method method, Object... args) {
    return new MethodInvocation(target, method, args);
  }

  /**
   * Factory method used to construct a new instance of {@link MethodInvocation} initialized with the given,
   * required {@link Class type} on which the {@code methodName named} {@link Method} accepting the given
   * array of {@link Object arguments} is declared.
   *
   * The {@link String named} {@link Method} is expected to be a {@link java.lang.reflect.Modifier#STATIC},
   * {@link Class} member {@link Method}.
   *
   * @param type {@link Class type} declaring the {@link Method}.
   * @param methodName {@link String name} of the {@link Method}.
   * @param args array of {@link Object arguments} passed to the {@link Method}.
   * @return an instance of {@link MethodInvocation} encapsulating all the necessary details to invoke
   * the {@link java.lang.reflect.Modifier#STATIC} {@link Method}.
   * @throws IllegalArgumentException if the {@link Class type} is {@literal null}.
   * @see #MethodInvocation(Object, Method, Object...)
   * @see java.lang.Class
   */
  public static @NotNull MethodInvocation newMethodInvocation(@NotNull Class type, String methodName,
      Object... args) {

    Assert.notNull(type, "Class type is required");

    return new MethodInvocation(null, ClassUtils.findMethod(type, methodName, args), args);
  }

  /**
   * Factory method used to construct an instance of {@link MethodInvocation} initialized with the given
   * {@link Object target} on which the {@code methodName named} {@link Method} accepting the given
   * array of {@link Object arguments} will be invoked.
   *
   * The {@link String named} {@link Method} represents a {@link java.lang.reflect.Modifier#STATIC non-static}
   * instance {@link Method} that will be invoked on the given target {@link Object}.
   *
   * @param target {@link Object} on which the {@link Method} will be invoked.
   * @param methodName {@link String name} of the {@link Method}.
   * @param args array of {@link Object arguments} passed to the {@link Method}.
   * @return an instance of {@link MethodInvocation} encapsulating all the necessary details to invoke
   * the instance {@link Method} on the target {@link Object}.
   * @throws IllegalArgumentException if the target {@link Object} is {@literal null}.
   * @see #MethodInvocation(Object, Method, Object...)
   * @see java.lang.Object
   */
  public static @NotNull MethodInvocation newMethodInvocation(@NotNull Object target, String methodName,
      Object... args) {

    Assert.notNull(target, "Target object is required");

    return new MethodInvocation(target, ClassUtils.findMethod(target.getClass(), methodName, args), args);
  }

  private final Method method;

  private Object target;

  private Object[] arguments;

  /**
   * Constructs a new instance of {@link MethodInvocation} initialized with the given {@link Object target}
   * on which the given {@link Method} will be invoked, passing the given array of {@link Object arguments}
   * to the {@link Method} during invocation.
   *
   * @param target {@link Object} on which the given {@link Method} is invoked.
   * @param method {@link Method} to invoke; must not be {@literal null}.
   * @param args array of {@link Object arguments} passed to the {@link Method}.
   * @throws IllegalArgumentException if the {@link Method} is {@literal null}, or {@link Object target}
   * is {@literal null} and the {@link Method} is not {@link java.lang.reflect.Modifier#STATIC}.
   * @see #validateArguments(Method, Object...)
   * @see java.lang.reflect.Method
   * @see java.lang.Object
   */
  public MethodInvocation(@Nullable Object target, @NotNull Method method, Object... args) {

    Assert.notNull(method, "Method is required");

    Assert.isTrue(target != null || ModifierUtils.isStatic(method),
      "Method must be static if target is null");

    this.arguments = validateArguments(method, args);
    this.method = method;
    this.target = target;
  }

  /**
   * Validates the array of {@link Object arguments} to be passed to the {@link Method} by comparing
   * the {@link Object arguments} to the given {@link Method Method's} declared parameters.
   *
   * @param method {@link Method} used to validate the array of {@link Object arguments}.
   * @param args array of {@link Object arguments} to validate.
   * @return the given array of {@link Object arguments}.
   * @throws IllegalArgumentException if {@link Method} is {@literal null}, or the number of arguments
   * does not equal the number of {@link Method} parameters, or an argument does not the match the type
   * of the corresponding {@link Method} parameter.
   * @see java.lang.reflect.Method
   */
  protected Object[] validateArguments(@NotNull Method method, Object... args) {

    Assert.notNull(method, "Method is required");

    Object[] arguments = ArrayUtils.nullSafeArray(args);

    int methodParameterCount = method.getParameterCount();

    String exceptionMessage =
      "The number of arguments [%1$d] does not match the number of parameters [%2$d] for method [%3$s] in class [%4$s]";

    assertThat(arguments.length)
      .throwing(newIllegalArgumentException(exceptionMessage, arguments.length, methodParameterCount, method.getName(),
        method.getDeclaringClass().getName()))
      .isEqualTo(methodParameterCount);

    Class[] parameterTypes = method.getParameterTypes();

    int parameterIndex = 0;

    for (Object argument : arguments) {
      assertThat(argument)
        .throwing(newIllegalArgumentException("Argument [%1$s] is not assignable to parameter [%2$d] of type [%3$s]",
          argument, parameterIndex, parameterTypes[parameterIndex].getName()))
        .isAssignableTo(parameterTypes[parameterIndex++]);
    }

    return arguments;
  }

  /**
   * Returns the array of {@link Object arguments} passed to the {@link Method}.
   *
   * @return an array of {@link Object arguments} passed to the {@link Method}.
   * Returns an empty array if the {@link Method} takes no arguments.
   */
  @NullSafe
  public Object[] getArguments() {
    return ArrayUtils.nullSafeArray(this.arguments);
  }

  /**
   * Returns the {@link Class} type on which this {@link Method} is declared.
   *
   * @return the declaring {@link Class type} for this {@link Method}.
   * @see java.lang.reflect.Method#getDeclaringClass()
   * @see #getMethod()
   */
  @NullSafe
  public @NotNull Class getDeclaringClass() {
    return getMethod().getDeclaringClass();
  }

  /**
   * Returns the target {@link Method} for this invocation.
   *
   * @return the target {@link Method} for this invocation.
   * @see java.lang.reflect.Method
   */
  public @NotNull Method getMethod() {
    return this.method;
  }

  /**
   * Returns the configured target {@link Object} on which the {@link Method} will be invoked.
   *
   * @return the configured target {@link Object} on which the {@link Method} will be invoked.
   * @see java.lang.Object
   */
  public @Nullable Object getTarget() {
    return this.target;
  }

  /**
   * Invokes the {@link Method} on the target {@link Object}.
   *
   * @param  {@link Class} type of the {@link Method} return value.
   * @return the result of the {@link Method} invocation on the given target {@link Object}
   * wrapped in a {@link Optional} to guard against {@literal null}.
   * @see java.util.Optional
   * @see #invoke(Object)
   * @see #getTarget()
   */
  public  Optional invoke() {
    return invoke(getTarget());
  }

  /**
   * Invokes the {@link Method} on the given target {@link Object}.
   *
   * @param  {@link Class} type of the {@link Method} return value.
   * @param target {@link Object} on which the {@link Method} will be invoked.
   * @return the result of the {@link Method} invocation on the given target {@link Object}
   * wrapped in a {@link Optional} to guard against {@literal null}.
   * @throws MethodInvocationException if an error occurs during the invocation of the {@link Method}
   * on the target {@link Object}.
   * @see java.lang.reflect.Method#invoke(Object, Object...)
   * @see java.util.Optional
   * @see #resolveTarget(Object)
   * @see #getArguments()
   * @see #getMethod()
   */
  @SuppressWarnings("unchecked")
  public  Optional invoke(Object target) {

    Object resolvedTarget = resolveTarget(target);

    Method method = getMethod();

    try {
      return Optional.ofNullable((T) method.invoke(resolvedTarget, getArguments()));
    }
    catch (IllegalAccessException | InvocationTargetException cause) {
      throw newMethodInvocationException(cause, "Failed to invoke method [%1$s] on target object [%2$s]",
        method.getName(), resolvedTarget);
    }
  }

  /**
   * Resolves the target {@link Object} on which the {@link Method} will be invoked.
   *
   * @param target {@link Object} to evaluate.
   * @return the resolved {@link Object} on which the {@link Method} will be invoked.
   * @see java.lang.Object
   * @see #getTarget()
   */
  protected Object resolveTarget(Object target) {
    return Optional.ofNullable(target).orElseGet(this::getTarget);
  }

  /**
   * Sets the accessibility of the {@link Method} to {@literal true}.
   *
   * @return this {@link MethodInvocation}.
   * @see java.lang.reflect.Method#setAccessible(boolean)
   * @see org.cp.elements.lang.reflect.MethodInvocation
   * @see #getMethod()
   */
  public MethodInvocation makeAccessible() {
    getMethod().setAccessible(true);
    return this;
  }

  /**
   * Sets the target {@link Object} on which the {@link Method} will be invoked.
   *
   * @param target {@link Object} on which the {@link Method} will be invoked.
   * @return this {@link MethodInvocation}.
   * @throws IllegalArgumentException if {@link Object target} is {@literal null} and {@link Method}
   * is not {@link java.lang.reflect.Modifier#STATIC}.
   * @see org.cp.elements.lang.reflect.MethodInvocation
   * @see java.lang.Object
   * @see #getMethod()
   */
  public MethodInvocation on(Object target) {

    Assert.isTrue(target != null || ModifierUtils.isStatic(getMethod()),
      "Method must be static if target is null");

    this.target = target;

    return this;
  }

  /**
   * Sets the array of {@link Object arguments} to pass to the {@link Method}.
   *
   * @param arguments array of {@link Object arguments} passed to the {@link Method}.
   * @return this {@link MethodInvocation}.
   * @throws IllegalArgumentException if the number of arguments is not equal to the number of {@link Method} parameters
   * or any argument {@link Class type} is not assignable to the corresponding parameter {@link Class type}.
   * @see org.cp.elements.lang.reflect.MethodInvocation
   * @see #validateArguments(Method, Object...)
   * @see #getMethod()
   */
  public MethodInvocation passing(Object... arguments) {
    this.arguments = validateArguments(getMethod(), arguments);
    return this;
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy