Skip to content

Commit c3fb1ed

Browse files
smyrickdariuszkuc
authored andcommitted
refactor: move types cache to class (#37)
* refactor: move types cache to class * rename test file * undo uneeded change
1 parent 4cc4a48 commit c3fb1ed

File tree

7 files changed

+250
-77
lines changed

7 files changed

+250
-77
lines changed

detekt_baseline.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,4 @@
1515
<ID>UnsafeCast:SchemaGeneratorAsyncTests.kt$SchemaGeneratorAsyncTests$schema.getObjectType("TopLevelQuery").getFieldDefinition("asynchronouslyDoSingle").type as GraphQLNonNull</ID>
1616
<ID>UnsafeCast:SchemaGeneratorAsyncTests.kt$SchemaGeneratorAsyncTests$schema.getObjectType("TopLevelQuery").getFieldDefinition("maybe").type as GraphQLNonNull</ID>
1717
</Whitelist>
18-
</SmellBaseline>
18+
</SmellBaseline>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.expedia.graphql.schema.exceptions
2+
3+
import kotlin.reflect.KType
4+
5+
/**
6+
* Thrown when trying to generate a class and cannot resolve the jvm erasure name.
7+
*/
8+
class CouldNotGetJvmNameOfKTypeException(kType: KType?)
9+
: RuntimeException("Could not get the name of the KClass $kType")

src/main/kotlin/com/expedia/graphql/schema/generator/SchemaGenerator.kt

Lines changed: 34 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,7 @@ import com.expedia.graphql.TopLevelObjectDef
44
import com.expedia.graphql.schema.KotlinDataFetcher
55
import com.expedia.graphql.schema.Parameter
66
import com.expedia.graphql.schema.SchemaGeneratorConfig
7-
import com.expedia.graphql.schema.exceptions.ConflictingTypesException
8-
import com.expedia.graphql.schema.exceptions.CouldNotGetNameOfEnumException
97
import com.expedia.graphql.schema.exceptions.InvalidInputFieldTypeException
10-
import com.expedia.graphql.schema.exceptions.TypeNotSupportedException
118
import com.expedia.graphql.schema.extensions.directives
129
import com.expedia.graphql.schema.extensions.getDeprecationReason
1310
import com.expedia.graphql.schema.extensions.graphQLDescription
@@ -52,7 +49,7 @@ internal class SchemaGenerator(
5249
private val config: SchemaGeneratorConfig
5350
) {
5451

55-
private val typesCache: MutableMap<String, KGraphQLType> = mutableMapOf()
52+
private val cache = TypesCache(config.supportedPackages)
5653
private val additionTypes = mutableSetOf<GraphQLType>()
5754
private val directives = mutableSetOf<GraphQLDirective>()
5855
private val reflections = Reflections(config.supportedPackages)
@@ -73,7 +70,7 @@ internal class SchemaGenerator(
7370

7471
private fun addAdditionalTypes(builder: GraphQLSchema.Builder) {
7572
additionTypes
76-
.filter { typesCache.none { (_, v) -> v.graphQLType.name == it.name } }
73+
.filter { cache.doesNotContainGraphQLType(it) }
7774
.forEach { builder.additionalType(it) }
7875
}
7976

@@ -83,9 +80,7 @@ internal class SchemaGenerator(
8380
val queryBuilder = GraphQLObjectType.Builder()
8481
queryBuilder.name(config.topLevelQueryName)
8582
for (query in queries) {
86-
query.klazz.declaredMemberFunctions
87-
.filter { config.hooks.isValidFunction(it) }
88-
.filter { func -> functionFilters.all { it.invoke(func) } }
83+
query.klazz.getValidFunctions(config.hooks)
8984
.forEach {
9085
val function = function(it, query.obj)
9186
val functionFromHook = config.hooks.didGenerateQueryType(it, function)
@@ -101,9 +96,7 @@ internal class SchemaGenerator(
10196
mutationBuilder.name(config.topLevelMutationName)
10297

10398
for (mutation in mutations) {
104-
mutation.klazz.declaredMemberFunctions
105-
.filter { config.hooks.isValidFunction(it) }
106-
.filter { func -> functionFilters.all { it.invoke(func) } }
99+
mutation.klazz.getValidFunctions(config.hooks)
107100
.forEach {
108101
val function = function(it, mutation.obj)
109102
val functionFromHook = config.hooks.didGenerateMutationType(it, function)
@@ -163,10 +156,10 @@ internal class SchemaGenerator(
163156
private fun argument(parameter: KParameter): GraphQLArgument {
164157
throwIfInterfaceIsNotAuthorized(parameter)
165158
return GraphQLArgument.newArgument()
166-
.name(parameter.name)
167-
.description(parameter.graphQLDescription() ?: parameter.type.graphQLDescription())
168-
.type(graphQLTypeOf(parameter.type, true) as GraphQLInputType)
169-
.build()
159+
.name(parameter.name)
160+
.description(parameter.graphQLDescription() ?: parameter.type.graphQLDescription())
161+
.type(graphQLTypeOf(parameter.type, true) as GraphQLInputType)
162+
.build()
170163
}
171164

172165
private fun graphQLTypeOf(type: KType, inputType: Boolean = false): GraphQLType {
@@ -178,21 +171,18 @@ internal class SchemaGenerator(
178171
}
179172

180173
private fun objectFromReflection(type: KType, inputType: Boolean): GraphQLType {
181-
val kClass = type.classifier as KClass<*>
182-
val cacheKey = getCacheKey(kClass, type, inputType)
183-
val cachedType = typesCache[cacheKey]
174+
val cacheKey = TypesCacheKey(type, inputType)
175+
val cachedType = cache.get(cacheKey)
184176

185177
if (cachedType != null) {
186-
val isSameNameButNotSameClass = cachedType.kClass != kClass
187-
when {
188-
isSameNameButNotSameClass -> throw ConflictingTypesException(cachedType.kClass, kClass)
189-
else -> return cachedType.graphQLType
190-
}
178+
return cachedType
191179
}
192180

181+
val kClass = type.classifier as KClass<*>
193182
val graphQLType = getGraphQLType(kClass, inputType, type)
183+
val kGraphQLType = KGraphQLType(kClass, graphQLType)
194184

195-
typesCache[cacheKey] = KGraphQLType(kClass, graphQLType)
185+
cache.put(cacheKey, kGraphQLType)
196186

197187
return graphQLType
198188
}
@@ -210,31 +200,6 @@ internal class SchemaGenerator(
210200
private fun KClass<*>.canBeGraphQLUnion(): Boolean =
211201
this.canBeGraphQLInterface() && this.declaredMemberProperties.isEmpty() && this.declaredMemberFunctions.isEmpty()
212202

213-
private fun getCacheKey(kClass: KClass<*>, type: KType, inputType: Boolean): String {
214-
if (kClass.isSubclassOf(Enum::class)) {
215-
return kClass.simpleName ?: throw CouldNotGetNameOfEnumException(kClass)
216-
}
217-
val cacheKeyFromTypeName = when {
218-
kClass.isSubclassOf(List::class) -> "List<${type.arguments.first().type!!.jvmErasure.simpleName}>"
219-
kClass.java.isArray -> "Array<${type.arguments.first().type!!.jvmErasure.simpleName}>"
220-
else -> {
221-
throwIfTypeIsNotSupported(type)
222-
type.jvmErasure.simpleName
223-
}
224-
}
225-
226-
return "$cacheKeyFromTypeName:$inputType"
227-
}
228-
229-
@Throws(TypeNotSupportedException::class)
230-
private fun throwIfTypeIsNotSupported(type: KType) {
231-
val qualifiedName = type.jvmErasure.qualifiedName ?: ""
232-
val comesFromSupportedPackageName = qualifiedName.startsWith(config.supportedPackages)
233-
if (!comesFromSupportedPackageName) {
234-
throw TypeNotSupportedException(qualifiedName, config.supportedPackages)
235-
}
236-
}
237-
238203
@Throws(InvalidInputFieldTypeException::class)
239204
private fun throwIfInterfaceIsNotAuthorized(parameter: KParameter) {
240205
if (parameter.type.jvmErasure.java.isInterface) throw InvalidInputFieldTypeException()
@@ -253,51 +218,45 @@ internal class SchemaGenerator(
253218
private fun listType(type: KType, inputType: Boolean): GraphQLList =
254219
GraphQLList.list(graphQLTypeOf(type.arguments.first().type!!, inputType))
255220

256-
private fun objectType(klass: KClass<*>, interfaceType: GraphQLInterfaceType? = null): GraphQLType {
221+
private fun objectType(kClass: KClass<*>, interfaceType: GraphQLInterfaceType? = null): GraphQLType {
257222
val builder = GraphQLObjectType.newObject()
258223

259-
builder.name(klass.simpleName)
260-
builder.description(klass.graphQLDescription())
224+
builder.name(kClass.simpleName)
225+
builder.description(kClass.graphQLDescription())
261226

262-
klass.directives().map {
227+
kClass.directives().map {
263228
builder.withDirective(it)
264229
directives.add(it)
265230
}
266231

267232
if (interfaceType != null) {
268233
builder.withInterface(interfaceType)
269234
} else {
270-
klass.superclasses
235+
kClass.superclasses
271236
.asSequence()
272237
.filter { it.canBeGraphQLInterface() && !it.canBeGraphQLUnion() }
273238
.map { objectFromReflection(it.createType(), false) as GraphQLInterfaceType }
274239
.forEach { builder.withInterface(it) }
275240
}
276241

277-
klass.declaredMemberProperties
278-
.filter { config.hooks.isValidProperty(it) }
279-
.filter { prop -> propertyFilters.all { it.invoke(prop) } }
242+
kClass.getValidProperties(config.hooks)
280243
.forEach { builder.field(property(it)) }
281244

282-
klass.declaredMemberFunctions
283-
.filter { config.hooks.isValidFunction(it) }
284-
.filter { func -> functionFilters.all { it.invoke(func) } }
245+
kClass.getValidFunctions(config.hooks)
285246
.forEach { builder.field(function(it)) }
286247

287248
return builder.build()
288249
}
289250

290-
private fun inputObjectType(klass: KClass<*>): GraphQLType {
251+
private fun inputObjectType(kClass: KClass<*>): GraphQLType {
291252
val builder = GraphQLInputObjectType.newInputObject()
292-
val name = getGraphQLClassName(klass, true)
253+
val name = getGraphQLClassName(kClass, true)
293254

294255
builder.name(name)
295-
builder.description(klass.graphQLDescription())
256+
builder.description(kClass.graphQLDescription())
296257

297258
// It does not make sense to run functions against the input types so we only process data fields
298-
klass.declaredMemberProperties
299-
.filter { config.hooks.isValidProperty(it) }
300-
.filter { prop -> propertyFilters.all { it.invoke(prop) } }
259+
kClass.getValidProperties(config.hooks)
301260
.forEach { builder.field(inputProperty(it)) }
302261

303262
return builder.build()
@@ -319,20 +278,16 @@ internal class SchemaGenerator(
319278
builder.name(kClass.simpleName)
320279
builder.description(kClass.graphQLDescription())
321280

322-
kClass.declaredMemberProperties
323-
.filter { config.hooks.isValidProperty(it) }
324-
.filter { prop -> propertyFilters.all { it.invoke(prop) } }
325-
.forEach { builder.field(property(it)) }
281+
kClass.getValidProperties(config.hooks)
282+
.forEach { builder.field(property(it)) }
326283

327-
kClass.declaredMemberFunctions
328-
.filter { config.hooks.isValidFunction(it) }
329-
.filter { func -> functionFilters.all { it.invoke(func) } }
330-
.forEach { builder.field(function(it, abstract = true)) }
284+
kClass.getValidFunctions(config.hooks)
285+
.forEach { builder.field(function(it, abstract = true)) }
331286

332287
builder.typeResolver { env: TypeResolutionEnvironment -> env.schema.getObjectType(env.getObject<Any>().javaClass.simpleName) }
333288
val interfaceType = builder.build()
334289

335-
val implementations = reflections.getSubTypesOf(Class.forName(kClass.javaObjectType.name))
290+
val implementations = getSubTypesOf(kClass)
336291
implementations.forEach {
337292
additionTypes.add(objectType(it.kotlin, interfaceType))
338293
}
@@ -347,12 +302,15 @@ internal class SchemaGenerator(
347302
builder.description(kClass.graphQLDescription())
348303
builder.typeResolver { env: TypeResolutionEnvironment -> env.schema.getObjectType(env.getObject<Any>().javaClass.simpleName) }
349304

350-
val implementations = reflections.getSubTypesOf(Class.forName(kClass.javaObjectType.name))
305+
val implementations = getSubTypesOf(kClass)
351306
implementations
352307
.forEach {
353308
builder.possibleType(objectType(it.kotlin) as GraphQLObjectType)
354309
}
355310

356311
return builder.build()
357312
}
313+
314+
private fun getSubTypesOf(kclass: KClass<*>): MutableSet<out Class<out Any>> =
315+
reflections.getSubTypesOf(Class.forName(kclass.javaObjectType.name))
358316
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package com.expedia.graphql.schema.generator
2+
3+
import com.expedia.graphql.schema.exceptions.ConflictingTypesException
4+
import com.expedia.graphql.schema.exceptions.CouldNotGetJvmNameOfKTypeException
5+
import com.expedia.graphql.schema.exceptions.CouldNotGetNameOfEnumException
6+
import com.expedia.graphql.schema.exceptions.TypeNotSupportedException
7+
import com.expedia.graphql.schema.models.KGraphQLType
8+
import graphql.schema.GraphQLType
9+
import kotlin.reflect.KClass
10+
import kotlin.reflect.KType
11+
import kotlin.reflect.full.isSubclassOf
12+
import kotlin.reflect.jvm.jvmErasure
13+
14+
internal data class TypesCacheKey(val type: KType, val inputType: Boolean)
15+
16+
internal class TypesCache(private val supportedPackages: String) {
17+
18+
private val cache: MutableMap<String, KGraphQLType> = mutableMapOf()
19+
20+
fun get(cacheKey: TypesCacheKey): GraphQLType? {
21+
val cacheKeyString = getCacheKeyString(cacheKey)
22+
val cachedType = cache[cacheKeyString]
23+
24+
if (cachedType != null) {
25+
val kClass = getKClassFromKType(cacheKey.type)
26+
val isSameNameButNotSameClass = cachedType.kClass != kClass
27+
when {
28+
isSameNameButNotSameClass -> throw ConflictingTypesException(cachedType.kClass, kClass)
29+
else -> return cachedType.graphQLType
30+
}
31+
}
32+
33+
return null
34+
}
35+
36+
fun put(key: TypesCacheKey, kGraphQLType: KGraphQLType) = cache.put(getCacheKeyString(key), kGraphQLType)
37+
38+
fun doesNotContainGraphQLType(graphQLType: GraphQLType) =
39+
cache.none { (_, v) -> v.graphQLType.name == graphQLType.name }
40+
41+
@Throws(CouldNotGetNameOfEnumException::class)
42+
private fun getCacheKeyString(cacheKey: TypesCacheKey): String {
43+
val kClass = getKClassFromKType(cacheKey.type)
44+
45+
if (kClass.isSubclassOf(Enum::class)) {
46+
return kClass.simpleName ?: throw CouldNotGetNameOfEnumException(kClass)
47+
}
48+
49+
val cacheKeyFromTypeName = when {
50+
kClass.isSubclassOf(List::class) -> "List<${getJvmErasureNameFromList(cacheKey.type)}>"
51+
kClass.java.isArray -> "Array<${getJvmErasureNameFromList(cacheKey.type)}>"
52+
else -> getCacheTypeName(cacheKey.type)
53+
}
54+
55+
return "$cacheKeyFromTypeName:${cacheKey.inputType}"
56+
}
57+
58+
@Suppress("Detekt.UnsafeCast")
59+
private fun getKClassFromKType(kType: KType) = kType.classifier as KClass<*>
60+
61+
private fun getCacheTypeName(kType: KType): String {
62+
throwIfTypeIsNotSupported(kType)
63+
return getJvmErasureName(kType)
64+
}
65+
66+
private fun getJvmErasureNameFromList(type: KType): String =
67+
getJvmErasureName(type.arguments.first().type)
68+
69+
@Throws(CouldNotGetJvmNameOfKTypeException::class)
70+
private fun getJvmErasureName(kType: KType?): String =
71+
kType?.jvmErasure?.simpleName ?: throw CouldNotGetJvmNameOfKTypeException(kType)
72+
73+
@Throws(TypeNotSupportedException::class)
74+
private fun throwIfTypeIsNotSupported(type: KType) {
75+
val qualifiedName = type.jvmErasure.qualifiedName ?: ""
76+
val comesFromSupportedPackageName = qualifiedName.startsWith(supportedPackages)
77+
if (!comesFromSupportedPackageName) {
78+
throw TypeNotSupportedException(qualifiedName, supportedPackages)
79+
}
80+
}
81+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.expedia.graphql.schema.generator
2+
3+
import com.expedia.graphql.schema.hooks.SchemaGeneratorHooks
4+
import kotlin.reflect.KClass
5+
import kotlin.reflect.full.declaredMemberFunctions
6+
import kotlin.reflect.full.declaredMemberProperties
7+
8+
internal fun KClass<*>.getValidProperties(hooks: SchemaGeneratorHooks) = this.declaredMemberProperties
9+
.filter { hooks.isValidProperty(it) }
10+
.filter { prop -> propertyFilters.all { it.invoke(prop) } }
11+
12+
internal fun KClass<*>.getValidFunctions(hooks: SchemaGeneratorHooks) = this.declaredMemberFunctions
13+
.filter { hooks.isValidFunction(it) }
14+
.filter { func -> functionFilters.all { it.invoke(func) } }

0 commit comments

Comments
 (0)