Repository: struts Updated Branches: refs/heads/master 4b96958be -> 534dfc6bd
[WW-4694] annotation processing improved in order to navigate around proxies, superclasses and interfaces Project: http://git-wip-us.apache.org/repos/asf/struts/repo Commit: http://git-wip-us.apache.org/repos/asf/struts/commit/c84b7967 Tree: http://git-wip-us.apache.org/repos/asf/struts/tree/c84b7967 Diff: http://git-wip-us.apache.org/repos/asf/struts/diff/c84b7967 Branch: refs/heads/master Commit: c84b7967e9eb2f6307466b1644977423831d2ef1 Parents: 0023d96 Author: Yasser Zamani <yasser.zam...@live.com> Authored: Sun Feb 5 17:54:28 2017 +0330 Committer: Yasser Zamani <yasser.zam...@live.com> Committed: Sun Feb 5 17:54:28 2017 +0330 ---------------------------------------------------------------------- .../xwork2/util/AnnotationUtils.java | 111 ++++++++++++++++--- .../xwork2/util/AnnotationUtilsTest.java | 35 ++++-- .../xwork2/util/annotation/DummyClass.java | 5 +- .../xwork2/util/annotation/DummyClassExt.java | 5 +- .../xwork2/util/annotation/DummyInterface.java | 7 ++ .../xwork2/util/annotation/MyAnnotationI.java | 8 ++ 6 files changed, 145 insertions(+), 26 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/struts/blob/c84b7967/core/src/main/java/com/opensymphony/xwork2/util/AnnotationUtils.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/util/AnnotationUtils.java b/core/src/main/java/com/opensymphony/xwork2/util/AnnotationUtils.java index ef0ee53..1c37918 100644 --- a/core/src/main/java/com/opensymphony/xwork2/util/AnnotationUtils.java +++ b/core/src/main/java/com/opensymphony/xwork2/util/AnnotationUtils.java @@ -122,7 +122,14 @@ public class AnnotationUtils { Collection<Method> toReturn = new HashSet<>(); for (Method m : clazz.getMethods()) { - if (ArrayUtils.isNotEmpty(annotation) && isAnnotatedBy(m, annotation)) { + boolean found = false; + for( Class<? extends Annotation> c : annotation ){ + if( null != findAnnotation(m, c) ){ + found = true; + break; + } + } + if (found) { toReturn.add(m); } else if (ArrayUtils.isEmpty(annotation) && ArrayUtils.isNotEmpty(m.getAnnotations())) { toReturn.add(m); @@ -133,22 +140,100 @@ public class AnnotationUtils { } /** - * Varargs version of <code>AnnotatedElement.isAnnotationPresent()</code> - * @param annotatedElement element to check - * @param annotation the {@link Annotation}s to find - * @return true is element is annotated by one of the annotation - * @see AnnotatedElement + * Find a single {@link Annotation} of {@code annotationType} from the supplied + * {@link Method}, traversing its super methods (i.e., from superclasses and + * interfaces) if no annotation can be found on the given method itself. + * <p>Annotations on methods are not inherited by default, so we need to handle + * this explicitly. + * @param method the method to look for annotations on + * @param annotationType the annotation type to look for + * @return the annotation found, or {@code null} if none */ - public static boolean isAnnotatedBy(AnnotatedElement annotatedElement, Class<? extends Annotation>... annotation) { - if (ArrayUtils.isEmpty(annotation)) { - return false; - } + public static <A extends Annotation> A findAnnotation(Method method, Class<A> annotationType) { + A result = getAnnotation(method, annotationType); + Class<?> clazz = method.getDeclaringClass(); + if (result == null) { + result = searchOnInterfaces(method, annotationType, clazz.getInterfaces()); + } + while (result == null) { + clazz = clazz.getSuperclass(); + if (clazz == null || clazz.equals(Object.class)) { + break; + } + try { + Method equivalentMethod = clazz.getDeclaredMethod(method.getName(), method.getParameterTypes()); + result = getAnnotation(equivalentMethod, annotationType); + } + catch (NoSuchMethodException ex) { + // No equivalent method found + } + if (result == null) { + result = searchOnInterfaces(method, annotationType, clazz.getInterfaces()); + } + } + return result; + } + + /** + * Get a single {@link Annotation} of {@code annotationType} from the supplied + * Method, Constructor or Field. Meta-annotations will be searched if the annotation + * is not declared locally on the supplied element. + * @param annotatedElement the Method, Constructor or Field from which to get the annotation + * @param annotationType the annotation type to look for, both locally and as a meta-annotation + * @return the matching annotation, or {@code null} if none found + */ + public static <T extends Annotation> T getAnnotation(AnnotatedElement annotatedElement, Class<T> annotationType) { + try { + T ann = annotatedElement.getAnnotation(annotationType); + if (ann == null) { + for (Annotation metaAnn : annotatedElement.getAnnotations()) { + ann = metaAnn.annotationType().getAnnotation(annotationType); + if (ann != null) { + break; + } + } + } + return ann; + } + catch (Exception ex) { + // Assuming nested Class values not resolvable within annotation attributes... + return null; + } + } - for( Class<? extends Annotation> c : annotation ){ - if( annotatedElement.isAnnotationPresent(c) ) return true; + private static <A extends Annotation> A searchOnInterfaces(Method method, Class<A> annotationType, Class<?>... ifcs) { + A annotation = null; + for (Class<?> iface : ifcs) { + if (isInterfaceWithAnnotatedMethods(iface)) { + try { + Method equivalentMethod = iface.getMethod(method.getName(), method.getParameterTypes()); + annotation = getAnnotation(equivalentMethod, annotationType); + } + catch (NoSuchMethodException ex) { + // Skip this interface - it doesn't have the method... + } + if (annotation != null) { + break; + } + } } + return annotation; + } - return false; + private static boolean isInterfaceWithAnnotatedMethods(Class<?> iface) { + boolean found = false; + for (Method ifcMethod : iface.getMethods()) { + try { + if (ifcMethod.getAnnotations().length > 0) { + found = true; + break; + } + } + catch (Exception ex) { + // Assuming nested Class values not resolvable within annotation attributes... + } + } + return found; } /** http://git-wip-us.apache.org/repos/asf/struts/blob/c84b7967/core/src/test/java/com/opensymphony/xwork2/util/AnnotationUtilsTest.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/com/opensymphony/xwork2/util/AnnotationUtilsTest.java b/core/src/test/java/com/opensymphony/xwork2/util/AnnotationUtilsTest.java index e59abe6..8ad4f35 100644 --- a/core/src/test/java/com/opensymphony/xwork2/util/AnnotationUtilsTest.java +++ b/core/src/test/java/com/opensymphony/xwork2/util/AnnotationUtilsTest.java @@ -5,8 +5,11 @@ import com.opensymphony.xwork2.util.annotation.DummyClass; import com.opensymphony.xwork2.util.annotation.DummyClassExt; import com.opensymphony.xwork2.util.annotation.MyAnnotation; import com.opensymphony.xwork2.util.annotation.MyAnnotation2; +import com.opensymphony.xwork2.util.annotation.MyAnnotationI; + import junit.framework.TestCase; +import java.lang.annotation.Retention; import java.lang.reflect.AnnotatedElement; import java.util.Collection; @@ -15,22 +18,32 @@ import java.util.Collection; */ public class AnnotationUtilsTest extends TestCase { - @SuppressWarnings("unchecked") - public void testIsAnnotatedByWithoutAnnotationArgsReturnsFalse() throws Exception { - assertFalse(AnnotationUtils.isAnnotatedBy(DummyClass.class)); - assertFalse(AnnotationUtils.isAnnotatedBy(DummyClass.class.getMethod("methodWithAnnotation"))); + public void testGetAnnotationMeta() throws Exception { + assertNotNull(AnnotationUtils.getAnnotation(DummyClass.class.getMethod("methodWithAnnotation"), Retention.class)); } - @SuppressWarnings("unchecked") - public void testIsAnnotatedByWithSingleAnnotationArgMatchingReturnsTrue() throws Exception { - assertTrue(AnnotationUtils.isAnnotatedBy(DummyClass.class.getMethod("methodWithAnnotation"), MyAnnotation.class)); + public void testGetAnnotation() throws Exception { + assertNull(AnnotationUtils.getAnnotation(DummyClass.class.getMethod("methodWithAnnotation"), Deprecated.class)); + assertNotNull(AnnotationUtils.getAnnotation(DummyClass.class.getMethod("methodWithAnnotation"), MyAnnotation.class)); + } + + public void testFindAnnotationFromSuperclass() throws Exception { + assertNotNull(AnnotationUtils.findAnnotation(DummyClassExt.class.getMethod("methodWithAnnotation"), MyAnnotation.class)); + } + + public void testFindAnnotationFromInterface() throws Exception { + assertNotNull(AnnotationUtils.findAnnotation(DummyClass.class.getMethod("interfaceMethodWithAnnotation"), MyAnnotationI.class)); + } + + public void testFindAnnotation() throws Exception { + assertNotNull(AnnotationUtils.findAnnotation(DummyClassExt.class.getMethod("anotherAnnotatedMethod"), MyAnnotation2.class)); } @SuppressWarnings("unchecked") - public void testIsAnnotatedByWithMultiAnnotationArgMatchingReturnsTrue() throws Exception { - assertFalse(AnnotationUtils.isAnnotatedBy(DummyClass.class.getMethod("methodWithAnnotation"), Deprecated.class)); - assertTrue(AnnotationUtils.isAnnotatedBy(DummyClass.class.getMethod("methodWithAnnotation"), MyAnnotation.class, Deprecated.class)); - assertTrue(AnnotationUtils.isAnnotatedBy(DummyClass.class.getMethod("methodWithAnnotation"), Deprecated.class, MyAnnotation.class)); + public void testGetAnnotatedMethodsIncludingSuperclassAndInterface() throws Exception { + + Collection<? extends AnnotatedElement> ans = AnnotationUtils.getAnnotatedMethods(DummyClassExt.class, Deprecated.class, MyAnnotation.class, MyAnnotation2.class, MyAnnotationI.class); + assertEquals(3, ans.size()); } @SuppressWarnings("unchecked") http://git-wip-us.apache.org/repos/asf/struts/blob/c84b7967/core/src/test/java/com/opensymphony/xwork2/util/annotation/DummyClass.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/com/opensymphony/xwork2/util/annotation/DummyClass.java b/core/src/test/java/com/opensymphony/xwork2/util/annotation/DummyClass.java index 22d6e07..da2f80e 100644 --- a/core/src/test/java/com/opensymphony/xwork2/util/annotation/DummyClass.java +++ b/core/src/test/java/com/opensymphony/xwork2/util/annotation/DummyClass.java @@ -1,7 +1,7 @@ package com.opensymphony.xwork2.util.annotation; @MyAnnotation("class-test") -public class DummyClass { +public class DummyClass implements DummyInterface { public DummyClass() { } @@ -10,4 +10,7 @@ public class DummyClass { public void methodWithAnnotation() { } + @Override + public void interfaceMethodWithAnnotation() { + } } http://git-wip-us.apache.org/repos/asf/struts/blob/c84b7967/core/src/test/java/com/opensymphony/xwork2/util/annotation/DummyClassExt.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/com/opensymphony/xwork2/util/annotation/DummyClassExt.java b/core/src/test/java/com/opensymphony/xwork2/util/annotation/DummyClassExt.java index cfeebdf..7c024f6 100644 --- a/core/src/test/java/com/opensymphony/xwork2/util/annotation/DummyClassExt.java +++ b/core/src/test/java/com/opensymphony/xwork2/util/annotation/DummyClassExt.java @@ -5,5 +5,8 @@ public final class DummyClassExt extends DummyClass { @MyAnnotation2 public void anotherAnnotatedMethod() { } - + + @Override + public void methodWithAnnotation() { + } } http://git-wip-us.apache.org/repos/asf/struts/blob/c84b7967/core/src/test/java/com/opensymphony/xwork2/util/annotation/DummyInterface.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/com/opensymphony/xwork2/util/annotation/DummyInterface.java b/core/src/test/java/com/opensymphony/xwork2/util/annotation/DummyInterface.java new file mode 100644 index 0000000..6faa3e4 --- /dev/null +++ b/core/src/test/java/com/opensymphony/xwork2/util/annotation/DummyInterface.java @@ -0,0 +1,7 @@ +package com.opensymphony.xwork2.util.annotation; + +public interface DummyInterface { + + @MyAnnotationI + public void interfaceMethodWithAnnotation(); +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/struts/blob/c84b7967/core/src/test/java/com/opensymphony/xwork2/util/annotation/MyAnnotationI.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/com/opensymphony/xwork2/util/annotation/MyAnnotationI.java b/core/src/test/java/com/opensymphony/xwork2/util/annotation/MyAnnotationI.java new file mode 100644 index 0000000..ea866b9 --- /dev/null +++ b/core/src/test/java/com/opensymphony/xwork2/util/annotation/MyAnnotationI.java @@ -0,0 +1,8 @@ +package com.opensymphony.xwork2.util.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface MyAnnotationI { +}