Skip to content

Commit 60adaff

Browse files
authored
Merge pull request #845 from graphql-java-kickstart/refactor/drop-declaring-type
Drop unused declaring type and rename GenericType to GenericTypeResolver
2 parents 2dbc802 + e1aa4f1 commit 60adaff

10 files changed

Lines changed: 130 additions & 190 deletions

File tree

‎src/main/kotlin/graphql/kickstart/tools/GenericType.kt‎

Lines changed: 0 additions & 147 deletions
This file was deleted.
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package graphql.kickstart.tools
2+
3+
import com.fasterxml.classmate.ResolvedType
4+
import graphql.kickstart.tools.util.JavaType
5+
import graphql.kickstart.tools.util.ParameterizedTypeImpl
6+
import graphql.kickstart.tools.util.Primitives
7+
import org.apache.commons.lang3.reflect.TypeUtils
8+
import java.lang.reflect.ParameterizedType
9+
import java.lang.reflect.TypeVariable
10+
import java.lang.reflect.WildcardType
11+
12+
/**
13+
* @author Andrew Potter
14+
*/
15+
internal class GenericTypeResolver(private val containingType: JavaType, private val options: SchemaParserOptions) {
16+
17+
fun getRawClass() = getRawClass(containingType)
18+
19+
fun getRawClass(type: JavaType): Class<*> = TypeUtils.getRawType(type, containingType)
20+
21+
fun isAssignableFrom(type: JavaType) = TypeUtils.isAssignable(type, containingType)
22+
23+
/**
24+
* Turns a type as declared in the source (a method's return or parameter type, or a field's type) into the type
25+
* the GraphQL type is matched against:
26+
* - type variables are resolved relative to the containing type,
27+
* e.g. `T` of `AbstractItem<T>.getId()` becomes `Long` for `class Item : AbstractItem<Long>()`
28+
* - generic wrappers are replaced by their wrapped type, e.g. `CompletableFuture<Foo>` becomes `Foo`
29+
* - wildcards are replaced by their upper bound, e.g. `? extends Foo` becomes `Foo`
30+
* - primitives are replaced by their boxed class, e.g. `int` becomes `Integer`
31+
*/
32+
fun resolve(javaType: JavaType): JavaType {
33+
return when (val type = resolveTypeVariables(javaType)) {
34+
is ParameterizedType -> {
35+
val rawType = type.rawType
36+
val wrapper = options.genericWrappers.find { it.type == rawType }
37+
?: return type
38+
39+
val typeArguments = type.actualTypeArguments
40+
if (typeArguments.size <= wrapper.index) {
41+
throw IndexOutOfBoundsException("Generic type '${TypeUtils.toString(type)}' does not have a type argument at index ${wrapper.index}!")
42+
}
43+
44+
val unwrapsTo = wrapper.schemaWrapper.invoke(typeArguments[wrapper.index])
45+
resolve(unwrapsTo)
46+
}
47+
is TypeVariable<*> -> error("Could not resolve type variable '${TypeUtils.toLongString(type)}' relative to ${TypeUtils.toString(containingType)}")
48+
is WildcardType -> type.upperBounds.firstOrNull()
49+
?: error("Unable to unwrap type, wildcard has no upper bound: $type")
50+
is Class<*> -> if (type.isPrimitive) Primitives.wrap(type) else type
51+
else -> error("Unable to unwrap type: $type")
52+
}
53+
}
54+
55+
private fun resolveTypeVariables(type: JavaType, resolving: Set<TypeVariable<*>> = emptySet()): JavaType {
56+
return when (type) {
57+
is ParameterizedType -> {
58+
val actualTypeArguments = type.actualTypeArguments.map { resolveTypeVariables(it, resolving) }.toTypedArray()
59+
ParameterizedTypeImpl(type.rawType as Class<*>, actualTypeArguments, type.ownerType?.let { resolveTypeVariables(it, resolving) })
60+
}
61+
is WildcardType -> TypeUtils.wildcardType()
62+
.withUpperBounds(*type.upperBounds.map { resolveTypeVariables(it, resolving) }.toTypedArray())
63+
.withLowerBounds(*type.lowerBounds.map { resolveTypeVariables(it, resolving) }.toTypedArray())
64+
.build()
65+
is ResolvedType -> {
66+
if (type.typeParameters.isEmpty()) {
67+
type.erasedType
68+
} else {
69+
val actualTypeArguments = type.typeParameters.map { resolveTypeVariables(it) }.toTypedArray()
70+
ParameterizedTypeImpl(type.erasedType, actualTypeArguments, null)
71+
}
72+
}
73+
is TypeVariable<*> -> {
74+
val genericDeclaration = type.genericDeclaration
75+
when {
76+
// only a variable leaked from a raw type can be bound to a type containing itself (e.g. T -> List<T>),
77+
// erase it like the raw type does instead of expanding it forever
78+
type in resolving -> TypeUtils.getRawType(type.bounds.first(), null) ?: Any::class.java
79+
// the containing type binds the variables of all its supertypes
80+
genericDeclaration is Class<*> -> generateSequence(containingType) { (it as? ParameterizedType)?.ownerType }
81+
// an inner class can also use the variables of its outer class, those are bound by its owner type (e.g. Connection<Owner>.Entry)
82+
.firstNotNullOfOrNull { TypeUtils.getTypeArguments(it, genericDeclaration)?.get(type) }
83+
// keep the full type argument (e.g. List<Foo>) rather than its raw class so nested generics aren't lost
84+
?.let { resolveTypeVariables(it, resolving + type) }
85+
?: type
86+
else -> type
87+
}
88+
}
89+
else -> type
90+
}
91+
}
92+
}

