Add findMethod(:Class, :String, :Object[]):Optional<Method> and supporting private Predicate creation methods.

findMethod(..) matches a Method based on the Object's Class type, the Method name and whether the arguments passed can be used to invoek a (potentially) overloaded method by the same name.

Add a resolveInvocationTarget(:Object, :Method) to resolve the target of the Method invocation.

The target of the Method invocation is determined by whether the Method is static; if the Method is static, then the resolved target is null, otherwise it is the target Object.
This commit is contained in:
John Blum
2019-04-29 15:31:21 -07:00
parent 60c65548ee
commit a366c7abd5
2 changed files with 153 additions and 5 deletions

View File

@@ -16,17 +16,22 @@
package org.springframework.geode.core.util;
import static org.springframework.data.gemfire.util.ArrayUtils.nullSafeArray;
import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalArgumentException;
import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalStateException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Predicate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
@@ -118,6 +123,56 @@ public abstract class ObjectUtils extends org.springframework.util.ObjectUtils {
}
}
/**
* Finds a {@link Method} with the given {@link Method#getName() name} on {@link Class type} which can be invoked
* with the given {@link Object arguments}.
*
* @param type {@link Class} type to evaluate for the {@link Method}.
* @param methodName {@link String} containing the name of the {@link Method} to find.
* @param args {@link Object array of arguments} used when invoking the method.
* @return an {@link Optional} {@link Method} on {@link Class type} potentially matching
* the {@link Object arguments} of the invocation.
* @see java.lang.Class
* @see java.lang.reflect.Method
* @see java.util.Optional
*/
public static Optional<Method> findMethod(@NonNull Class<?> type, @NonNull String methodName, Object... args) {
return Arrays.stream(nullSafeArray(type.getDeclaredMethods(), Method.class))
.filter(methodNameMatchesPredicate(methodName))
.filter(argumentsMatchParameterTypesPredicate(args))
.findFirst();
}
private static Predicate<Method> argumentsMatchParameterTypesPredicate(Object... args) {
return method -> {
Class<?>[] parameterTypes = nullSafeArray(method.getParameterTypes(), Class.class);
Object[] arguments = nullSafeArray(args, Object.class);
if (arguments.length != parameterTypes.length) {
return false;
}
for (int index = 0; index < parameterTypes.length; index++) {
Object argument = arguments[index];
if (argument != null && !parameterTypes[index].isInstance(argument)) {
return false;
}
}
return true;
};
}
private static Predicate<Method> methodNameMatchesPredicate(String methodName) {
return method -> method.getName().equals(methodName);
}
/**
* Gets the {@link Object value} of the given {@link String named} {@link Field} on the given {@link Object}.
*
@@ -184,17 +239,17 @@ public abstract class ObjectUtils extends org.springframework.util.ObjectUtils {
methodName, org.springframework.util.ObjectUtils.nullSafeClassName(obj)));
}
private static Constructor makeAccessible(Constructor<?> constructor) {
public static Constructor makeAccessible(Constructor<?> constructor) {
ReflectionUtils.makeAccessible(constructor);
return constructor;
}
private static Field makeAccessible(Field field) {
public static Field makeAccessible(Field field) {
ReflectionUtils.makeAccessible(field);
return field;
}
private static Method makeAccessible(Method method) {
public static Method makeAccessible(Method method) {
ReflectionUtils.makeAccessible(method);
return method;
}
@@ -232,6 +287,23 @@ public abstract class ObjectUtils extends org.springframework.util.ObjectUtils {
return value;
}
/**
* Resolves the {@link Object invocation target} for the given {@link Method}.
*
* If the {@link Method} is {@link Modifier#STATIC} then {@literl null} is returned,
* otherwise {@link Object target} will be returned.
*
* @param <T> {@lin Class type} of the {@link Object target}.
* @param target {@link Object} on which the {@link Method} will be invoked.
* @param method {@link Method} to invoke on the {@link Object}.
* @return the resolved {@link Object invocation method}.
* @see java.lang.Object
* @see java.lang.reflect.Method
*/
public static <T> T resolveInvocationTarget(T target, Method method) {
return Modifier.isStatic(method.getModifiers()) ? null : target;
}
@FunctionalInterface
public interface ExceptionThrowingOperation<T> {
T doExceptionThrowingOperation() throws Exception;

View File

@@ -20,6 +20,8 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newRuntimeException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Optional;
import org.junit.Test;
@@ -61,6 +63,50 @@ public class ObjectUtilsUnitTests {
}
}
@Test
public void findMethodUsingExactArguments() {
Optional<Method> method =
ObjectUtils.findMethod(TestObject.class, "exactArgumentMethod", true);
assertThat(method.orElse(null)).isNotNull();
assertThat(method.map(Method::getName).orElse(null)).isEqualTo("exactArgumentMethod");
}
@Test
public void findMethodUsingGenericArguments() {
Optional<Method> method =
ObjectUtils.findMethod(TestObject.class, "genericArgumentMethod", "test", 2L);
assertThat(method.orElse(null)).isNotNull();
assertThat(method.map(Method::getName).orElse(null)).isEqualTo("genericArgumentMethod");
}
@Test
public void findMethodWithTooFewArguments() {
Optional<Method> method = ObjectUtils.findMethod(TestObject.class, "exactArgumentMethod");
assertThat(method.isPresent()).isFalse();
}
@Test
public void findMethodWithTooManyArguments() {
Optional<Method> method = ObjectUtils.findMethod(TestObject.class, "testMethod", 'X');
assertThat(method.isPresent()).isFalse();
}
@Test
public void findNonExistingMethod() {
Optional<Method> method = ObjectUtils.findMethod(TestObject.class, "nonExistingMethod");
assertThat(method.isPresent()).isFalse();
}
@Test
public void getFieldValueIsSuccessful() throws Exception {
@@ -74,7 +120,7 @@ public class ObjectUtilsUnitTests {
}
@Test(expected = IllegalArgumentException.class)
public void testGetFieldWithNullObjectThrowsIllegalArgumentException() throws Exception {
public void getFieldWithNullObjectThrowsIllegalArgumentException() throws Exception {
try {
ObjectUtils.get(null, TestObject.class.getDeclaredField("existingField"));
@@ -89,7 +135,7 @@ public class ObjectUtilsUnitTests {
}
@Test(expected = IllegalArgumentException.class)
public void testGetFieldWithNullFieldThrowsIllegalArgumentException() throws Exception {
public void getFieldWithNullFieldThrowsIllegalArgumentException() throws Exception {
try {
ObjectUtils.get(new TestObject(), (Field) null);
@@ -160,6 +206,22 @@ public class ObjectUtilsUnitTests {
}
}
@Test
public void resolveInvocationTargetOnClassMethodReturnsNull() throws Exception {
Method staticTestMethod = TestObject.class.getDeclaredMethod("staticTestMethod");
assertThat(ObjectUtils.resolveInvocationTarget(TestObject.INSTANCE, staticTestMethod)).isNull();
}
@Test
public void resolveInvocationTargetOnObjectMethodReturnsObject() throws Exception {
Method testMethod = TestObject.class.getDeclaredMethod("testMethod");
assertThat(ObjectUtils.resolveInvocationTarget(TestObject.INSTANCE, testMethod)).isEqualTo(TestObject.INSTANCE);
}
@Test
public void returnValueThrowOnNullWithNonNullValueReturnsValue() {
assertThat(ObjectUtils.returnValueThrowOnNull("test")).isEqualTo("test");
@@ -183,8 +245,22 @@ public class ObjectUtilsUnitTests {
@SuppressWarnings("unused")
public static class TestObject {
public static final TestObject INSTANCE = new TestObject();
private Object existingField = "MOCK";
public static Object staticTestMethod() {
return "STATIC";
}
public Object exactArgumentMethod(Boolean condition) {
return "exactArgumentMethod";
}
public Object genericArgumentMethod(String name, Number value) {
return "genericArgumentMethod";
}
public String testMethod() {
return "TEST";
}