Skip to content

Commit 3579314

Browse files
committed
Fix for AnnotationIntrospector to support overridden methods.
Some utility methods from the Spring Framework project have been included to support this.
1 parent e290cac commit 3579314

2 files changed

Lines changed: 148 additions & 6 deletions

File tree

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/*
2+
* Copyright 2002-2014 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package unquietcode.tools.flapi;
17+
18+
import java.lang.reflect.Method;
19+
import java.util.ArrayList;
20+
import java.util.Arrays;
21+
import java.util.List;
22+
23+
/**
24+
* Method reflection methods copied from Spring Framework's ReflectionUtils class,
25+
* with the minimum modifications.
26+
*/
27+
public class SpringMethodUtils {
28+
29+
30+
/**
31+
* Perform the given callback operation on all matching methods of the given
32+
* class and superclasses.
33+
* <p>The same named method occurring on subclass and superclass will appear
34+
* twice, unless excluded by a {@link MethodFilter}.
35+
* @param clazz class to start looking at
36+
* @param mc the callback to invoke for each method
37+
* @see #doWithMethods(Class, MethodCallback, MethodFilter)
38+
*/
39+
public static void doWithMethods(Class<?> clazz, MethodCallback mc) throws IllegalArgumentException {
40+
doWithMethods(clazz, mc, null);
41+
}
42+
43+
/**
44+
* Perform the given callback operation on all matching methods of the given
45+
* class and superclasses (or given interface and super-interfaces).
46+
* <p>The same named method occurring on subclass and superclass will appear
47+
* twice, unless excluded by the specified {@link MethodFilter}.
48+
* @param clazz class to start looking at
49+
* @param mc the callback to invoke for each method
50+
* @param mf the filter that determines the methods to apply the callback to
51+
*/
52+
public static void doWithMethods(Class<?> clazz, MethodCallback mc, MethodFilter mf)
53+
throws IllegalArgumentException {
54+
55+
// Keep backing up the inheritance hierarchy.
56+
Method[] methods = clazz.getDeclaredMethods();
57+
for (Method method : methods) {
58+
if (mf != null && !mf.matches(method)) {
59+
continue;
60+
}
61+
try {
62+
mc.doWith(method);
63+
}
64+
catch (IllegalAccessException ex) {
65+
throw new IllegalStateException("Shouldn't be illegal to access method '" + method.getName() + "': " + ex);
66+
}
67+
}
68+
if (clazz.getSuperclass() != null) {
69+
doWithMethods(clazz.getSuperclass(), mc, mf);
70+
}
71+
else if (clazz.isInterface()) {
72+
for (Class<?> superIfc : clazz.getInterfaces()) {
73+
doWithMethods(superIfc, mc, mf);
74+
}
75+
}
76+
}
77+
/**
78+
* Get the unique set of declared methods on the leaf class and all superclasses. Leaf
79+
* class methods are included first and while traversing the superclass hierarchy any methods found
80+
* with signatures matching a method already included are filtered out.
81+
*/
82+
public static Method[] getUniqueDeclaredMethods(Class<?> leafClass) throws IllegalArgumentException {
83+
final List<Method> methods = new ArrayList<Method>(32);
84+
doWithMethods(leafClass, new MethodCallback() {
85+
@Override
86+
public void doWith(Method method) {
87+
boolean knownSignature = false;
88+
Method methodBeingOverriddenWithCovariantReturnType = null;
89+
for (Method existingMethod : methods) {
90+
if (method.getName().equals(existingMethod.getName()) &&
91+
Arrays.equals(method.getParameterTypes(), existingMethod.getParameterTypes())) {
92+
// Is this a covariant return type situation?
93+
if (existingMethod.getReturnType() != method.getReturnType() &&
94+
existingMethod.getReturnType().isAssignableFrom(method.getReturnType())) {
95+
methodBeingOverriddenWithCovariantReturnType = existingMethod;
96+
}
97+
else {
98+
knownSignature = true;
99+
}
100+
break;
101+
}
102+
}
103+
if (methodBeingOverriddenWithCovariantReturnType != null) {
104+
methods.remove(methodBeingOverriddenWithCovariantReturnType);
105+
}
106+
if (!knownSignature) {
107+
methods.add(method);
108+
}
109+
}
110+
});
111+
return methods.toArray(new Method[methods.size()]);
112+
}
113+
114+
115+
/**
116+
* Action to take on each method.
117+
*/
118+
public interface MethodCallback {
119+
120+
/**
121+
* Perform an operation using the given method.
122+
* @param method the method to operate on
123+
*/
124+
void doWith(Method method) throws IllegalArgumentException, IllegalAccessException;
125+
}
126+
127+
128+
/**
129+
* Callback optionally used to filter methods to be operated on by a method callback.
130+
*/
131+
public interface MethodFilter {
132+
133+
/**
134+
* Determine whether the given method matches.
135+
* @param method the method to check
136+
*/
137+
boolean matches(Method method);
138+
}
139+
}

src/main/java/unquietcode/tools/flapi/annotations/AnnotationIntrospector.java

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import unquietcode.tools.flapi.Constants;
2222
import unquietcode.tools.flapi.DescriptorBuilderException;
2323
import unquietcode.tools.flapi.IntrospectorSupport;
24+
import unquietcode.tools.flapi.SpringMethodUtils;
2425
import unquietcode.tools.flapi.beans.BeanIntrospector;
2526
import unquietcode.tools.flapi.helpers.AnnotationsHelperImpl;
2627
import unquietcode.tools.flapi.helpers.DocumentationHelperImpl;
@@ -32,10 +33,7 @@
3233
import java.lang.annotation.Annotation;
3334
import java.lang.reflect.AnnotatedElement;
3435
import java.lang.reflect.Method;
35-
import java.util.ArrayList;
36-
import java.util.HashMap;
37-
import java.util.List;
38-
import java.util.Map;
36+
import java.util.*;
3937
import java.util.concurrent.atomic.AtomicReference;
4038

4139
/**
@@ -49,7 +47,7 @@ public class AnnotationIntrospector extends IntrospectorSupport {
4947
public static boolean isAnnotated(Class<?> clazz) {
5048

5149
// for every method in the class
52-
for (Method method : clazz.getDeclaredMethods()) {
50+
for (Method method : getAllMethods(clazz)) {
5351
List<Annotation> methodQuantifiers = findAnnotatedElements(MethodQuantifier.class, method.getAnnotations());
5452

5553
// if the method has at least one marker
@@ -65,6 +63,11 @@ public static boolean isAnnotated(Class<?> clazz) {
6563
return false;
6664
}
6765

66+
private static Set<Method> getAllMethods(Class<?> clazz) {
67+
Method[] methods = SpringMethodUtils.getUniqueDeclaredMethods(clazz);
68+
return new HashSet<Method>(Arrays.asList(methods));
69+
}
70+
6871
public DescriptorOutline createDescriptor(Class<?> clazz) {
6972
DescriptorOutline descriptor = new DescriptorOutline();
7073
descriptor.setPackageName(clazz.getPackage().getName() + ".builder");
@@ -107,7 +110,7 @@ private boolean handleClass(BlockOutline blockOutline, Class<?> blockClass) {
107110

108111
boolean atLeastOne = false;
109112

110-
for (Method method : blockClass.getDeclaredMethods()) {
113+
for (Method method : getAllMethods(blockClass)) {
111114
List<Annotation> methodQuantifiers = findAnnotatedElements(MethodQuantifier.class, method.getAnnotations());
112115

113116
// skip methods which are not annotated

0 commit comments

Comments
 (0)