diff --git a/test-utils-core/pom.xml b/test-utils-core/pom.xml index a4e2ceb..f948102 100644 --- a/test-utils-core/pom.xml +++ b/test-utils-core/pom.xml @@ -88,13 +88,12 @@ org.reflections reflections - - junit junit - test + + com.btmatthews.hamcrest hamcrest-matchers diff --git a/test-utils-core/src/main/java/uk/gov/justice/services/test/utils/core/reflection/ReflectionUtil.java b/test-utils-core/src/main/java/uk/gov/justice/services/test/utils/core/reflection/ReflectionUtil.java new file mode 100644 index 0000000..b11e6f5 --- /dev/null +++ b/test-utils-core/src/main/java/uk/gov/justice/services/test/utils/core/reflection/ReflectionUtil.java @@ -0,0 +1,135 @@ +package uk.gov.justice.services.test.utils.core.reflection; + + +import static java.util.Arrays.stream; +import static java.util.Comparator.comparing; +import static java.util.stream.Collectors.toList; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.List; +import java.util.Optional; + +import edu.umd.cs.findbugs.annotations.SuppressWarnings; + +public final class ReflectionUtil { + + private ReflectionUtil() { + } + + /** + * @param classWithMethods - class type + * @return - list of methods of the classWithMethods + */ + public static List methodsOf(final Class classWithMethods) { + return stream(classWithMethods.getDeclaredMethods()).filter(m -> !m.getName().contains("jacoco") && !m.getName().contains("lambda")) + .sorted(comparing(Method::getName)) + .collect(toList()); + } + + /** + * returns first method of the given class + * + * @param classWithMethod - class type + * @return - first method of the given classWithMethod + */ + public static Optional firstMethodOf(final Class classWithMethod) { + final List methods = methodsOf(classWithMethod); + + return methods.stream().findFirst(); + } + + /** + * Returns method of the given class with the given name + * + * @param classWithMethod - class type + * @param methodName - name of method in class + * @return - method of the given class with the given name + */ + public static Optional methodOf(final Class classWithMethod, final String methodName) { + return methodsOf(classWithMethod).stream() + .filter(method -> method.getName().equals(methodName)) + .findFirst(); + } + + /** + * Get the method of a Class with the given annotation. Returns Optional.empty() if no method + * found. + * + * @param classWithMethod - the Class with the annotated method + * @param annotationClass - the Annotation Class to find + * @return the Method with the given annotation + */ + public static Optional annotatedMethod(final Class classWithMethod, final Class annotationClass) { + return stream(classWithMethod.getDeclaredMethods()) + .filter(method -> method.isAnnotationPresent(annotationClass)) + .findFirst(); + } + + /** + * sets value of the field by reflection + * + * @param object - object to modify + * @param fieldName - name of the field belonging to the object + * @param fieldValue - value of the field to be set + * @throws IllegalAccessException if unable to access field + */ + public static void setField(final Object object, final String fieldName, final Object fieldValue) + throws IllegalAccessException { + final Optional field = fieldOf(object.getClass(), fieldName); + + if (field.isPresent()) { + field.get().setAccessible(true); + field.get().set(object, fieldValue); + } + } + + /** + * Searches for a field in the given class by reflection + * + * @param classWithField - class type + * @param fieldName - name of field in class + * @return - field belonging to the given classWithField with the given fieldName + */ + public static Optional fieldOf(final Class classWithField, final String fieldName) { + return stream(classWithField.getDeclaredFields()) + .filter(f -> f.getName().equals(fieldName)) + .findFirst(); + } + + /** + * Get the value of a field from an Object + * + * @param object - object to find value from + * @param fieldName - name of field in class + * @param returnClassType - defines the return field value class type + * @param the class type to return + * @return the field value returned cast to the class type defined by the returnClassType + */ + public static Optional fieldValue(final Object object, + final String fieldName, + @SuppressWarnings("unused") final Class returnClassType) throws IllegalAccessException { + return fieldValue(object, fieldName) + .map(o -> (T) o); + } + + /** + * Get the value of a field from an Object + * + * @param object - object to find value from + * @param fieldName - name of field in class + * @return - field belonging to the given clazz with the given fieldName + */ + public static Optional fieldValue(final Object object, final String fieldName) throws IllegalAccessException { + final Optional field = fieldOf(object.getClass(), fieldName); + + if (field.isPresent()) { + field.get().setAccessible(true); + + return Optional.ofNullable(field.get().get(object)); + } + + return Optional.empty(); + } +} diff --git a/test-utils-core/src/test/java/uk/gov/justice/services/test/utils/core/reflection/ReflectionUtilTest.java b/test-utils-core/src/test/java/uk/gov/justice/services/test/utils/core/reflection/ReflectionUtilTest.java new file mode 100644 index 0000000..d190370 --- /dev/null +++ b/test-utils-core/src/test/java/uk/gov/justice/services/test/utils/core/reflection/ReflectionUtilTest.java @@ -0,0 +1,111 @@ +package uk.gov.justice.services.test.utils.core.reflection; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import static net.trajano.commons.testing.UtilityClassTestUtil.assertUtilityClassWellDefined; +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import java.lang.reflect.Method; +import java.util.List; +import java.util.Optional; + +import org.junit.Test; + +public class ReflectionUtilTest { + + @Test + public void shouldBeWellDefinedUtilityClass() { + assertUtilityClassWellDefined(ReflectionUtil.class); + } + + @Test + public void shouldReturnListOfMethods() throws Exception { + final List methods = ReflectionUtil.methodsOf(TestClass.class); + + assertThat(methods.size(), is(2)); + assertThat(methods.get(0).getName(), is("method1")); + assertThat(methods.get(1).getName(), is("method2")); + } + + @Test + public void shouldReturnTheFirstMethod() throws Exception { + final Optional method = ReflectionUtil.firstMethodOf(TestClass.class); + + assertThat(method.isPresent(), is(true)); + assertThat(method.get().getName(), is("method1")); + } + + @Test + public void shouldReturnTheNamedMethod() throws Exception { + final Optional method = ReflectionUtil.methodOf(TestClass.class, "method2"); + + assertThat(method.isPresent(), is(true)); + assertThat(method.get().getName(), is("method2")); + } + + @Test + public void shouldReturnAnnotatedMethod() throws Exception { + final Optional method = ReflectionUtil.annotatedMethod(AnnotatedTestClass.class, TestAnnotation.class); + + assertThat(method.isPresent(), is(true)); + assertThat(method.get().getName(), is("method2")); + } + + @Test + public void shouldReturnOptionalEmptyIfNoMethodWithAnnotation() throws Exception { + final Optional method = ReflectionUtil.annotatedMethod(TestClass.class, TestAnnotation.class); + + assertThat(method, is(Optional.empty())); + } + + @Test + public void shouldGetAndSetFieldValues() throws Exception { + final TestClass testClass = new TestClass(); + + final Optional fieldValue = ReflectionUtil.fieldValue(testClass, "field1"); + + assertThat(fieldValue.isPresent(), is(true)); + assertThat(fieldValue.get(), is(instanceOf(String.class))); + assertThat(fieldValue.get(), is("value1")); + + ReflectionUtil.setField(testClass, "field1", "new value"); + final Optional resultValue = ReflectionUtil.fieldValue(testClass, "field1", String.class); + + assertThat(resultValue.isPresent(), is(true)); + assertThat(resultValue.get(), is("new value")); + } + + @Documented + @Target(METHOD) + @Retention(RUNTIME) + @interface TestAnnotation { + } + + private class TestClass { + + private String field1 = "value1"; + + public void method1() { + } + + public boolean method2() { + return true; + } + } + + private class AnnotatedTestClass { + + public void method1() { + } + + @TestAnnotation + public boolean method2() { + return true; + } + } +} \ No newline at end of file