From a366c7abd52c4b43df63e343d643f6fcf3f59314 Mon Sep 17 00:00:00 2001 From: John Blum Date: Mon, 29 Apr 2019 15:31:21 -0700 Subject: [PATCH] Add findMethod(:Class, :String, :Object[]):Optional 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. --- .../geode/core/util/ObjectUtils.java | 78 +++++++++++++++++- .../geode/core/util/ObjectUtilsUnitTests.java | 80 ++++++++++++++++++- 2 files changed, 153 insertions(+), 5 deletions(-) diff --git a/spring-geode/src/main/java/org/springframework/geode/core/util/ObjectUtils.java b/spring-geode/src/main/java/org/springframework/geode/core/util/ObjectUtils.java index b3f5947b..a69feae7 100644 --- a/spring-geode/src/main/java/org/springframework/geode/core/util/ObjectUtils.java +++ b/spring-geode/src/main/java/org/springframework/geode/core/util/ObjectUtils.java @@ -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 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 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 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 {@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 resolveInvocationTarget(T target, Method method) { + return Modifier.isStatic(method.getModifiers()) ? null : target; + } + @FunctionalInterface public interface ExceptionThrowingOperation { T doExceptionThrowingOperation() throws Exception; diff --git a/spring-geode/src/test/java/org/springframework/geode/core/util/ObjectUtilsUnitTests.java b/spring-geode/src/test/java/org/springframework/geode/core/util/ObjectUtilsUnitTests.java index 1ac57fd4..57026040 100644 --- a/spring-geode/src/test/java/org/springframework/geode/core/util/ObjectUtilsUnitTests.java +++ b/spring-geode/src/test/java/org/springframework/geode/core/util/ObjectUtilsUnitTests.java @@ -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 = + 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 = + 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 = ObjectUtils.findMethod(TestObject.class, "exactArgumentMethod"); + + assertThat(method.isPresent()).isFalse(); + } + + @Test + public void findMethodWithTooManyArguments() { + + Optional method = ObjectUtils.findMethod(TestObject.class, "testMethod", 'X'); + + assertThat(method.isPresent()).isFalse(); + } + + @Test + public void findNonExistingMethod() { + + Optional 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"; }