Skip to content

Commit 661fb59

Browse files
committed
Move sealed subclass matching out of SchemaClassScanner
Moves sealedSubclassHint, sealedSubclassMatches, sealedTypesOf, sealedSubclasses and withSealedTypeArguments to SealedSubclassMatcher.kt. It reads the dictionary as a Map<TypeDefinition<*>, EntryView> and never changes it. Edits to compile: - SealedSubclassMatcher's header. - private dropped on sealedSubclassHint and sealedSubclassMatches. - The scanner holds a sealedSubclassMatcher and calls both through it. - Imports.
1 parent c0c5efe commit 661fb59

2 files changed

Lines changed: 77 additions & 61 deletions

File tree

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

Lines changed: 3 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package graphql.kickstart.tools
33
import graphql.kickstart.tools.ScanDictionary.DictionaryReference
44
import graphql.kickstart.tools.ScanDictionary.FieldTypeReference
55
import graphql.kickstart.tools.ScanDictionary.InitialDictionaryEntry
6-
import graphql.kickstart.tools.ScanDictionary.SealedSubclassReference
76
import graphql.kickstart.tools.ScanDictionary.UnionMemberReference
87
import graphql.kickstart.tools.ScanDictionary.UnusedTypeReference
98
import graphql.kickstart.tools.resolver.FieldResolver
@@ -15,12 +14,10 @@ import graphql.language.*
1514
import graphql.schema.idl.ScalarInfo
1615
import graphql.schema.idl.TypeUtil
1716
import org.apache.commons.lang3.ClassUtils
18-
import org.apache.commons.lang3.reflect.TypeUtils
1917
import org.slf4j.LoggerFactory
2018
import java.lang.reflect.Field
2119
import java.lang.reflect.Member
2220
import java.lang.reflect.Modifier
23-
import java.lang.reflect.ParameterizedType
2421

