Skip to content

Commit 2e6118c

Browse files
chonton-elementumyrashk
authored andcommitted
Support inheritence of GraphQLField annotation (#61)
1 parent 653f805 commit 2e6118c

File tree

3 files changed

+103
-31
lines changed

3 files changed

+103
-31
lines changed

src/main/java/graphql/annotations/GraphQLAnnotations.java

Lines changed: 74 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636

3737
import javax.validation.constraints.NotNull;
3838
import java.lang.reflect.AccessibleObject;
39+
import java.lang.reflect.AnnotatedElement;
3940
import java.lang.reflect.AnnotatedType;
4041
import java.lang.reflect.Constructor;
4142
import java.lang.reflect.Field;
@@ -46,6 +47,7 @@
4647
import java.util.Collections;
4748
import java.util.Comparator;
4849
import java.util.HashMap;
50+
import java.util.LinkedList;
4951
import java.util.List;
5052
import java.util.Map;
5153
import java.util.Optional;
@@ -172,26 +174,76 @@ public static GraphQLInterfaceType.Builder ifaceBuilder(Class<?> iface) throws G
172174
return getInstance().getIfaceBuilder(iface);
173175
}
174176

175-
private Class<?> getDeclaringClass(Method method) {
176-
Class<?> object = method.getDeclaringClass();
177-
Class<?> declaringClass = object;
178-
for (Class<?> iface : object.getInterfaces()) {
177+
private static Boolean isGraphQLField(AnnotatedElement element) {
178+
GraphQLField annotation = element.getAnnotation(GraphQLField.class);
179+
if (annotation == null) {
180+
return null;
181+
}
182+
return annotation.value();
183+
}
184+
185+
/**
186+
* breadthFirst parental ascent looking for closest method declaration with explicit annotation
187+
*
188+
* @param method The method to match
189+
* @return The closest GraphQLField annotation
190+
*/
191+
private boolean breadthFirstSearch(Method method) {
192+
final List<Class<?>> queue = new LinkedList<>();
193+
final String methodName = method.getName();
194+
final Class<?>[] parameterTypes = method.getParameterTypes();
195+
queue.add(method.getDeclaringClass());
196+
do {
197+
Class<?> cls = queue.remove(0);
198+
179199
try {
180-
iface.getMethod(method.getName(), method.getParameterTypes());
181-
declaringClass = iface;
200+
method = cls.getDeclaredMethod(methodName, parameterTypes);
201+
Boolean gqf = isGraphQLField(method);
202+
if (gqf != null) {
203+
return gqf;
204+
}
182205
} catch (NoSuchMethodException e) {
183206
}
184-
}
185207

186-
try {
187-
if (object.getSuperclass() != null) {
188-
object.getSuperclass().getMethod(method.getName(), method.getParameterTypes());
189-
declaringClass = object.getSuperclass();
208+
Boolean gqf = isGraphQLField(cls);
209+
if (gqf != null) {
210+
return gqf;
190211
}
191-
} catch (NoSuchMethodException e) {
192-
}
193-
return declaringClass;
194212

213+
// add interfaces to places to search
214+
for (Class<?> iface : cls.getInterfaces()) {
215+
queue.add(iface);
216+
}
217+
// add parent class to places to search
218+
Class<?> nxt = cls.getSuperclass();
219+
if (nxt != null) {
220+
queue.add(nxt);
221+
}
222+
} while (!queue.isEmpty());
223+
return false;
224+
}
225+
226+
/**
227+
* direct parental ascent looking for closest declaration with explicit annotation
228+
*
229+
* @param field The field to find
230+
* @return The closest GraphQLField annotation
231+
*/
232+
private boolean parentalSearch(Field field) {
233+
Boolean gqf = isGraphQLField(field);
234+
if (gqf != null) {
235+
return gqf;
236+
}
237+
Class<?> cls = field.getDeclaringClass();
238+
239+
do {
240+
gqf = isGraphQLField(cls);
241+
if (gqf != null) {
242+
return gqf;
243+
}
244+
cls = cls.getSuperclass();
245+
} while (cls != null);
246+
return false;
195247
}
196248

197249
@Override
@@ -228,28 +280,21 @@ public GraphQLObjectType.Builder getObjectBuilder(Class<?> object) throws GraphQ
228280
if (description != null) {
229281
builder.description(description.value());
230282
}
231-
for (Method method : getOrderedMethods(object)) {
232283

233-
Class<?> declaringClass = getDeclaringClass(method);
234-
235-
boolean valid = false;
236-
try {
237-
valid = (method.getAnnotation(GraphQLField.class) != null ||
238-
declaringClass.getMethod(method.getName(), method.getParameterTypes()).getAnnotation(GraphQLField.class) != null)
239-
&& !method.isBridge();
240-
} catch (NoSuchMethodException e) {
241-
throw new GraphQLAnnotationsException("Unable to introspect method : " + method, e);
284+
for (Method method : getOrderedMethods(object)) {
285+
if(method.isBridge() || method.isSynthetic()) {
286+
continue;
242287
}
243-
244-
if (valid) {
288+
if (breadthFirstSearch(method)) {
245289
builder.field(getField(method));
246290
}
247291
}
248292

249293
for (Field field : getAllFields(object).values()) {
250-
boolean valid = !Modifier.isStatic(field.getModifiers()) &&
251-
field.getAnnotation(GraphQLField.class) != null;
252-
if (valid) {
294+
if(Modifier.isStatic(field.getModifiers())) {
295+
continue;
296+
}
297+
if (parentalSearch(field)) {
253298
builder.field(getField(field));
254299
}
255300
}

src/main/java/graphql/annotations/GraphQLField.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919
import java.lang.annotation.RetentionPolicy;
2020
import java.lang.annotation.Target;
2121

22-
@Target({ElementType.METHOD, ElementType.FIELD})
22+
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE})
2323
@Retention(RetentionPolicy.RUNTIME)
2424
public @interface GraphQLField {
25+
boolean value() default true;
2526
}

src/test/java/graphql/annotations/GraphQLObjectTest.java

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
import static graphql.schema.GraphQLFieldDefinition.newFieldDefinition;
4747
import static graphql.schema.GraphQLSchema.newSchema;
4848
import static org.testng.Assert.assertEquals;
49+
import static org.testng.Assert.assertNotNull;
4950
import static org.testng.Assert.assertNull;
5051
import static org.testng.Assert.assertTrue;
5152

@@ -684,5 +685,30 @@ public void parametrizedArg() {
684685
assertEquals(((GraphQLList) t).getWrappedType(), Scalars.GraphQLString);
685686
}
686687

688+
@GraphQLField
689+
public static class InheritGraphQLFieldTest {
690+
public String inheritedOn;
687691

688-
}
692+
@GraphQLField(false)
693+
public String forcedOff;
694+
695+
public String getOn() {
696+
return "on";
697+
}
698+
699+
@GraphQLField(false)
700+
public String getOff() {
701+
return "off";
702+
}
703+
}
704+
705+
@Test
706+
public void inheritGraphQLField() {
707+
GraphQLObjectType object = GraphQLAnnotations.object(InheritGraphQLFieldTest.class);
708+
assertNotNull(object.getFieldDefinition("on"));
709+
assertNull(object.getFieldDefinition("off"));
710+
assertNotNull(object.getFieldDefinition("inheritedOn"));
711+
assertNull(object.getFieldDefinition("forcedOff"));
712+
}
713+
714+
}

0 commit comments

Comments
 (0)