Skip to content

Commit

Permalink
LANG-1317: Adds MethodUtils#findAnnotation and extend MethodUtils#get…
Browse files Browse the repository at this point in the history
…MethodsWithAnnotation for non-public, super-class and interface methods (closes #261)
  • Loading branch information
yasserzamani authored and PascalSchumacher committed Apr 21, 2017
1 parent 859224f commit 46007c1
Show file tree
Hide file tree
Showing 5 changed files with 310 additions and 8 deletions.
158 changes: 150 additions & 8 deletions src/main/java/org/apache/commons/lang3/reflect/MethodUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -830,7 +830,7 @@ public static Set<Method> getOverrideHierarchy(final Method method, final Interf
}

/**
* Gets all methods of the given class that are annotated with the given annotation.
* Gets all class level public methods of the given class that are annotated with the given annotation.
* @param cls
* the {@link Class} to query
* @param annotationCls
Expand All @@ -841,12 +841,11 @@ public static Set<Method> getOverrideHierarchy(final Method method, final Interf
* @since 3.4
*/
public static Method[] getMethodsWithAnnotation(final Class<?> cls, final Class<? extends Annotation> annotationCls) {
final List<Method> annotatedMethodsList = getMethodsListWithAnnotation(cls, annotationCls);
return annotatedMethodsList.toArray(new Method[annotatedMethodsList.size()]);
return getMethodsWithAnnotation(cls, annotationCls, false, false);
}

/**
* Gets all methods of the given class that are annotated with the given annotation.
* Gets all class level public methods of the given class that are annotated with the given annotation.
* @param cls
* the {@link Class} to query
* @param annotationCls
Expand All @@ -857,16 +856,159 @@ public static Method[] getMethodsWithAnnotation(final Class<?> cls, final Class<
* @since 3.4
*/
public static List<Method> getMethodsListWithAnnotation(final Class<?> cls, final Class<? extends Annotation> annotationCls) {
return getMethodsListWithAnnotation(cls, annotationCls, false, false);
}

/**
* Gets all methods of the given class that are annotated with the given annotation.
* @param cls
* the {@link Class} to query
* @param annotationCls
* the {@link java.lang.annotation.Annotation} that must be present on a method to be matched
* @param searchSupers
* determines if also a lookup in the entire inheritance hierarchy of the given class should be performed
* @param ignoreAccess
* determines if also non public methods should be considered
* @return an array of Methods (possibly empty).
* @throws IllegalArgumentException
* if the class or annotation are {@code null}
* @since 3.6
*/
public static Method[] getMethodsWithAnnotation(final Class<?> cls, final Class<? extends Annotation> annotationCls,
boolean searchSupers, boolean ignoreAccess) {
final List<Method> annotatedMethodsList = getMethodsListWithAnnotation(cls, annotationCls, searchSupers,
ignoreAccess);
return annotatedMethodsList.toArray(new Method[annotatedMethodsList.size()]);
}

/**
* Gets all methods of the given class that are annotated with the given annotation.
* @param cls
* the {@link Class} to query
* @param annotationCls
* the {@link Annotation} that must be present on a method to be matched
* @param searchSupers
* determines if also a lookup in the entire inheritance hierarchy of the given class should be performed
* @param ignoreAccess
* determines if also non public methods should be considered
* @return a list of Methods (possibly empty).
* @throws IllegalArgumentException
* if the class or annotation are {@code null}
* @since 3.6
*/
public static List<Method> getMethodsListWithAnnotation(final Class<?> cls,
final Class<? extends Annotation> annotationCls,
boolean searchSupers, boolean ignoreAccess) {

Validate.isTrue(cls != null, "The class must not be null");
Validate.isTrue(annotationCls != null, "The annotation class must not be null");
final Method[] allMethods = cls.getMethods();
List<Class<?>> classes = (searchSupers ? getAllSuperclassesAndInterfaces(cls)
: new ArrayList<Class<?>>());
classes.add(0, cls);
final List<Method> annotatedMethods = new ArrayList<>();
for (final Method method : allMethods) {
if (method.getAnnotation(annotationCls) != null) {
annotatedMethods.add(method);
for (Class<?> acls : classes) {
final Method[] methods = (ignoreAccess ? acls.getDeclaredMethods() : acls.getMethods());
for (final Method method : methods) {
if (method.getAnnotation(annotationCls) != null) {
annotatedMethods.add(method);
}
}
}
return annotatedMethods;
}

/**
* <p>Gets the annotation object that is present on the given method or any equivalent method in
* super classes and interfaces, with the given annotation type. Returns null if the annotation
* type was not present on any of them.</p>
*
* <p>Stops searching for an annotation once the first annotation of the specified type has been
* found. i.e, additional annotations of the specified type will be silently ignored.</p>
* @param <A>
* the annotation type
* @param method
* the {@link Method} to query
* @param annotationCls
* the {@link Annotation} to check if is present on the method
* @param searchSupers
* determines if lookup in the entire inheritance hierarchy of the given class if was not directly present
* @param ignoreAccess
* determines if underlying method has to be accessible
* @return the first matching annotation, or {@code null} if not found
* @throws IllegalArgumentException
* if the method or annotation are {@code null}
* @since 3.6
*/
public static <A extends Annotation> A getAnnotation(final Method method, final Class<A> annotationCls,
boolean searchSupers, boolean ignoreAccess) {

Validate.isTrue(method != null, "The method must not be null");
Validate.isTrue(annotationCls != null, "The annotation class must not be null");
if(!ignoreAccess && !MemberUtils.isAccessible(method)) {
return null;
}

A annotation = method.getAnnotation(annotationCls);

if(annotation == null && searchSupers) {
Class<?> mcls = method.getDeclaringClass();
List<Class<?>> classes = getAllSuperclassesAndInterfaces(mcls);
for (Class<?> acls : classes) {
Method equivalentMethod;
try {
equivalentMethod = (ignoreAccess ? acls.getDeclaredMethod(method.getName(), method.getParameterTypes())
: acls.getMethod(method.getName(), method.getParameterTypes()));
} catch (NoSuchMethodException e) {
// If not found, just keep on search
continue;
}
annotation = equivalentMethod.getAnnotation(annotationCls);
if (annotation != null) {
break;
}
}
}

return annotation;
}

/**
* <p>Gets a combination of {@link ClassUtils#getAllSuperclasses}(Class)} and
* {@link ClassUtils#getAllInterfaces}(Class)}, one from superclasses, one
* from interfaces, and so on in a breadth first way.</p>
*
* @param cls the class to look up, may be {@code null}
* @return the combined {@code List} of superclasses and interfaces in order
* going up from this one
* {@code null} if null input
* @since 3.6
*/
private static List<Class<?>> getAllSuperclassesAndInterfaces(final Class<?> cls) {
if (cls == null) {
return null;
}

final List<Class<?>> classes = new ArrayList<>();
List<Class<?>> allSuperclasses = ClassUtils.getAllSuperclasses(cls);
int sci = 0;
List<Class<?>> allInterfaces = ClassUtils.getAllInterfaces(cls);
int ifi = 0;
while (ifi < allInterfaces.size() ||
sci < allSuperclasses.size()) {
Class<?> acls;
if (ifi >= allInterfaces.size()) {
acls = allSuperclasses.get(sci++);
} else if (sci >= allSuperclasses.size()) {
acls = allInterfaces.get(ifi++);
} else if (ifi < sci) {
acls = allInterfaces.get(ifi++);
} else if (sci < ifi) {
acls = allSuperclasses.get(sci++);
} else {
acls = allInterfaces.get(ifi++);
}
classes.add(acls);
}
return classes;
}
}
136 changes: 136 additions & 0 deletions src/test/java/org/apache/commons/lang3/reflect/MethodUtilsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
import org.apache.commons.lang3.reflect.testbed.Annotated;
import org.apache.commons.lang3.reflect.testbed.GenericConsumer;
import org.apache.commons.lang3.reflect.testbed.GenericParent;
import org.apache.commons.lang3.reflect.testbed.PublicChild;
import org.apache.commons.lang3.reflect.testbed.StringParameterizedChild;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.junit.Assert;
Expand Down Expand Up @@ -681,6 +682,125 @@ public void testGetMethodsWithAnnotation() throws NoSuchMethodException {
assertThat(methodsWithAnnotation, hasItemInArray(MethodUtilsTest.class.getMethod("testGetMethodsListWithAnnotation")));
}

@Test
public void testGetMethodsWithAnnotationSearchSupersAndIgnoreAccess() throws NoSuchMethodException {
assertArrayEquals(new Method[0], MethodUtils.getMethodsWithAnnotation(Object.class, Annotated.class,
true, true));

final Method[] methodsWithAnnotation = MethodUtils.getMethodsWithAnnotation(PublicChild.class, Annotated.class,
true, true);
assertEquals(4, methodsWithAnnotation.length);
assertEquals("PublicChild", methodsWithAnnotation[0].getDeclaringClass().getSimpleName());
assertEquals("PublicChild", methodsWithAnnotation[1].getDeclaringClass().getSimpleName());
assertTrue(methodsWithAnnotation[0].getName().endsWith("AnnotatedMethod"));
assertTrue(methodsWithAnnotation[1].getName().endsWith("AnnotatedMethod"));
assertEquals("Foo.doIt",
methodsWithAnnotation[2].getDeclaringClass().getSimpleName() + '.' +
methodsWithAnnotation[2].getName());
assertEquals("Parent.parentProtectedAnnotatedMethod",
methodsWithAnnotation[3].getDeclaringClass().getSimpleName() + '.' +
methodsWithAnnotation[3].getName());
}

@Test
public void testGetMethodsWithAnnotationNotSearchSupersButIgnoreAccess() throws NoSuchMethodException {
assertArrayEquals(new Method[0], MethodUtils.getMethodsWithAnnotation(Object.class, Annotated.class,
false, true));

final Method[] methodsWithAnnotation = MethodUtils.getMethodsWithAnnotation(PublicChild.class, Annotated.class,
false, true);
assertEquals(2, methodsWithAnnotation.length);
assertEquals("PublicChild", methodsWithAnnotation[0].getDeclaringClass().getSimpleName());
assertEquals("PublicChild", methodsWithAnnotation[1].getDeclaringClass().getSimpleName());
assertTrue(methodsWithAnnotation[0].getName().endsWith("AnnotatedMethod"));
assertTrue(methodsWithAnnotation[1].getName().endsWith("AnnotatedMethod"));
}

@Test
public void testGetMethodsWithAnnotationSearchSupersButNotIgnoreAccess() throws NoSuchMethodException {
assertArrayEquals(new Method[0], MethodUtils.getMethodsWithAnnotation(Object.class, Annotated.class,
true, false));

final Method[] methodsWithAnnotation = MethodUtils.getMethodsWithAnnotation(PublicChild.class, Annotated.class,
true, false);
assertEquals(2, methodsWithAnnotation.length);
assertEquals("PublicChild.publicAnnotatedMethod",
methodsWithAnnotation[0].getDeclaringClass().getSimpleName() + '.' +
methodsWithAnnotation[0].getName());
assertEquals("Foo.doIt",
methodsWithAnnotation[1].getDeclaringClass().getSimpleName() + '.' +
methodsWithAnnotation[1].getName());
}

@Test
public void testGetMethodsWithAnnotationNotSearchSupersAndNotIgnoreAccess() throws NoSuchMethodException {
assertArrayEquals(new Method[0], MethodUtils.getMethodsWithAnnotation(Object.class, Annotated.class,
false, false));

final Method[] methodsWithAnnotation = MethodUtils.getMethodsWithAnnotation(PublicChild.class, Annotated.class,
false, false);
assertEquals(1, methodsWithAnnotation.length);
assertEquals("PublicChild.publicAnnotatedMethod",
methodsWithAnnotation[0].getDeclaringClass().getSimpleName() + '.' +
methodsWithAnnotation[0].getName());
}

@Test
public void testGetAnnotationSearchSupersAndIgnoreAccess() throws NoSuchMethodException {
assertNull(MethodUtils.getAnnotation(PublicChild.class.getMethod("parentNotAnnotatedMethod"),
Annotated.class, true, true));
assertNotNull(MethodUtils.getAnnotation(PublicChild.class.getMethod("doIt"), Annotated.class,
true, true));
assertNotNull(MethodUtils.getAnnotation(PublicChild.class.getMethod("parentProtectedAnnotatedMethod"),
Annotated.class, true, true));
assertNotNull(MethodUtils.getAnnotation(PublicChild.class.getDeclaredMethod("privateAnnotatedMethod"),
Annotated.class, true, true));
assertNotNull(MethodUtils.getAnnotation(PublicChild.class.getMethod("publicAnnotatedMethod"),
Annotated.class, true, true));
}

@Test
public void testGetAnnotationNotSearchSupersButIgnoreAccess() throws NoSuchMethodException {
assertNull(MethodUtils.getAnnotation(PublicChild.class.getMethod("parentNotAnnotatedMethod"),
Annotated.class, false, true));
assertNull(MethodUtils.getAnnotation(PublicChild.class.getMethod("doIt"), Annotated.class,
false, true));
assertNull(MethodUtils.getAnnotation(PublicChild.class.getMethod("parentProtectedAnnotatedMethod"),
Annotated.class, false, true));
assertNotNull(MethodUtils.getAnnotation(PublicChild.class.getDeclaredMethod("privateAnnotatedMethod"),
Annotated.class, false, true));
assertNotNull(MethodUtils.getAnnotation(PublicChild.class.getMethod("publicAnnotatedMethod"),
Annotated.class, false, true));
}

@Test
public void testGetAnnotationSearchSupersButNotIgnoreAccess() throws NoSuchMethodException {
assertNull(MethodUtils.getAnnotation(PublicChild.class.getMethod("parentNotAnnotatedMethod"),
Annotated.class, true, false));
assertNull(MethodUtils.getAnnotation(PublicChild.class.getMethod("doIt"), Annotated.class,
true, false));
assertNull(MethodUtils.getAnnotation(PublicChild.class.getMethod("parentProtectedAnnotatedMethod"),
Annotated.class, true, false));
assertNull(MethodUtils.getAnnotation(PublicChild.class.getDeclaredMethod("privateAnnotatedMethod"),
Annotated.class, true, false));
assertNotNull(MethodUtils.getAnnotation(PublicChild.class.getMethod("publicAnnotatedMethod"),
Annotated.class, true, false));
}

@Test
public void testGetAnnotationNotSearchSupersAndNotIgnoreAccess() throws NoSuchMethodException {
assertNull(MethodUtils.getAnnotation(PublicChild.class.getMethod("parentNotAnnotatedMethod"),
Annotated.class, false, false));
assertNull(MethodUtils.getAnnotation(PublicChild.class.getMethod("doIt"), Annotated.class,
false, false));
assertNull(MethodUtils.getAnnotation(PublicChild.class.getMethod("parentProtectedAnnotatedMethod"),
Annotated.class, false, false));
assertNull(MethodUtils.getAnnotation(PublicChild.class.getDeclaredMethod("privateAnnotatedMethod"),
Annotated.class, false, false));
assertNotNull(MethodUtils.getAnnotation(PublicChild.class.getMethod("publicAnnotatedMethod"),
Annotated.class, false, false));
}

@Test(expected = IllegalArgumentException.class)
public void testGetMethodsWithAnnotationIllegalArgumentException1() {
MethodUtils.getMethodsWithAnnotation(FieldUtilsTest.class, null);
Expand Down Expand Up @@ -724,6 +844,22 @@ public void testGetMethodsListWithAnnotationIllegalArgumentException3() {
MethodUtils.getMethodsListWithAnnotation(null, null);
}

@Test(expected = IllegalArgumentException.class)
public void testGetAnnotationIllegalArgumentException1() {
MethodUtils.getAnnotation(FieldUtilsTest.class.getDeclaredMethods()[0], null, true,
true);
}

@Test(expected = IllegalArgumentException.class)
public void testGetAnnotationIllegalArgumentException2() {
MethodUtils.getAnnotation(null, Annotated.class, true, true);
}

@Test(expected = IllegalArgumentException.class)
public void testGetAnnotationIllegalArgumentException3() {
MethodUtils.getAnnotation(null, null, true, true);
}

private void expectMatchingAccessibleMethodParameterTypes(final Class<?> cls,
final String methodName, final Class<?>[] requestTypes, final Class<?>[] actualTypes) {
final Method m = MethodUtils.getMatchingAccessibleMethod(cls, methodName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,6 @@
public interface Foo {
public static final String VALUE = "foo";

@Annotated
void doIt();
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,11 @@ class Parent implements Foo {
@Override
public void doIt() {
}

@Annotated
protected void parentProtectedAnnotatedMethod() {
}

public void parentNotAnnotatedMethod() {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,20 @@
*/
public class PublicChild extends Parent {
static final String VALUE = "child";

@Override
public void parentProtectedAnnotatedMethod() {
}

@Override
public void parentNotAnnotatedMethod() {
}

@Annotated
private void privateAnnotatedMethod() {
}

@Annotated
public void publicAnnotatedMethod() {
}
}

0 comments on commit 46007c1

Please sign in to comment.