2522
/**
2623
* @author Andrew Potter
@@ -66,6 +63,7 @@ internal class SchemaClassScanner(
6663
private val unvalidatedTypes = mutableSetOf<TypeDefinition<*>>()
6764
private val queue = linkedSetOf<QueueItem>()
6865
private val dictionary = ScanDictionary(options, log, typeExtensions, definitionsByName, typeClassMatcher, this.initialDictionary, ::enqueue, ::handleFoundScalarType)
66+
private val sealedSubclassMatcher = SealedSubclassMatcher(dictionary, typeExtensions)
6967

7068
private val fieldResolversByType = mutableMapOf<ObjectTypeDefinition, MutableMap<FieldDefinition, FieldResolver>>()
7169

@@ -399,70 +397,14 @@ internal class SchemaClassScanner(
399397
}
400398
}
401399

402-
val sealedMatches = typesWithoutClass.filterIsInstance<ObjectTypeDefinition>().mapNotNull { sealedSubclassMatches(it).singleOrNull() }
400+
val sealedMatches = typesWithoutClass.filterIsInstance<ObjectTypeDefinition>().mapNotNull { sealedSubclassMatcher.sealedSubclassMatches(it).singleOrNull() }
403401
if (sealedMatches.isEmpty()) {
404-
typesWithoutClass.firstOrNull()?.let { throw SchemaClassScannerError(failureMessage(it) + sealedSubclassHint(it)) }
402+
typesWithoutClass.firstOrNull()?.let { throw SchemaClassScannerError(failureMessage(it) + sealedSubclassMatcher.sealedSubclassHint(it)) }
405403
}
406404

407405
sealedMatches.forEach { dictionary.handleFoundType(it) }
408406
}
409407

410-
private fun sealedSubclassHint(type: TypeDefinition<*>): String {
411-
if (type !is ObjectTypeDefinition) {
412-
return ""
413-
}
414-
415-
val sealedTypes = sealedTypesOf(type)
416-
if (sealedTypes.isEmpty()) {
417-
return ""
418-
}
419-
420-
val sealedTypeNames = sealedTypes.joinToString(" or ") { it.unwrap().name }
421-
val matches = sealedSubclassMatches(type)
422-
return if (matches.isEmpty()) {
423-
" No subclass of sealed type $sealedTypeNames is named '${type.name}'."
424-
} else {
425-
" More than one subclass of sealed type $sealedTypeNames is named '${type.name}': ${matches.joinToString { it.javaType.unwrap().name }}."
426-
}
427-
}
428-
429-
private fun sealedSubclassMatches(type: ObjectTypeDefinition): List<TypeClassMatcher.ValidMatch> =
430-
sealedTypesOf(type)
431-
.flatMap { sealedType ->
432-
sealedSubclasses(sealedType.unwrap())
433-
.filter { it.simpleName == type.name }
434-
.map { TypeClassMatcher.ValidMatch(type, withSealedTypeArguments(it, sealedType), SealedSubclassReference(sealedType)) }
435-
}
436-
.distinctBy { it.javaType.unwrap() }
437-
438-
private fun sealedTypesOf(type: ObjectTypeDefinition): List<JavaType> =
439-
dictionary
440-
.filterKeys { definition ->
441-
when (definition) {
442-
is UnionTypeDefinition -> definition.withExtensions(typeExtensions).flatMap { it.memberTypes }.filterIsInstance<TypeName>().any { it.name == type.name }
443-
is InterfaceTypeDefinition -> type.withExtensions(typeExtensions).flatMap { it.implements }.filterIsInstance<TypeName>().any { it.name == definition.name }
444-
else -> false
445-
}
446-
}
447-
.values.mapNotNull { it.javaType }
448-
.distinct()
449-
.filter { it.unwrap().kotlin.isSealed }
450-
451-
// kotlin-reflect also returns the permitted subclasses of Java sealed classes
452-
private fun sealedSubclasses(clazz: Class<*>): List<Class<*>> =
453-
clazz.kotlin.sealedSubclasses.flatMap { listOf(it.java) + sealedSubclasses(it.java) }
454-
455-
private fun withSealedTypeArguments(subclass: Class<*>, sealedType: JavaType): JavaType {
456-
if (sealedType !is ParameterizedType || subclass.typeParameters.isEmpty()) {
457-
return subclass
458-
}
459-
460-
return TypeUtils.determineTypeArguments(subclass, sealedType)
461-
?.takeIf { it.keys.containsAll(subclass.typeParameters.asList()) }
462-
?.let { TypeUtils.parameterize(subclass, it) }
463-
?: subclass
464-
}
465-
466408
/**
467409
* Scan a new object for types that haven't been mapped yet.
468410
*/
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package graphql.kickstart.tools
2+
3+
import graphql.kickstart.tools.ScanDictionary.SealedSubclassReference
4+
import graphql.kickstart.tools.util.JavaType
5+
import graphql.kickstart.tools.util.unwrap
6+
import graphql.kickstart.tools.util.withExtensions
7+
import graphql.language.InterfaceTypeDefinition
8+
import graphql.language.ObjectTypeDefinition
9+
import graphql.language.TypeDefinition
10+
import graphql.language.TypeName
11+
import graphql.language.UnionTypeDefinition
12+
import org.apache.commons.lang3.reflect.TypeUtils
13+
import java.lang.reflect.ParameterizedType
14+
15+
internal class SealedSubclassMatcher(
16+
private val dictionary: Map<TypeDefinition<*>, ScanDictionary.EntryView>,
17+
private val typeExtensions: TypeExtensions,
18+
) {
19+
fun sealedSubclassHint(type: TypeDefinition<*>): String {
20+
if (type !is ObjectTypeDefinition) {
21+
return ""
22+
}
23+
24+
val sealedTypes = sealedTypesOf(type)
25+
if (sealedTypes.isEmpty()) {
26+
return ""
27+
}
28+
29+
val sealedTypeNames = sealedTypes.joinToString(" or ") { it.unwrap().name }
30+
val matches = sealedSubclassMatches(type)
31+
return if (matches.isEmpty()) {
32+
" No subclass of sealed type $sealedTypeNames is named '${type.name}'."
33+
} else {
34+
" More than one subclass of sealed type $sealedTypeNames is named '${type.name}': ${matches.joinToString { it.javaType.unwrap().name }}."
35+
}
36+
}
37+
38+
fun sealedSubclassMatches(type: ObjectTypeDefinition): List<TypeClassMatcher.ValidMatch> =
39+
sealedTypesOf(type)
40+
.flatMap { sealedType ->
41+
sealedSubclasses(sealedType.unwrap())
42+
.filter { it.simpleName == type.name }
43+
.map { TypeClassMatcher.ValidMatch(type, withSealedTypeArguments(it, sealedType), SealedSubclassReference(sealedType)) }
44+
}
45+
.distinctBy { it.javaType.unwrap() }
46+
47+
private fun sealedTypesOf(type: ObjectTypeDefinition): List<JavaType> =
48+
dictionary
49+
.filterKeys { definition ->
50+
when (definition) {
51+
is UnionTypeDefinition -> definition.withExtensions(typeExtensions).flatMap { it.memberTypes }.filterIsInstance<TypeName>().any { it.name == type.name }
52+
is InterfaceTypeDefinition -> type.withExtensions(typeExtensions).flatMap { it.implements }.filterIsInstance<TypeName>().any { it.name == definition.name }
53+
else -> false
54+
}
55+
}
56+
.values.mapNotNull { it.javaType }
57+
.distinct()
58+
.filter { it.unwrap().kotlin.isSealed }
59+
60+
// kotlin-reflect also returns the permitted subclasses of Java sealed classes
61+
private fun sealedSubclasses(clazz: Class<*>): List<Class<*>> =
62+
clazz.kotlin.sealedSubclasses.flatMap { listOf(it.java) + sealedSubclasses(it.java) }
63+
64+
private fun withSealedTypeArguments(subclass: Class<*>, sealedType: JavaType): JavaType {
65+
if (sealedType !is ParameterizedType || subclass.typeParameters.isEmpty()) {
66+
return subclass
67+
}
68+
69+
return TypeUtils.determineTypeArguments(subclass, sealedType)
70+
?.takeIf { it.keys.containsAll(subclass.typeParameters.asList()) }
71+
?.let { TypeUtils.parameterize(subclass, it) }
72+
?: subclass
73+
}
74+
}

0 commit comments

Comments
 (0)