‎src/main/kotlin/graphql/kickstart/tools/SchemaClassScanner.kt‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -396,7 +396,7 @@ internal class SchemaClassScanner(
396396
handleFoundType(typeClassMatcher.match(TypeClassMatcher.PotentialMatch.parameterType(
397397
inputValueDefinition.type,
398398
inputValueJavaType,
399-
GenericType(javaType, options).relativeToType(javaType),
399+
GenericTypeResolver(javaType, options),
400400
InputObjectReference(inputValueDefinition)
401401
)))
402402
} else {

‎src/main/kotlin/graphql/kickstart/tools/TypeClassMatcher.kt‎

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package graphql.kickstart.tools
33
import graphql.execution.DataFetcherResult
44
import graphql.kickstart.tools.util.GraphQLLangType
55
import graphql.kickstart.tools.util.JavaType
6+
import graphql.kickstart.tools.util.isSubtypeOf
67
import graphql.language.*
78
import graphql.schema.idl.ScalarInfo
89
import java.lang.reflect.ParameterizedType
@@ -13,10 +14,6 @@ import java.util.*
1314
*/
1415
internal class TypeClassMatcher(private val definitionsByName: Map<String, TypeDefinition<*>>) {
1516

16-
companion object {
17-
fun isListType(realType: ParameterizedType, generic: GenericType) = generic.isTypeAssignableFromRawClass(realType, Iterable::class.java)
18-
}
19-
2017
private fun error(potentialMatch: PotentialMatch, msg: String) = SchemaClassScannerError("Unable to match type definition (${potentialMatch.graphQLType}) for reference ${potentialMatch.reference} with java type (${potentialMatch.javaType}): $msg")
2118

2219
fun match(potentialMatch: PotentialMatch): Match {
@@ -25,33 +22,33 @@ internal class TypeClassMatcher(private val definitionsByName: Map<String, TypeD
2522

2623
private fun match(potentialMatch: PotentialMatch, graphQLType: GraphQLLangType, javaType: JavaType, root: Boolean = false): Match {
2724

28-
var realType = potentialMatch.generic.unwrapGenericType(javaType)
25+
var realType = potentialMatch.typeResolver.resolve(javaType)
2926

30-
if (realType is ParameterizedType && potentialMatch.generic.isTypeAssignableFromRawClass(realType, DataFetcherResult::class.java)) {
27+
if (realType is ParameterizedType && realType.isSubtypeOf(DataFetcherResult::class.java)) {
3128
if (potentialMatch.location != Location.RETURN_TYPE) {
3229
throw error(potentialMatch, "${DataFetcherResult::class.java.name} can only be used as a return type")
3330
}
3431

35-
realType = potentialMatch.generic.unwrapGenericType(realType.actualTypeArguments.first())
32+
realType = potentialMatch.typeResolver.resolve(realType.actualTypeArguments.first())
3633

37-
if (realType is ParameterizedType && potentialMatch.generic.isTypeAssignableFromRawClass(realType, DataFetcherResult::class.java)) {
34+
if (realType is ParameterizedType && realType.isSubtypeOf(DataFetcherResult::class.java)) {
3835
throw error(potentialMatch, "${DataFetcherResult::class.java.name} cannot be nested within itself")
3936
}
4037
}
4138

4239
var optional = false
4340

4441
// Handle jdk8 Optionals
45-
if (realType is ParameterizedType && potentialMatch.generic.isTypeAssignableFromRawClass(realType, Optional::class.java)) {
42+
if (realType is ParameterizedType && realType.isSubtypeOf(Optional::class.java)) {
4643
optional = true
4744

4845
if (potentialMatch.location == Location.RETURN_TYPE && !root) {
4946
throw error(potentialMatch, "${Optional::class.java.name} can only be used at the top level of a return type")
5047
}
5148

52-
realType = potentialMatch.generic.unwrapGenericType(realType.actualTypeArguments.first())
49+
realType = potentialMatch.typeResolver.resolve(realType.actualTypeArguments.first())
5350

54-
if (realType is ParameterizedType && potentialMatch.generic.isTypeAssignableFromRawClass(realType, Optional::class.java)) {
51+
if (realType is ParameterizedType && realType.isSubtypeOf(Optional::class.java)) {
5552
throw error(potentialMatch, "${Optional::class.java.name} cannot be nested within itself")
5653
}
5754
}
@@ -66,7 +63,7 @@ internal class TypeClassMatcher(private val definitionsByName: Map<String, TypeD
6663
}
6764

6865
is ListType -> when {
69-
realType is ParameterizedType && isListType(realType, potentialMatch) ->
66+
realType is ParameterizedType && realType.isSubtypeOf(Iterable::class.java) ->
7067
match(potentialMatch, graphQLType.type, realType.actualTypeArguments.first())
7168
realType is Class<*> && realType.isArray ->
7269
match(potentialMatch, graphQLType.type, realType.componentType)
@@ -93,8 +90,6 @@ internal class TypeClassMatcher(private val definitionsByName: Map<String, TypeD
9390
}
9491
}
9592

96-
private fun isListType(realType: ParameterizedType, potentialMatch: PotentialMatch) = isListType(realType, potentialMatch.generic)
97-
9893
internal sealed interface Match
9994

10095
internal data class ScalarMatch(val type: ScalarTypeDefinition) : Match
@@ -109,16 +104,16 @@ internal class TypeClassMatcher(private val definitionsByName: Map<String, TypeD
109104
internal data class PotentialMatch(
110105
val graphQLType: GraphQLLangType,
111106
val javaType: JavaType,
112-
val generic: GenericType.RelativeTo,
107+
val typeResolver: GenericTypeResolver,
113108
val reference: SchemaClassScanner.Reference,
114109
val location: Location
115110
) {
116111
companion object {
117-
fun returnValue(graphQLType: GraphQLLangType, javaType: JavaType, generic: GenericType.RelativeTo, reference: SchemaClassScanner.Reference) =
118-
PotentialMatch(graphQLType, javaType, generic, reference, Location.RETURN_TYPE)
112+
fun returnValue(graphQLType: GraphQLLangType, javaType: JavaType, typeResolver: GenericTypeResolver, reference: SchemaClassScanner.Reference) =
113+
PotentialMatch(graphQLType, javaType, typeResolver, reference, Location.RETURN_TYPE)
119114

120-
fun parameterType(graphQLType: GraphQLLangType, javaType: JavaType, generic: GenericType.RelativeTo, reference: SchemaClassScanner.Reference) =
121-
PotentialMatch(graphQLType, javaType, generic, reference, Location.PARAMETER_TYPE)
115+
fun parameterType(graphQLType: GraphQLLangType, javaType: JavaType, typeResolver: GenericTypeResolver, reference: SchemaClassScanner.Reference) =
116+
PotentialMatch(graphQLType, javaType, typeResolver, reference, Location.PARAMETER_TYPE)
122117
}
123118
}
124119
}

0 commit comments

Comments
 (0)