mirror of
https://github.com/fankes/moshi.git
synced 2025-10-20 00:19:21 +08:00
Begin to resolve supertype type parameters
This commit is contained in:
@@ -18,6 +18,7 @@ package com.squareup.moshi
|
||||
import com.squareup.kotlinpoet.ARRAY
|
||||
import com.squareup.kotlinpoet.AnnotationSpec
|
||||
import com.squareup.kotlinpoet.ClassName
|
||||
import com.squareup.kotlinpoet.CodeBlock
|
||||
import com.squareup.kotlinpoet.FileSpec
|
||||
import com.squareup.kotlinpoet.FunSpec
|
||||
import com.squareup.kotlinpoet.KModifier
|
||||
@@ -34,31 +35,28 @@ import me.eugeniomarletti.kotlin.metadata.visibility
|
||||
import org.jetbrains.kotlin.serialization.ProtoBuf.Visibility
|
||||
import java.lang.reflect.Type
|
||||
import javax.lang.model.element.TypeElement
|
||||
import javax.lang.model.util.Elements
|
||||
|
||||
/** Generates a JSON adapter for a target type. */
|
||||
internal class AdapterGenerator(
|
||||
target: TargetType,
|
||||
private val propertyList: List<PropertyGenerator>,
|
||||
val elements: Elements
|
||||
private val propertyList: List<PropertyGenerator>
|
||||
) {
|
||||
private val className = target.name
|
||||
private val isDataClass = target.proto.isDataClass
|
||||
private val hasCompanionObject = target.hasCompanionObject
|
||||
private val visibility = target.proto.visibility!!
|
||||
val genericTypeNames = target.genericTypeNames
|
||||
private val typeVariables = target.typeVariables
|
||||
|
||||
private val nameAllocator = NameAllocator()
|
||||
private val adapterName = "${className.simpleNames().joinToString(separator = "_")}JsonAdapter"
|
||||
private val originalTypeName = target.element.asType().asTypeName()
|
||||
|
||||
val moshiParam = ParameterSpec.builder(
|
||||
private val moshiParam = ParameterSpec.builder(
|
||||
nameAllocator.newName("moshi"),
|
||||
Moshi::class).build()
|
||||
val typesParam = ParameterSpec.builder(
|
||||
private val typesParam = ParameterSpec.builder(
|
||||
nameAllocator.newName("types"),
|
||||
ParameterizedTypeName.get(ARRAY,
|
||||
Type::class.asTypeName()))
|
||||
ParameterizedTypeName.get(ARRAY, Type::class.asTypeName()))
|
||||
.build()
|
||||
private val readerParam = ParameterSpec.builder(
|
||||
nameAllocator.newName("reader"),
|
||||
@@ -83,12 +81,7 @@ internal class AdapterGenerator(
|
||||
.joinToString(", ") { "\"$it\"" }})", JsonReader.Options::class.asTypeName())
|
||||
.build()
|
||||
|
||||
private val delegateAdapters = propertyList.distinctBy { it.delegateKey }
|
||||
|
||||
fun generateFile(generatedOption: TypeElement?): FileSpec {
|
||||
for (property in delegateAdapters) {
|
||||
property.delegateKey.reserveName(nameAllocator)
|
||||
}
|
||||
for (property in propertyList) {
|
||||
property.allocateNames(nameAllocator)
|
||||
}
|
||||
@@ -114,8 +107,8 @@ internal class AdapterGenerator(
|
||||
|
||||
result.superclass(jsonAdapterTypeName)
|
||||
|
||||
if (genericTypeNames.isNotEmpty()) {
|
||||
result.addTypeVariables(genericTypeNames)
|
||||
if (typeVariables.isNotEmpty()) {
|
||||
result.addTypeVariables(typeVariables)
|
||||
}
|
||||
|
||||
// TODO make this configurable. Right now it just matches the source model
|
||||
@@ -125,9 +118,18 @@ internal class AdapterGenerator(
|
||||
|
||||
result.primaryConstructor(generateConstructor())
|
||||
|
||||
val typeRenderer: TypeRenderer = object : TypeRenderer() {
|
||||
override fun renderTypeVariable(typeVariable: TypeVariableName): CodeBlock {
|
||||
val index = typeVariables.indexOfFirst { it == typeVariable }
|
||||
check(index != -1) { "Unexpected type variable $typeVariable" }
|
||||
return CodeBlock.of("%N[%L]", typesParam, index)
|
||||
}
|
||||
}
|
||||
|
||||
result.addProperty(optionsProperty)
|
||||
for (uniqueAdapter in delegateAdapters) {
|
||||
result.addProperty(uniqueAdapter.delegateKey.generateProperty(nameAllocator, this))
|
||||
for (uniqueAdapter in propertyList.distinctBy { it.delegateKey }) {
|
||||
result.addProperty(uniqueAdapter.delegateKey.generateProperty(
|
||||
nameAllocator, typeRenderer, moshiParam))
|
||||
}
|
||||
|
||||
result.addFunction(generateToStringFun())
|
||||
@@ -141,7 +143,7 @@ internal class AdapterGenerator(
|
||||
val result = FunSpec.constructorBuilder()
|
||||
result.addParameter(moshiParam)
|
||||
|
||||
if (genericTypeNames.isNotEmpty()) {
|
||||
if (typeVariables.isNotEmpty()) {
|
||||
result.addParameter(typesParam)
|
||||
}
|
||||
|
||||
@@ -307,9 +309,9 @@ internal class AdapterGenerator(
|
||||
result.addModifiers(KModifier.INTERNAL)
|
||||
}
|
||||
|
||||
if (genericTypeNames.isNotEmpty()) {
|
||||
if (typeVariables.isNotEmpty()) {
|
||||
result.addParameter(typesParam)
|
||||
result.addTypeVariables(genericTypeNames)
|
||||
result.addTypeVariables(typeVariables)
|
||||
result.addStatement("return %N(%N, %N)", adapterName, moshiParam, typesParam)
|
||||
} else {
|
||||
result.addStatement("return %N(%N)", adapterName, moshiParam)
|
||||
|
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright (C) 2018 Square, Inc.
|
||||
*
|
||||
* 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
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.squareup.moshi
|
||||
|
||||
import com.squareup.kotlinpoet.TypeName
|
||||
import com.squareup.kotlinpoet.TypeVariableName
|
||||
import com.squareup.kotlinpoet.asTypeName
|
||||
import javax.lang.model.element.TypeElement
|
||||
import javax.lang.model.type.DeclaredType
|
||||
import javax.lang.model.util.Types
|
||||
|
||||
/**
|
||||
* A concrete type like `List<String>` with enough information to know how to resolve its type
|
||||
* variables.
|
||||
*/
|
||||
internal class AppliedType private constructor(
|
||||
val element: TypeElement,
|
||||
val resolver: TypeResolver,
|
||||
private val mirror: DeclaredType
|
||||
) {
|
||||
/** Returns all supertypes of this, recursively. Includes both interface and class supertypes. */
|
||||
fun supertypes(
|
||||
types: Types,
|
||||
result: MutableSet<AppliedType> = mutableSetOf()
|
||||
): Set<AppliedType> {
|
||||
result.add(this)
|
||||
for (supertype in types.directSupertypes(mirror)) {
|
||||
val supertypeDeclaredType = supertype as DeclaredType
|
||||
val supertypeElement = supertypeDeclaredType.asElement() as TypeElement
|
||||
val appliedSupertype = AppliedType(supertypeElement,
|
||||
resolver(supertypeElement, supertypeDeclaredType), supertypeDeclaredType)
|
||||
appliedSupertype.supertypes(types, result)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/** Returns a resolver that uses `element` and `mirror` to resolve type parameters. */
|
||||
private fun resolver(element: TypeElement, mirror: DeclaredType): TypeResolver {
|
||||
return object : TypeResolver() {
|
||||
override fun resolveTypeVariable(typeVariable: TypeVariableName): TypeName {
|
||||
val index = element.typeParameters.indexOfFirst {
|
||||
it.simpleName.toString() == typeVariable.name
|
||||
}
|
||||
check(index != -1) { "Unexpected type variable $typeVariable in $mirror" }
|
||||
val argument = mirror.typeArguments[index]
|
||||
return argument.asTypeName()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString() = mirror.toString()
|
||||
|
||||
companion object {
|
||||
fun get(typeElement: TypeElement): AppliedType {
|
||||
return AppliedType(typeElement, TypeResolver(), typeElement.asType() as DeclaredType)
|
||||
}
|
||||
}
|
||||
}
|
@@ -19,6 +19,7 @@ import com.squareup.kotlinpoet.ClassName
|
||||
import com.squareup.kotlinpoet.CodeBlock
|
||||
import com.squareup.kotlinpoet.KModifier
|
||||
import com.squareup.kotlinpoet.NameAllocator
|
||||
import com.squareup.kotlinpoet.ParameterSpec
|
||||
import com.squareup.kotlinpoet.ParameterizedTypeName
|
||||
import com.squareup.kotlinpoet.PropertySpec
|
||||
import com.squareup.kotlinpoet.TypeName
|
||||
@@ -34,25 +35,27 @@ internal data class DelegateKey(
|
||||
) {
|
||||
val nullable get() = type.nullable || type is TypeVariableName
|
||||
|
||||
fun reserveName(nameAllocator: NameAllocator) {
|
||||
/** Returns an adapter to use when encoding and decoding this property. */
|
||||
fun generateProperty(
|
||||
nameAllocator: NameAllocator,
|
||||
typeRenderer: TypeRenderer,
|
||||
moshiParameter: ParameterSpec): PropertySpec {
|
||||
val qualifierNames = jsonQualifiers.joinToString("") {
|
||||
"At${it.annotationType.asElement().simpleName}"
|
||||
}
|
||||
nameAllocator.newName("${type.toVariableName().decapitalize()}${qualifierNames}Adapter", this)
|
||||
}
|
||||
val adapterName = nameAllocator.newName(
|
||||
"${type.toVariableName().decapitalize()}${qualifierNames}Adapter", this)
|
||||
|
||||
/** Returns an adapter to use when encoding and decoding this property. */
|
||||
fun generateProperty(nameAllocator: NameAllocator, enclosing: AdapterGenerator): PropertySpec {
|
||||
val adapterTypeName = ParameterizedTypeName.get(
|
||||
JsonAdapter::class.asTypeName(), type)
|
||||
val qualifiers = jsonQualifiers
|
||||
val standardArgs = arrayOf(enclosing.moshiParam,
|
||||
val standardArgs = arrayOf(moshiParameter,
|
||||
if (type is ClassName && qualifiers.isEmpty()) {
|
||||
""
|
||||
} else {
|
||||
CodeBlock.of("<%T>", type)
|
||||
},
|
||||
type.makeType(enclosing.elements, enclosing.typesParam, enclosing.genericTypeNames))
|
||||
typeRenderer.render(type))
|
||||
val standardArgsSize = standardArgs.size + 1
|
||||
val (initializerString, args) = when {
|
||||
qualifiers.isEmpty() -> "" to emptyArray()
|
||||
@@ -77,7 +80,7 @@ internal data class DelegateKey(
|
||||
|
||||
val nullModifier = if (nullable) ".nullSafe()" else ".nonNull()"
|
||||
|
||||
return PropertySpec.builder(nameAllocator.get(this), adapterTypeName, KModifier.PRIVATE)
|
||||
return PropertySpec.builder(adapterName, adapterTypeName, KModifier.PRIVATE)
|
||||
.initializer("%1N.adapter%2L(%3L$initializerString)$nullModifier", *finalArgs)
|
||||
.build()
|
||||
}
|
||||
|
@@ -120,7 +120,7 @@ class JsonClassCodeGenProcessor : KotlinAbstractProcessor(), KotlinMetadataUtils
|
||||
}
|
||||
}
|
||||
|
||||
return AdapterGenerator(type, sortedProperties, elementUtils)
|
||||
return AdapterGenerator(type, sortedProperties)
|
||||
}
|
||||
|
||||
private fun AdapterGenerator.generateAndWrite(generatedOption: TypeElement?) {
|
||||
|
@@ -36,7 +36,6 @@ import javax.tools.Diagnostic
|
||||
internal data class TargetProperty(
|
||||
val name: String,
|
||||
val type: TypeName,
|
||||
private val typeWithResolvedAliases: TypeName,
|
||||
private val proto: Property,
|
||||
private val parameter: TargetParameter?,
|
||||
private val annotationHolder: ExecutableElement?,
|
||||
@@ -87,7 +86,7 @@ internal data class TargetProperty(
|
||||
return PropertyGenerator(this)
|
||||
}
|
||||
|
||||
fun delegateKey() = DelegateKey(typeWithResolvedAliases, jsonQualifiers())
|
||||
fun delegateKey() = DelegateKey(type, jsonQualifiers())
|
||||
|
||||
/** Returns the JsonQualifiers on the field and parameter of this property. */
|
||||
private fun jsonQualifiers(): Set<AnnotationMirror> {
|
||||
@@ -107,8 +106,7 @@ internal data class TargetProperty(
|
||||
private val Element?.qualifiers: Set<AnnotationMirror>
|
||||
get() {
|
||||
if (this == null) return setOf()
|
||||
return AnnotationMirrors.getAnnotatedAnnotations(this,
|
||||
JsonQualifier::class.java)
|
||||
return AnnotationMirrors.getAnnotatedAnnotations(this, JsonQualifier::class.java)
|
||||
}
|
||||
|
||||
/** Returns the @Json name of this property, or this property's name if none is provided. */
|
||||
|
@@ -41,7 +41,6 @@ import javax.lang.model.element.ElementKind
|
||||
import javax.lang.model.element.ExecutableElement
|
||||
import javax.lang.model.element.TypeElement
|
||||
import javax.lang.model.element.VariableElement
|
||||
import javax.lang.model.type.DeclaredType
|
||||
import javax.lang.model.util.Elements
|
||||
import javax.lang.model.util.Types
|
||||
import javax.tools.Diagnostic.Kind.ERROR
|
||||
@@ -52,7 +51,7 @@ internal data class TargetType(
|
||||
val element: TypeElement,
|
||||
val constructor: TargetConstructor,
|
||||
val properties: Map<String, TargetProperty>,
|
||||
val genericTypeNames: List<TypeVariableName>
|
||||
val typeVariables: List<TypeVariableName>
|
||||
) {
|
||||
val name = element.className
|
||||
val hasCompanionObject = proto.hasCompanionObjectName()
|
||||
@@ -93,31 +92,37 @@ internal data class TargetType(
|
||||
}
|
||||
}
|
||||
|
||||
val typeVariables = genericTypeNames(proto, typeMetadata.data.nameResolver)
|
||||
val appliedType = AppliedType.get(element)
|
||||
|
||||
val constructor = TargetConstructor.primary(typeMetadata, elements)
|
||||
val properties = mutableMapOf<String, TargetProperty>()
|
||||
for (supertype in element.supertypes(types)) {
|
||||
if (supertype.asClassName() == OBJECT_CLASS) {
|
||||
for (supertype in appliedType.supertypes(types)) {
|
||||
if (supertype.element.asClassName() == OBJECT_CLASS) {
|
||||
continue // Don't load properties for java.lang.Object.
|
||||
}
|
||||
if (supertype.kind != ElementKind.CLASS) {
|
||||
if (supertype.element.kind != ElementKind.CLASS) {
|
||||
continue // Don't load properties for interface types.
|
||||
}
|
||||
if (supertype.kotlinMetadata == null) {
|
||||
if (supertype.element.kotlinMetadata == null) {
|
||||
messager.printMessage(ERROR,
|
||||
"@JsonClass can't be applied to $element: supertype $supertype is not a Kotlin type",
|
||||
element)
|
||||
return null
|
||||
}
|
||||
for ((name, property) in declaredProperties(supertype, constructor)) {
|
||||
val supertypeProperties = declaredProperties(
|
||||
supertype.element, supertype.resolver, constructor)
|
||||
for ((name, property) in supertypeProperties) {
|
||||
properties.putIfAbsent(name, property)
|
||||
}
|
||||
}
|
||||
val genericTypeNames = genericTypeNames(proto, typeMetadata.data.nameResolver)
|
||||
return TargetType(proto, element, constructor, properties, genericTypeNames)
|
||||
return TargetType(proto, element, constructor, properties, typeVariables)
|
||||
}
|
||||
|
||||
/** Returns the properties declared by `typeElement`. */
|
||||
private fun declaredProperties(
|
||||
typeElement: TypeElement,
|
||||
typeResolver: TypeResolver,
|
||||
constructor: TargetConstructor
|
||||
): Map<String, TargetProperty> {
|
||||
val typeMetadata: KotlinClassMetadata = typeElement.kotlinMetadata as KotlinClassMetadata
|
||||
@@ -158,13 +163,10 @@ internal data class TargetType(
|
||||
val result = mutableMapOf<String, TargetProperty>()
|
||||
for (property in classProto.propertyList) {
|
||||
val name = nameResolver.getString(property.name)
|
||||
val type = property.returnType.asTypeName(
|
||||
nameResolver, classProto::getTypeParameter, false)
|
||||
val typeWithResolvedAliases = property.returnType.asTypeName(
|
||||
nameResolver, classProto::getTypeParameter, true)
|
||||
result[name] = TargetProperty(name, type, typeWithResolvedAliases, property,
|
||||
constructor.parameters[name], annotationHolders[name], fields[name],
|
||||
setters[name], getters[name])
|
||||
val type = typeResolver.resolve(property.returnType.asTypeName(
|
||||
nameResolver, classProto::getTypeParameter, true))
|
||||
result[name] = TargetProperty(name, type, property, constructor.parameters[name],
|
||||
annotationHolders[name], fields[name], setters[name], getters[name])
|
||||
}
|
||||
|
||||
return result
|
||||
@@ -180,19 +182,6 @@ internal data class TargetType(
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns all supertypes of this, recursively. Includes interface and class supertypes. */
|
||||
private fun TypeElement.supertypes(
|
||||
types: Types,
|
||||
result: MutableSet<TypeElement> = mutableSetOf()
|
||||
): Set<TypeElement> {
|
||||
result.add(this)
|
||||
for (supertype in types.directSupertypes(asType())) {
|
||||
val supertypeElement = (supertype as DeclaredType).asElement() as TypeElement
|
||||
supertypeElement.supertypes(types, result)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private val Element.name get() = simpleName.toString()
|
||||
|
||||
private fun genericTypeNames(proto: Class, nameResolver: NameResolver): List<TypeVariableName> {
|
||||
|
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright (C) 2018 Square, Inc.
|
||||
*
|
||||
* 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
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.squareup.moshi
|
||||
|
||||
import com.squareup.kotlinpoet.ARRAY
|
||||
import com.squareup.kotlinpoet.BOOLEAN
|
||||
import com.squareup.kotlinpoet.BYTE
|
||||
import com.squareup.kotlinpoet.CHAR
|
||||
import com.squareup.kotlinpoet.ClassName
|
||||
import com.squareup.kotlinpoet.CodeBlock
|
||||
import com.squareup.kotlinpoet.DOUBLE
|
||||
import com.squareup.kotlinpoet.FLOAT
|
||||
import com.squareup.kotlinpoet.INT
|
||||
import com.squareup.kotlinpoet.LONG
|
||||
import com.squareup.kotlinpoet.ParameterizedTypeName
|
||||
import com.squareup.kotlinpoet.SHORT
|
||||
import com.squareup.kotlinpoet.TypeName
|
||||
import com.squareup.kotlinpoet.TypeVariableName
|
||||
import com.squareup.kotlinpoet.WildcardTypeName
|
||||
import com.squareup.kotlinpoet.asTypeName
|
||||
|
||||
/**
|
||||
* Renders literals like `Types.newParameterizedType(List::class.java, String::class.java)`.
|
||||
* Rendering is pluggable so that type variables can either be resolved or emitted as other code
|
||||
* blocks.
|
||||
*/
|
||||
abstract class TypeRenderer {
|
||||
abstract fun renderTypeVariable(typeVariable: TypeVariableName): CodeBlock
|
||||
|
||||
fun render(typeName: TypeName): CodeBlock {
|
||||
if (typeName.nullable) {
|
||||
return render(typeName.asNonNullable())
|
||||
}
|
||||
|
||||
return when (typeName) {
|
||||
is ClassName -> CodeBlock.of("%T::class.java", typeName)
|
||||
|
||||
is ParameterizedTypeName -> {
|
||||
// If it's an Array type, we shortcut this to return Types.arrayOf()
|
||||
if (typeName.rawType == ARRAY) {
|
||||
CodeBlock.of("%T.arrayOf(%L)",
|
||||
Types::class,
|
||||
render(typeName.typeArguments[0].objectType()))
|
||||
} else {
|
||||
val placeholders = typeName.typeArguments.joinToString(", ") { "%L" }
|
||||
CodeBlock.of(
|
||||
"%T.newParameterizedType(%T::class.java, $placeholders)",
|
||||
Types::class,
|
||||
typeName.rawType.objectType(),
|
||||
*(typeName.typeArguments.map { render(it.objectType()) }.toTypedArray()))
|
||||
}
|
||||
}
|
||||
|
||||
is WildcardTypeName -> {
|
||||
val target: TypeName
|
||||
val method: String
|
||||
when {
|
||||
typeName.lowerBounds.size == 1 -> {
|
||||
target = typeName.lowerBounds[0]
|
||||
method = "supertypeOf"
|
||||
}
|
||||
typeName.upperBounds.size == 1 -> {
|
||||
target = typeName.upperBounds[0]
|
||||
method = "subtypeOf"
|
||||
}
|
||||
else -> throw IllegalArgumentException(
|
||||
"Unrepresentable wildcard type. Cannot have more than one bound: $typeName")
|
||||
}
|
||||
CodeBlock.of("%T.%L(%T::class.java)", Types::class, method, target)
|
||||
}
|
||||
|
||||
is TypeVariableName -> renderTypeVariable(typeName)
|
||||
|
||||
else -> throw IllegalArgumentException("Unrepresentable type: $typeName")
|
||||
}
|
||||
}
|
||||
|
||||
private fun TypeName.objectType(): TypeName {
|
||||
return when (this) {
|
||||
BOOLEAN -> Boolean::class.javaObjectType.asTypeName()
|
||||
BYTE -> Byte::class.javaObjectType.asTypeName()
|
||||
SHORT -> Short::class.javaObjectType.asTypeName()
|
||||
INT -> Integer::class.javaObjectType.asTypeName()
|
||||
LONG -> Long::class.javaObjectType.asTypeName()
|
||||
CHAR -> Character::class.javaObjectType.asTypeName()
|
||||
FLOAT -> Float::class.javaObjectType.asTypeName()
|
||||
DOUBLE -> Double::class.javaObjectType.asTypeName()
|
||||
else -> this
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (C) 2018 Square, Inc.
|
||||
*
|
||||
* 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
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.squareup.moshi
|
||||
|
||||
import com.squareup.kotlinpoet.ClassName
|
||||
import com.squareup.kotlinpoet.ParameterizedTypeName
|
||||
import com.squareup.kotlinpoet.TypeName
|
||||
import com.squareup.kotlinpoet.TypeVariableName
|
||||
import com.squareup.kotlinpoet.WildcardTypeName
|
||||
|
||||
/**
|
||||
* Resolves type parameters against a type declaration. Use this to fill in type variables with
|
||||
* their actual type parameters.
|
||||
*/
|
||||
open class TypeResolver {
|
||||
open fun resolveTypeVariable(typeVariable: TypeVariableName): TypeName = typeVariable
|
||||
|
||||
fun resolve(typeName: TypeName): TypeName {
|
||||
return when (typeName) {
|
||||
is ClassName -> typeName
|
||||
|
||||
is ParameterizedTypeName -> {
|
||||
ParameterizedTypeName.get(
|
||||
typeName.rawType, *(typeName.typeArguments.map { resolve(it) }.toTypedArray()))
|
||||
}
|
||||
|
||||
is WildcardTypeName -> {
|
||||
when {
|
||||
typeName.lowerBounds.size == 1 -> {
|
||||
WildcardTypeName.supertypeOf(resolve(typeName.lowerBounds[0]))
|
||||
}
|
||||
typeName.upperBounds.size == 1 -> {
|
||||
WildcardTypeName.subtypeOf(resolve(typeName.upperBounds[0]))
|
||||
}
|
||||
else -> {
|
||||
throw IllegalArgumentException(
|
||||
"Unrepresentable wildcard type. Cannot have more than one bound: $typeName")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is TypeVariableName -> resolveTypeVariable(typeName)
|
||||
|
||||
else -> throw IllegalArgumentException("Unrepresentable type: $typeName")
|
||||
}
|
||||
}
|
||||
}
|
@@ -15,25 +15,9 @@
|
||||
*/
|
||||
package com.squareup.moshi
|
||||
|
||||
import com.squareup.kotlinpoet.ARRAY
|
||||
import com.squareup.kotlinpoet.BOOLEAN
|
||||
import com.squareup.kotlinpoet.BYTE
|
||||
import com.squareup.kotlinpoet.CHAR
|
||||
import com.squareup.kotlinpoet.ClassName
|
||||
import com.squareup.kotlinpoet.CodeBlock
|
||||
import com.squareup.kotlinpoet.DOUBLE
|
||||
import com.squareup.kotlinpoet.FLOAT
|
||||
import com.squareup.kotlinpoet.INT
|
||||
import com.squareup.kotlinpoet.LONG
|
||||
import com.squareup.kotlinpoet.ParameterSpec
|
||||
import com.squareup.kotlinpoet.ParameterizedTypeName
|
||||
import com.squareup.kotlinpoet.SHORT
|
||||
import com.squareup.kotlinpoet.TypeName
|
||||
import com.squareup.kotlinpoet.TypeVariableName
|
||||
import com.squareup.kotlinpoet.WildcardTypeName
|
||||
import com.squareup.kotlinpoet.asTypeName
|
||||
import javax.lang.model.element.ElementKind
|
||||
import javax.lang.model.util.Elements
|
||||
|
||||
internal fun TypeName.rawType(): ClassName {
|
||||
return when (this) {
|
||||
@@ -42,98 +26,3 @@ internal fun TypeName.rawType(): ClassName {
|
||||
else -> throw IllegalArgumentException("Cannot get raw type from $this")
|
||||
}
|
||||
}
|
||||
|
||||
private fun ClassName.isClass(elements: Elements): Boolean {
|
||||
val fqcn = toString()
|
||||
if (fqcn.startsWith("kotlin.collections.")) {
|
||||
// These are special kotlin interfaces are only visible in kotlin, because they're replaced by
|
||||
// the compiler with concrete java classes/
|
||||
return false
|
||||
} else if (this == ARRAY) {
|
||||
// This is a "fake" class and not visible to Elements.
|
||||
return true
|
||||
}
|
||||
return elements.getTypeElement(fqcn).kind == ElementKind.INTERFACE
|
||||
}
|
||||
|
||||
private fun TypeName.objectType(): TypeName {
|
||||
return when (this) {
|
||||
BOOLEAN -> Boolean::class.javaObjectType.asTypeName()
|
||||
BYTE -> Byte::class.javaObjectType.asTypeName()
|
||||
SHORT -> Short::class.javaObjectType.asTypeName()
|
||||
INT -> Integer::class.javaObjectType.asTypeName()
|
||||
LONG -> Long::class.javaObjectType.asTypeName()
|
||||
CHAR -> Character::class.javaObjectType.asTypeName()
|
||||
FLOAT -> Float::class.javaObjectType.asTypeName()
|
||||
DOUBLE -> Double::class.javaObjectType.asTypeName()
|
||||
else -> this
|
||||
}
|
||||
}
|
||||
|
||||
internal fun TypeName.makeType(
|
||||
elementUtils: Elements,
|
||||
typesArray: ParameterSpec,
|
||||
genericTypeNames: List<TypeVariableName>
|
||||
): CodeBlock {
|
||||
if (nullable) {
|
||||
return asNonNullable().makeType(elementUtils, typesArray, genericTypeNames)
|
||||
}
|
||||
return when (this) {
|
||||
is ClassName -> CodeBlock.of(
|
||||
"%T::class.java", this)
|
||||
is ParameterizedTypeName -> {
|
||||
// If it's an Array type, we shortcut this to return Types.arrayOf()
|
||||
if (rawType == ARRAY) {
|
||||
return CodeBlock.of("%T.arrayOf(%L)",
|
||||
Types::class,
|
||||
typeArguments[0].objectType().makeType(elementUtils, typesArray, genericTypeNames))
|
||||
}
|
||||
// If it's a Class type, we have to specify the generics.
|
||||
val rawTypeParameters = if (rawType.isClass(elementUtils)) {
|
||||
CodeBlock.of(
|
||||
typeArguments.joinTo(
|
||||
buffer = StringBuilder(),
|
||||
separator = ", ",
|
||||
prefix = "<",
|
||||
postfix = ">") { "%T" }
|
||||
.toString(),
|
||||
*(typeArguments.map { objectType() }.toTypedArray())
|
||||
)
|
||||
} else {
|
||||
CodeBlock.of("")
|
||||
}
|
||||
CodeBlock.of(
|
||||
"%T.newParameterizedType(%T%L::class.java, ${typeArguments
|
||||
.joinToString(", ") { "%L" }})",
|
||||
Types::class,
|
||||
rawType.objectType(),
|
||||
rawTypeParameters,
|
||||
*(typeArguments.map {
|
||||
it.objectType().makeType(elementUtils, typesArray, genericTypeNames)
|
||||
}.toTypedArray()))
|
||||
}
|
||||
is WildcardTypeName -> {
|
||||
val target: TypeName
|
||||
val method: String
|
||||
when {
|
||||
lowerBounds.size == 1 -> {
|
||||
target = lowerBounds[0]
|
||||
method = "supertypeOf"
|
||||
}
|
||||
upperBounds.size == 1 -> {
|
||||
target = upperBounds[0]
|
||||
method = "subtypeOf"
|
||||
}
|
||||
else -> throw IllegalArgumentException(
|
||||
"Unrepresentable wildcard type. Cannot have more than one bound: " + this)
|
||||
}
|
||||
CodeBlock.of("%T.%L(%T::class.java)",
|
||||
Types::class, method, target)
|
||||
}
|
||||
is TypeVariableName -> {
|
||||
CodeBlock.of("%N[%L]", typesArray,
|
||||
genericTypeNames.indexOfFirst { it == this })
|
||||
}
|
||||
else -> throw IllegalArgumentException("Unrepresentable type: " + this)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user