Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LANG-1317 Adds MethodUtils#findAnnotation and extend MethodUtils#getMethodsWithAnnotation for non-public, super-class and interface methods #261

Closed
wants to merge 6 commits into from
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 @@ -832,7 +832,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 @@ -843,12 +843,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 @@ -859,16 +858,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() {
}
}