diff --git a/src/main/java/graphql/annotations/processor/typeBuilders/OutputObjectBuilder.java b/src/main/java/graphql/annotations/processor/typeBuilders/OutputObjectBuilder.java index 44c04d90..979778e2 100644 --- a/src/main/java/graphql/annotations/processor/typeBuilders/OutputObjectBuilder.java +++ b/src/main/java/graphql/annotations/processor/typeBuilders/OutputObjectBuilder.java @@ -23,6 +23,7 @@ import graphql.annotations.processor.retrievers.GraphQLInterfaceRetriever; import graphql.annotations.processor.retrievers.GraphQLObjectInfoRetriever; import graphql.annotations.processor.searchAlgorithms.SearchAlgorithm; +import graphql.annotations.processor.util.ClassUtils; import graphql.schema.GraphQLFieldDefinition; import graphql.schema.GraphQLInterfaceType; import graphql.schema.GraphQLObjectType; @@ -95,7 +96,7 @@ public GraphQLObjectType.Builder getOutputObjectBuilder(Class object, Process } } - for (Class iface : object.getInterfaces()) { + for (Class iface : ClassUtils.getAllInterfaces(object)) { if (iface.getAnnotation(GraphQLTypeResolver.class) != null) { String ifaceName = graphQLObjectInfoRetriever.getTypeName(iface); if (container.getProcessing().contains(ifaceName)) { diff --git a/src/main/java/graphql/annotations/processor/util/ClassUtils.java b/src/main/java/graphql/annotations/processor/util/ClassUtils.java new file mode 100644 index 00000000..b293aa31 --- /dev/null +++ b/src/main/java/graphql/annotations/processor/util/ClassUtils.java @@ -0,0 +1,95 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + */ + +package graphql.annotations.processor.util; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +/** + * Operates on classes without using reflection. + * + *

+ * This class handles invalid {@code null} inputs as best it can. Each method documents its behavior in more detail. + *

+ * + *

+ * The notion of a {@code canonical name} includes the human readable name for the type, for example {@code int[]}. The + * non-canonical method variants work with the JVM names, such as {@code [I}. + *

+ * + *

+ * This class and the functions contained within are from the Apache commons-lang project. + *

+ * + */ +public class ClassUtils { + + /** + * Gets a {@link List} of all interfaces implemented by the given class and its superclasses. + * + *

+ * The order is determined by looking through each interface in turn as declared in the source file and following its + * hierarchy up. Then each superclass is considered in the same way. Later duplicates are ignored, so the order is + * maintained. + *

+ * + * @param cls the class to look up, may be {@code null} + * @return the {@link List} of interfaces in order, {@code null} if null input + */ + public static List> getAllInterfaces(final Class cls) { + if (cls == null) { + return null; + } + + final LinkedHashSet> interfacesFound = new LinkedHashSet<>(); + getAllInterfaces(cls, interfacesFound); + + return new ArrayList<>(interfacesFound); + } + + /** + * Gets the interfaces for the specified class. + * + * @param cls the class to look up, may be {@code null} + * @param interfacesFound the {@link Set} of interfaces for the class + */ + private static void getAllInterfaces(Class cls, final HashSet> interfacesFound) { + while (cls != null) { + final Class[] interfaces = cls.getInterfaces(); + + for (final Class i : interfaces) { + if (interfacesFound.add(i)) { + getAllInterfaces(i, interfacesFound); + } + } + + cls = cls.getSuperclass(); + } + } + + /** + * ClassUtils instances should NOT be constructed in standard programming. Instead, the class should be used as + * {@code ClassUtils.getShortClassName(cls)}. + * + *

+ * This constructor is public to permit tools that require a JavaBean instance to operate. + *

+ */ + public ClassUtils() { + } + +} diff --git a/src/test/java/graphql/annotations/GraphQLFragmentTest.java b/src/test/java/graphql/annotations/GraphQLFragmentTest.java index b127bff4..9a4b0df0 100644 --- a/src/test/java/graphql/annotations/GraphQLFragmentTest.java +++ b/src/test/java/graphql/annotations/GraphQLFragmentTest.java @@ -17,6 +17,7 @@ import graphql.ExecutionResult; import graphql.GraphQL; import graphql.TypeResolutionEnvironment; +import graphql.annotations.GraphQLFragmentTest.MyInterface; import graphql.annotations.annotationTypes.GraphQLField; import graphql.annotations.annotationTypes.GraphQLTypeResolver; import graphql.annotations.processor.GraphQLAnnotations; @@ -95,7 +96,10 @@ public MyObject2 getMy() { } } - public static class MyObject2 implements MyInterface { + public static class MyObject2 extends SuperClass { + } + + public static class SuperClass implements MyInterface { public String getA() { return "a2"; } diff --git a/src/test/java/graphql/annotations/GraphQLInterfaceTest.java b/src/test/java/graphql/annotations/GraphQLInterfaceTest.java index 4dba806d..4c08daad 100644 --- a/src/test/java/graphql/annotations/GraphQLInterfaceTest.java +++ b/src/test/java/graphql/annotations/GraphQLInterfaceTest.java @@ -128,7 +128,7 @@ public void testUnion() { public void testInterfaces() { GraphQLObjectType object = this.graphQLAnnotations.object(TestObject.class); List ifaces = object.getInterfaces(); - assertEquals(ifaces.size(), 1); + assertEquals(ifaces.size(), 2); assertEquals(ifaces.get(0).getName(), "TestIface"); }