org.cp.elements.lang.reflect.MethodInvocation Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of cp-elements Show documentation
Show all versions of cp-elements Show documentation
Java Simplified. Extensions and Useful Constructs for the Java Platform.
Codeprimate Elements (a.k.a. cp-elements) is a Java library and micro-framework used to simplify
the development of software applications written in Java. Elements packages several APIs into one
library in order to address various application concerns and aspects of software design and development
collectively and conveniently. Elements is a highly simple, yet robust and proven library built on
solid OO principles, software design patterns and best practices to effectively solve common
and reoccurring problems in software development.
/*
* Copyright 2016 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.LangExtensions.assertThat;
import static org.cp.elements.lang.RuntimeExceptionsFactory.newIllegalArgumentException;
import static org.cp.elements.util.ArrayUtils.nullSafeArray;
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.NullSafe;
/**
* 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 MethodInvocation newMethodInvocation(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 MethodInvocation newMethodInvocation(Object target, Method method, Object... args) {
return new MethodInvocation(target, method, args);
}
/**
* Factory method used to construct an instance of {@link MethodInvocation} initialized with the given
* {@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 MethodInvocation newMethodInvocation(Class> type, String methodName, Object... args) {
Assert.notNull(type, "Class type cannot be null");
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 MethodInvocation newMethodInvocation(Object target, String methodName, Object... args) {
Assert.notNull(target, "Target object cannot be null");
return new MethodInvocation(target, ClassUtils.findMethod(target.getClass(), methodName, args), args);
}
private final Method method;
private Object target;
private Object[] arguments;
/**
* Constructs an instance of {@link MethodInvocation} initialized with the given {@link Object target}
* on which the given {@link Method} will 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.
* @param args array of {@link Object arguments} passed to the {@link Method}.
* @throws IllegalArgumentException if {@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(Object target, Method method, Object... args) {
Assert.notNull(method, "Method cannot be null");
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(Method method, Object... args) {
Assert.notNull(method, "Method cannot be null");
Object[] arguments = nullSafeArray(args);
int methodParameterCount = method.getParameterCount();
assertThat(arguments.length)
.throwing(newIllegalArgumentException(
"The number of arguments [%1$d] does not match the number of parameters [%2$d] for method [%3$s] in class [%4$s]",
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 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 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 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 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 e) {
throw new MethodInvocationException(String.format("Failed to invoke method [%1$s] on target object [%2$s]",
method.getName(), resolvedTarget), e);
}
}
/**
* 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;
}
}