mirror of
https://github.com/fankes/moshi.git
synced 2025-10-20 00:19:21 +08:00
Decouple code gen API from apt and kotlin-metadata (#816)
* Decouple AdapterGenerator from kotlin-metadata * Decouple AdapterGenerator from javax elements API * Extract checkIsVisibility helper * Remove elements and kotlinmetadata from property, constructor, type * Extract kotlinpoet-only parts to new API package for better separation * Add mirroring APIs * Finish extraction of PropertyGenerator to no-elements/no-kotlin-metadata * Update for new tag API in KotlinPoet * Update for new tag API in KotlinPoet * Move rest of elements utils out of API We should split this up better as metadata.kt has become a dumping ground * Move type handling to JsonClassCodegenProcessor * Rebase fix * Opportunistic idiomatic require()
This commit is contained in:
@@ -18,6 +18,7 @@ package com.squareup.moshi.kotlin.codegen
|
|||||||
import com.squareup.kotlinpoet.TypeName
|
import com.squareup.kotlinpoet.TypeName
|
||||||
import com.squareup.kotlinpoet.TypeVariableName
|
import com.squareup.kotlinpoet.TypeVariableName
|
||||||
import com.squareup.kotlinpoet.asTypeName
|
import com.squareup.kotlinpoet.asTypeName
|
||||||
|
import com.squareup.moshi.kotlin.codegen.api.TypeResolver
|
||||||
import javax.lang.model.element.TypeElement
|
import javax.lang.model.element.TypeElement
|
||||||
import javax.lang.model.type.DeclaredType
|
import javax.lang.model.type.DeclaredType
|
||||||
import javax.lang.model.util.Types
|
import javax.lang.model.util.Types
|
||||||
|
@@ -16,9 +16,11 @@
|
|||||||
package com.squareup.moshi.kotlin.codegen
|
package com.squareup.moshi.kotlin.codegen
|
||||||
|
|
||||||
import com.google.auto.service.AutoService
|
import com.google.auto.service.AutoService
|
||||||
|
import com.squareup.kotlinpoet.asClassName
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
import com.squareup.moshi.kotlin.codegen.api.AdapterGenerator
|
||||||
|
import com.squareup.moshi.kotlin.codegen.api.PropertyGenerator
|
||||||
import me.eugeniomarletti.kotlin.metadata.KotlinMetadataUtils
|
import me.eugeniomarletti.kotlin.metadata.KotlinMetadataUtils
|
||||||
import me.eugeniomarletti.kotlin.metadata.declaresDefaultValue
|
|
||||||
import me.eugeniomarletti.kotlin.processing.KotlinAbstractProcessor
|
import me.eugeniomarletti.kotlin.processing.KotlinAbstractProcessor
|
||||||
import net.ltgt.gradle.incap.IncrementalAnnotationProcessor
|
import net.ltgt.gradle.incap.IncrementalAnnotationProcessor
|
||||||
import net.ltgt.gradle.incap.IncrementalAnnotationProcessorType.ISOLATING
|
import net.ltgt.gradle.incap.IncrementalAnnotationProcessorType.ISOLATING
|
||||||
@@ -28,6 +30,7 @@ import javax.annotation.processing.RoundEnvironment
|
|||||||
import javax.lang.model.SourceVersion
|
import javax.lang.model.SourceVersion
|
||||||
import javax.lang.model.element.Element
|
import javax.lang.model.element.Element
|
||||||
import javax.lang.model.element.TypeElement
|
import javax.lang.model.element.TypeElement
|
||||||
|
import javax.lang.model.element.VariableElement
|
||||||
import javax.tools.Diagnostic.Kind.ERROR
|
import javax.tools.Diagnostic.Kind.ERROR
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -74,9 +77,9 @@ class JsonClassCodegenProcessor : KotlinAbstractProcessor(), KotlinMetadataUtils
|
|||||||
override fun init(processingEnv: ProcessingEnvironment) {
|
override fun init(processingEnv: ProcessingEnvironment) {
|
||||||
super.init(processingEnv)
|
super.init(processingEnv)
|
||||||
generatedType = processingEnv.options[OPTION_GENERATED]?.let {
|
generatedType = processingEnv.options[OPTION_GENERATED]?.let {
|
||||||
if (it !in POSSIBLE_GENERATED_NAMES) {
|
require(it in POSSIBLE_GENERATED_NAMES) {
|
||||||
throw IllegalArgumentException("Invalid option value for $OPTION_GENERATED. Found $it, " +
|
"Invalid option value for $OPTION_GENERATED. Found $it, " +
|
||||||
"allowable values are $POSSIBLE_GENERATED_NAMES.")
|
"allowable values are $POSSIBLE_GENERATED_NAMES."
|
||||||
}
|
}
|
||||||
processingEnv.elementUtils.getTypeElement(it)
|
processingEnv.elementUtils.getTypeElement(it)
|
||||||
}
|
}
|
||||||
@@ -87,7 +90,12 @@ class JsonClassCodegenProcessor : KotlinAbstractProcessor(), KotlinMetadataUtils
|
|||||||
val jsonClass = type.getAnnotation(annotation)
|
val jsonClass = type.getAnnotation(annotation)
|
||||||
if (jsonClass.generateAdapter && jsonClass.generator.isEmpty()) {
|
if (jsonClass.generateAdapter && jsonClass.generator.isEmpty()) {
|
||||||
val generator = adapterGenerator(type) ?: continue
|
val generator = adapterGenerator(type) ?: continue
|
||||||
generator.generateFile(generatedType)
|
generator
|
||||||
|
.generateFile(generatedType?.asClassName()) {
|
||||||
|
it.toBuilder()
|
||||||
|
.addOriginatingElement(type)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
.writeTo(filer)
|
.writeTo(filer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -96,7 +104,7 @@ class JsonClassCodegenProcessor : KotlinAbstractProcessor(), KotlinMetadataUtils
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun adapterGenerator(element: Element): AdapterGenerator? {
|
private fun adapterGenerator(element: Element): AdapterGenerator? {
|
||||||
val type = TargetType.get(messager, elementUtils, typeUtils, element) ?: return null
|
val type = targetType(messager, elementUtils, typeUtils, element) ?: return null
|
||||||
|
|
||||||
val properties = mutableMapOf<String, PropertyGenerator>()
|
val properties = mutableMapOf<String, PropertyGenerator>()
|
||||||
for (property in type.properties.values) {
|
for (property in type.properties.values) {
|
||||||
@@ -107,9 +115,9 @@ class JsonClassCodegenProcessor : KotlinAbstractProcessor(), KotlinMetadataUtils
|
|||||||
}
|
}
|
||||||
|
|
||||||
for ((name, parameter) in type.constructor.parameters) {
|
for ((name, parameter) in type.constructor.parameters) {
|
||||||
if (type.properties[parameter.name] == null && !parameter.proto.declaresDefaultValue) {
|
if (type.properties[parameter.name] == null && !parameter.hasDefault) {
|
||||||
messager.printMessage(
|
messager.printMessage(
|
||||||
ERROR, "No property for required constructor parameter $name", parameter.element)
|
ERROR, "No property for required constructor parameter $name", parameter.tag<VariableElement>())
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,63 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.kotlin.codegen
|
|
||||||
|
|
||||||
import me.eugeniomarletti.kotlin.metadata.KotlinClassMetadata
|
|
||||||
import me.eugeniomarletti.kotlin.metadata.isPrimary
|
|
||||||
import me.eugeniomarletti.kotlin.metadata.jvm.getJvmConstructorSignature
|
|
||||||
import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.Constructor
|
|
||||||
import javax.lang.model.element.ElementKind
|
|
||||||
import javax.lang.model.element.ExecutableElement
|
|
||||||
import javax.lang.model.util.Elements
|
|
||||||
|
|
||||||
/** A constructor in user code that should be called by generated code. */
|
|
||||||
internal data class TargetConstructor(
|
|
||||||
val element: ExecutableElement,
|
|
||||||
val proto: Constructor,
|
|
||||||
val parameters: Map<String, TargetParameter>
|
|
||||||
) {
|
|
||||||
companion object {
|
|
||||||
fun primary(metadata: KotlinClassMetadata, elements: Elements): TargetConstructor {
|
|
||||||
val (nameResolver, classProto) = metadata.data
|
|
||||||
|
|
||||||
// todo allow custom constructor
|
|
||||||
val proto = classProto.constructorList
|
|
||||||
.single { it.isPrimary }
|
|
||||||
val constructorJvmSignature = proto.getJvmConstructorSignature(
|
|
||||||
nameResolver, classProto.typeTable)
|
|
||||||
val element = classProto.fqName
|
|
||||||
.let(nameResolver::getString)
|
|
||||||
.replace('/', '.')
|
|
||||||
.let(elements::getTypeElement)
|
|
||||||
.enclosedElements
|
|
||||||
.mapNotNull {
|
|
||||||
it.takeIf { it.kind == ElementKind.CONSTRUCTOR }?.let { it as ExecutableElement }
|
|
||||||
}
|
|
||||||
.first()
|
|
||||||
// TODO Temporary until JVM method signature matching is better
|
|
||||||
// .single { it.jvmMethodSignature == constructorJvmSignature }
|
|
||||||
|
|
||||||
val parameters = mutableMapOf<String, TargetParameter>()
|
|
||||||
for (parameter in proto.valueParameterList) {
|
|
||||||
val name = nameResolver.getString(parameter.name)
|
|
||||||
val index = proto.valueParameterList.indexOf(parameter)
|
|
||||||
parameters[name] = TargetParameter(name, parameter, index, element.parameters[index])
|
|
||||||
}
|
|
||||||
|
|
||||||
return TargetConstructor(element, proto, parameters)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,167 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.kotlin.codegen
|
|
||||||
|
|
||||||
import com.google.auto.common.AnnotationMirrors
|
|
||||||
import com.google.auto.common.MoreTypes
|
|
||||||
import com.squareup.kotlinpoet.AnnotationSpec
|
|
||||||
import com.squareup.kotlinpoet.TypeName
|
|
||||||
import com.squareup.moshi.Json
|
|
||||||
import com.squareup.moshi.JsonQualifier
|
|
||||||
import me.eugeniomarletti.kotlin.metadata.declaresDefaultValue
|
|
||||||
import me.eugeniomarletti.kotlin.metadata.hasSetter
|
|
||||||
import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.Property
|
|
||||||
import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.Visibility.INTERNAL
|
|
||||||
import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.Visibility.PROTECTED
|
|
||||||
import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.Visibility.PUBLIC
|
|
||||||
import me.eugeniomarletti.kotlin.metadata.visibility
|
|
||||||
import java.lang.annotation.ElementType
|
|
||||||
import java.lang.annotation.Retention
|
|
||||||
import java.lang.annotation.RetentionPolicy
|
|
||||||
import java.lang.annotation.Target
|
|
||||||
import javax.annotation.processing.Messager
|
|
||||||
import javax.lang.model.element.AnnotationMirror
|
|
||||||
import javax.lang.model.element.Element
|
|
||||||
import javax.lang.model.element.ExecutableElement
|
|
||||||
import javax.lang.model.element.Modifier
|
|
||||||
import javax.lang.model.element.Name
|
|
||||||
import javax.lang.model.element.VariableElement
|
|
||||||
import javax.tools.Diagnostic
|
|
||||||
|
|
||||||
/** A property in user code that maps to JSON. */
|
|
||||||
internal data class TargetProperty(
|
|
||||||
val name: String,
|
|
||||||
val type: TypeName,
|
|
||||||
private val proto: Property,
|
|
||||||
private val parameter: TargetParameter?,
|
|
||||||
private val annotationHolder: ExecutableElement?,
|
|
||||||
private val field: VariableElement?,
|
|
||||||
private val setter: ExecutableElement?,
|
|
||||||
private val getter: ExecutableElement?
|
|
||||||
) {
|
|
||||||
val parameterIndex get() = parameter?.index ?: -1
|
|
||||||
|
|
||||||
val hasDefault get() = parameter?.proto?.declaresDefaultValue ?: true
|
|
||||||
|
|
||||||
private val isTransient get() = field != null && Modifier.TRANSIENT in field.modifiers
|
|
||||||
|
|
||||||
private val element get() = field ?: setter ?: getter!!
|
|
||||||
|
|
||||||
private val isSettable get() = proto.hasSetter || parameter != null
|
|
||||||
|
|
||||||
private val isVisible: Boolean
|
|
||||||
get() {
|
|
||||||
return proto.visibility == INTERNAL
|
|
||||||
|| proto.visibility == PROTECTED
|
|
||||||
|| proto.visibility == PUBLIC
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a generator for this property, or null if either there is an error and this property
|
|
||||||
* cannot be used with code gen, or if no codegen is necessary for this property.
|
|
||||||
*/
|
|
||||||
fun generator(messager: Messager): PropertyGenerator? {
|
|
||||||
if (isTransient) {
|
|
||||||
if (!hasDefault) {
|
|
||||||
messager.printMessage(
|
|
||||||
Diagnostic.Kind.ERROR, "No default value for transient property ${this}", element)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
return PropertyGenerator(this, DelegateKey(type, emptyList()), true)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isVisible) {
|
|
||||||
messager.printMessage(Diagnostic.Kind.ERROR, "property ${this} is not visible", element)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isSettable) {
|
|
||||||
return null // This property is not settable. Ignore it.
|
|
||||||
}
|
|
||||||
|
|
||||||
val jsonQualifierMirrors = jsonQualifiers()
|
|
||||||
for (jsonQualifier in jsonQualifierMirrors) {
|
|
||||||
// Check Java types since that covers both Java and Kotlin annotations.
|
|
||||||
val annotationElement = MoreTypes.asTypeElement(jsonQualifier.annotationType)
|
|
||||||
annotationElement.getAnnotation(Retention::class.java)?.let {
|
|
||||||
if (it.value != RetentionPolicy.RUNTIME) {
|
|
||||||
messager.printMessage(Diagnostic.Kind.ERROR,
|
|
||||||
"JsonQualifier @${jsonQualifier.simpleName} must have RUNTIME retention")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
annotationElement.getAnnotation(Target::class.java)?.let {
|
|
||||||
if (ElementType.FIELD !in it.value) {
|
|
||||||
messager.printMessage(Diagnostic.Kind.ERROR,
|
|
||||||
"JsonQualifier @${jsonQualifier.simpleName} must support FIELD target")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val jsonQualifierSpecs = jsonQualifierMirrors.map {
|
|
||||||
AnnotationSpec.get(it).toBuilder()
|
|
||||||
.useSiteTarget(AnnotationSpec.UseSiteTarget.FIELD)
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
return PropertyGenerator(this, DelegateKey(type, jsonQualifierSpecs))
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Returns the JsonQualifiers on the field and parameter of this property. */
|
|
||||||
private fun jsonQualifiers(): Set<AnnotationMirror> {
|
|
||||||
val elementQualifiers = element.qualifiers
|
|
||||||
val annotationHolderQualifiers = annotationHolder.qualifiers
|
|
||||||
val parameterQualifiers = parameter?.element.qualifiers
|
|
||||||
|
|
||||||
// TODO(jwilson): union the qualifiers somehow?
|
|
||||||
return when {
|
|
||||||
elementQualifiers.isNotEmpty() -> elementQualifiers
|
|
||||||
annotationHolderQualifiers.isNotEmpty() -> annotationHolderQualifiers
|
|
||||||
parameterQualifiers.isNotEmpty() -> parameterQualifiers
|
|
||||||
else -> setOf()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val Element?.qualifiers: Set<AnnotationMirror>
|
|
||||||
get() {
|
|
||||||
if (this == null) return setOf()
|
|
||||||
return AnnotationMirrors.getAnnotatedAnnotations(this, JsonQualifier::class.java)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Returns the @Json name of this property, or this property's name if none is provided. */
|
|
||||||
fun jsonName(): String {
|
|
||||||
val fieldJsonName = element.jsonName
|
|
||||||
val annotationHolderJsonName = annotationHolder.jsonName
|
|
||||||
val parameterJsonName = parameter?.element.jsonName
|
|
||||||
|
|
||||||
return when {
|
|
||||||
fieldJsonName != null -> fieldJsonName
|
|
||||||
annotationHolderJsonName != null -> annotationHolderJsonName
|
|
||||||
parameterJsonName != null -> parameterJsonName
|
|
||||||
else -> name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val Element?.jsonName: String?
|
|
||||||
get() {
|
|
||||||
if (this == null) return null
|
|
||||||
return getAnnotation(Json::class.java)?.name
|
|
||||||
}
|
|
||||||
|
|
||||||
private val AnnotationMirror.simpleName: Name
|
|
||||||
get() = MoreTypes.asTypeElement(annotationType).simpleName!!
|
|
||||||
|
|
||||||
override fun toString() = name
|
|
||||||
}
|
|
@@ -1,229 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.kotlin.codegen
|
|
||||||
|
|
||||||
import com.squareup.kotlinpoet.ClassName
|
|
||||||
import com.squareup.kotlinpoet.KModifier
|
|
||||||
import com.squareup.kotlinpoet.ParameterizedTypeName
|
|
||||||
import com.squareup.kotlinpoet.TypeVariableName
|
|
||||||
import com.squareup.kotlinpoet.asClassName
|
|
||||||
import com.squareup.kotlinpoet.asTypeName
|
|
||||||
import me.eugeniomarletti.kotlin.metadata.KotlinClassMetadata
|
|
||||||
import me.eugeniomarletti.kotlin.metadata.KotlinMetadata
|
|
||||||
import me.eugeniomarletti.kotlin.metadata.classKind
|
|
||||||
import me.eugeniomarletti.kotlin.metadata.getPropertyOrNull
|
|
||||||
import me.eugeniomarletti.kotlin.metadata.isInnerClass
|
|
||||||
import me.eugeniomarletti.kotlin.metadata.kotlinMetadata
|
|
||||||
import me.eugeniomarletti.kotlin.metadata.modality
|
|
||||||
import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.Class
|
|
||||||
import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.Modality.ABSTRACT
|
|
||||||
import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.TypeParameter
|
|
||||||
import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.Visibility.INTERNAL
|
|
||||||
import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.Visibility.LOCAL
|
|
||||||
import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.Visibility.PUBLIC
|
|
||||||
import me.eugeniomarletti.kotlin.metadata.shadow.metadata.deserialization.NameResolver
|
|
||||||
import me.eugeniomarletti.kotlin.metadata.shadow.util.capitalizeDecapitalize.decapitalizeAsciiOnly
|
|
||||||
import me.eugeniomarletti.kotlin.metadata.visibility
|
|
||||||
import javax.annotation.processing.Messager
|
|
||||||
import javax.lang.model.element.Element
|
|
||||||
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.util.Elements
|
|
||||||
import javax.lang.model.util.Types
|
|
||||||
import javax.tools.Diagnostic.Kind.ERROR
|
|
||||||
|
|
||||||
/** A user type that should be decoded and encoded by generated code. */
|
|
||||||
internal data class TargetType(
|
|
||||||
val proto: Class,
|
|
||||||
val element: TypeElement,
|
|
||||||
val constructor: TargetConstructor,
|
|
||||||
val properties: Map<String, TargetProperty>,
|
|
||||||
val typeVariables: List<TypeVariableName>
|
|
||||||
) {
|
|
||||||
val name = element.className
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private val OBJECT_CLASS = ClassName("java.lang", "Object")
|
|
||||||
|
|
||||||
/** Returns a target type for `element`, or null if it cannot be used with code gen. */
|
|
||||||
fun get(messager: Messager, elements: Elements, types: Types, element: Element): TargetType? {
|
|
||||||
val typeMetadata: KotlinMetadata? = element.kotlinMetadata
|
|
||||||
if (element !is TypeElement || typeMetadata !is KotlinClassMetadata) {
|
|
||||||
messager.printMessage(
|
|
||||||
ERROR, "@JsonClass can't be applied to $element: must be a Kotlin class", element)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
val proto = typeMetadata.data.classProto
|
|
||||||
when {
|
|
||||||
proto.classKind == Class.Kind.ENUM_CLASS -> {
|
|
||||||
messager.printMessage(
|
|
||||||
ERROR, "@JsonClass with 'generateAdapter = \"true\"' can't be applied to $element: code gen for enums is not supported or necessary", element)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
proto.classKind != Class.Kind.CLASS -> {
|
|
||||||
messager.printMessage(
|
|
||||||
ERROR, "@JsonClass can't be applied to $element: must be a Kotlin class", element)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
proto.isInnerClass -> {
|
|
||||||
messager.printMessage(
|
|
||||||
ERROR, "@JsonClass can't be applied to $element: must not be an inner class", element)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
proto.modality == ABSTRACT -> {
|
|
||||||
messager.printMessage(
|
|
||||||
ERROR, "@JsonClass can't be applied to $element: must not be abstract", element)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
proto.visibility == LOCAL -> {
|
|
||||||
messager.printMessage(
|
|
||||||
ERROR, "@JsonClass can't be applied to $element: must not be local", element)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val typeVariables = genericTypeNames(proto, typeMetadata.data.nameResolver)
|
|
||||||
val appliedType = AppliedType.get(element)
|
|
||||||
|
|
||||||
val constructor = TargetConstructor.primary(typeMetadata, elements)
|
|
||||||
if (constructor.proto.visibility != INTERNAL && constructor.proto.visibility != PUBLIC) {
|
|
||||||
messager.printMessage(ERROR, "@JsonClass can't be applied to $element: " +
|
|
||||||
"primary constructor is not internal or public", element)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
val properties = mutableMapOf<String, TargetProperty>()
|
|
||||||
for (supertype in appliedType.supertypes(types)) {
|
|
||||||
if (supertype.element.asClassName() == OBJECT_CLASS) {
|
|
||||||
continue // Don't load properties for java.lang.Object.
|
|
||||||
}
|
|
||||||
if (supertype.element.kind != ElementKind.CLASS) {
|
|
||||||
continue // Don't load properties for interface types.
|
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
|
||||||
val supertypeProperties = declaredProperties(
|
|
||||||
supertype.element, supertype.resolver, constructor)
|
|
||||||
for ((name, property) in supertypeProperties) {
|
|
||||||
properties.putIfAbsent(name, property)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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
|
|
||||||
val nameResolver = typeMetadata.data.nameResolver
|
|
||||||
val classProto = typeMetadata.data.classProto
|
|
||||||
|
|
||||||
val annotationHolders = mutableMapOf<String, ExecutableElement>()
|
|
||||||
val fields = mutableMapOf<String, VariableElement>()
|
|
||||||
val setters = mutableMapOf<String, ExecutableElement>()
|
|
||||||
val getters = mutableMapOf<String, ExecutableElement>()
|
|
||||||
for (element in typeElement.enclosedElements) {
|
|
||||||
if (element is VariableElement) {
|
|
||||||
fields[element.name] = element
|
|
||||||
} else if (element is ExecutableElement) {
|
|
||||||
when {
|
|
||||||
element.name.startsWith("get") -> {
|
|
||||||
val name = element.name.substring("get".length).decapitalizeAsciiOnly()
|
|
||||||
getters[name] = element
|
|
||||||
}
|
|
||||||
element.name.startsWith("is") -> {
|
|
||||||
val name = element.name.substring("is".length).decapitalizeAsciiOnly()
|
|
||||||
getters[name] = element
|
|
||||||
}
|
|
||||||
element.name.startsWith("set") -> {
|
|
||||||
val name = element.name.substring("set".length).decapitalizeAsciiOnly()
|
|
||||||
setters[name] = element
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val propertyProto = typeMetadata.data.getPropertyOrNull(element)
|
|
||||||
if (propertyProto != null) {
|
|
||||||
val name = nameResolver.getString(propertyProto.name)
|
|
||||||
annotationHolders[name] = element
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val result = mutableMapOf<String, TargetProperty>()
|
|
||||||
for (property in classProto.propertyList) {
|
|
||||||
val name = nameResolver.getString(property.name)
|
|
||||||
val type = typeResolver.resolve(property.returnType.asTypeName(
|
|
||||||
nameResolver, classProto::getTypeParameter, false))
|
|
||||||
result[name] = TargetProperty(name, type, property, constructor.parameters[name],
|
|
||||||
annotationHolders[name], fields[name], setters[name], getters[name])
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
private val Element.className: ClassName
|
|
||||||
get() {
|
|
||||||
val typeName = asType().asTypeName()
|
|
||||||
return when (typeName) {
|
|
||||||
is ClassName -> typeName
|
|
||||||
is ParameterizedTypeName -> typeName.rawType
|
|
||||||
else -> throw IllegalStateException("unexpected TypeName: ${typeName::class}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val Element.name get() = simpleName.toString()
|
|
||||||
|
|
||||||
private fun genericTypeNames(proto: Class, nameResolver: NameResolver): List<TypeVariableName> {
|
|
||||||
return proto.typeParameterList.map {
|
|
||||||
val possibleBounds = it.upperBoundList
|
|
||||||
.map { it.asTypeName(nameResolver, proto::getTypeParameter, false) }
|
|
||||||
val typeVar = if (possibleBounds.isEmpty()) {
|
|
||||||
TypeVariableName(
|
|
||||||
name = nameResolver.getString(it.name),
|
|
||||||
variance = it.varianceModifier)
|
|
||||||
} else {
|
|
||||||
TypeVariableName(
|
|
||||||
name = nameResolver.getString(it.name),
|
|
||||||
bounds = *possibleBounds.toTypedArray(),
|
|
||||||
variance = it.varianceModifier)
|
|
||||||
}
|
|
||||||
return@map typeVar.copy(reified = it.reified)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val TypeParameter.varianceModifier: KModifier?
|
|
||||||
get() {
|
|
||||||
return variance.asKModifier().let {
|
|
||||||
// We don't redeclare out variance here
|
|
||||||
if (it == KModifier.OUT) {
|
|
||||||
null
|
|
||||||
} else {
|
|
||||||
it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -13,10 +13,11 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package com.squareup.moshi.kotlin.codegen
|
package com.squareup.moshi.kotlin.codegen.api
|
||||||
|
|
||||||
import com.squareup.kotlinpoet.ARRAY
|
import com.squareup.kotlinpoet.ARRAY
|
||||||
import com.squareup.kotlinpoet.AnnotationSpec
|
import com.squareup.kotlinpoet.AnnotationSpec
|
||||||
|
import com.squareup.kotlinpoet.ClassName
|
||||||
import com.squareup.kotlinpoet.CodeBlock
|
import com.squareup.kotlinpoet.CodeBlock
|
||||||
import com.squareup.kotlinpoet.FileSpec
|
import com.squareup.kotlinpoet.FileSpec
|
||||||
import com.squareup.kotlinpoet.FunSpec
|
import com.squareup.kotlinpoet.FunSpec
|
||||||
@@ -35,11 +36,9 @@ import com.squareup.moshi.JsonReader
|
|||||||
import com.squareup.moshi.JsonWriter
|
import com.squareup.moshi.JsonWriter
|
||||||
import com.squareup.moshi.Moshi
|
import com.squareup.moshi.Moshi
|
||||||
import com.squareup.moshi.internal.Util
|
import com.squareup.moshi.internal.Util
|
||||||
import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.Visibility
|
|
||||||
import me.eugeniomarletti.kotlin.metadata.visibility
|
|
||||||
import java.lang.reflect.Constructor
|
import java.lang.reflect.Constructor
|
||||||
|
import com.squareup.moshi.kotlin.codegen.JsonClassCodegenProcessor
|
||||||
import java.lang.reflect.Type
|
import java.lang.reflect.Type
|
||||||
import javax.lang.model.element.TypeElement
|
|
||||||
|
|
||||||
private val MOSHI_UTIL = Util::class.asClassName()
|
private val MOSHI_UTIL = Util::class.asClassName()
|
||||||
|
|
||||||
@@ -49,14 +48,13 @@ internal class AdapterGenerator(
|
|||||||
private val propertyList: List<PropertyGenerator>
|
private val propertyList: List<PropertyGenerator>
|
||||||
) {
|
) {
|
||||||
private val nonTransientProperties = propertyList.filterNot { it.isTransient }
|
private val nonTransientProperties = propertyList.filterNot { it.isTransient }
|
||||||
private val className = target.name
|
private val className = target.typeName.rawType()
|
||||||
private val visibility = target.proto.visibility!!
|
private val visibility = target.visibility
|
||||||
private val typeVariables = target.typeVariables
|
private val typeVariables = target.typeVariables
|
||||||
|
|
||||||
private val nameAllocator = NameAllocator()
|
private val nameAllocator = NameAllocator()
|
||||||
private val adapterName = "${className.simpleNames.joinToString(separator = "_")}JsonAdapter"
|
private val adapterName = "${className.simpleNames.joinToString(separator = "_")}JsonAdapter"
|
||||||
private val originalElement = target.element
|
private val originalTypeName = target.typeName
|
||||||
private val originalTypeName = target.element.asType().asTypeName()
|
|
||||||
|
|
||||||
private val moshiParam = ParameterSpec.builder(
|
private val moshiParam = ParameterSpec.builder(
|
||||||
nameAllocator.newName("moshi"),
|
nameAllocator.newName("moshi"),
|
||||||
@@ -98,23 +96,22 @@ internal class AdapterGenerator(
|
|||||||
.initializer("null")
|
.initializer("null")
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
fun generateFile(generatedOption: TypeElement?): FileSpec {
|
fun generateFile(generatedOption: ClassName?, typeHook: (TypeSpec) -> TypeSpec = { it }): FileSpec {
|
||||||
for (property in nonTransientProperties) {
|
for (property in nonTransientProperties) {
|
||||||
property.allocateNames(nameAllocator)
|
property.allocateNames(nameAllocator)
|
||||||
}
|
}
|
||||||
|
|
||||||
val result = FileSpec.builder(className.packageName, adapterName)
|
val result = FileSpec.builder(className.packageName, adapterName)
|
||||||
result.addComment("Code generated by moshi-kotlin-codegen. Do not edit.")
|
result.addComment("Code generated by moshi-kotlin-codegen. Do not edit.")
|
||||||
result.addType(generateType(generatedOption))
|
result.addType(generateType(generatedOption).let(typeHook))
|
||||||
return result.build()
|
return result.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun generateType(generatedOption: TypeElement?): TypeSpec {
|
private fun generateType(generatedOption: ClassName?): TypeSpec {
|
||||||
val result = TypeSpec.classBuilder(adapterName)
|
val result = TypeSpec.classBuilder(adapterName)
|
||||||
.addOriginatingElement(originalElement)
|
|
||||||
|
|
||||||
generatedOption?.let {
|
generatedOption?.let {
|
||||||
result.addAnnotation(AnnotationSpec.builder(it.asClassName())
|
result.addAnnotation(AnnotationSpec.builder(it)
|
||||||
.addMember("value = [%S]", JsonClassCodegenProcessor::class.java.canonicalName)
|
.addMember("value = [%S]", JsonClassCodegenProcessor::class.java.canonicalName)
|
||||||
.addMember("comments = %S", "https://github.com/square/moshi")
|
.addMember("comments = %S", "https://github.com/square/moshi")
|
||||||
.build())
|
.build())
|
||||||
@@ -127,7 +124,7 @@ internal class AdapterGenerator(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO make this configurable. Right now it just matches the source model
|
// TODO make this configurable. Right now it just matches the source model
|
||||||
if (visibility == Visibility.INTERNAL) {
|
if (visibility == KModifier.INTERNAL) {
|
||||||
result.addModifiers(KModifier.INTERNAL)
|
result.addModifiers(KModifier.INTERNAL)
|
||||||
}
|
}
|
||||||
|
|
@@ -13,7 +13,7 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package com.squareup.moshi.kotlin.codegen
|
package com.squareup.moshi.kotlin.codegen.api
|
||||||
|
|
||||||
import com.squareup.kotlinpoet.AnnotationSpec
|
import com.squareup.kotlinpoet.AnnotationSpec
|
||||||
import com.squareup.kotlinpoet.ClassName
|
import com.squareup.kotlinpoet.ClassName
|
@@ -13,7 +13,7 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package com.squareup.moshi.kotlin.codegen
|
package com.squareup.moshi.kotlin.codegen.api
|
||||||
|
|
||||||
import com.squareup.kotlinpoet.BOOLEAN
|
import com.squareup.kotlinpoet.BOOLEAN
|
||||||
import com.squareup.kotlinpoet.NameAllocator
|
import com.squareup.kotlinpoet.NameAllocator
|
||||||
@@ -26,7 +26,7 @@ internal class PropertyGenerator(
|
|||||||
val isTransient: Boolean = false
|
val isTransient: Boolean = false
|
||||||
) {
|
) {
|
||||||
val name = target.name
|
val name = target.name
|
||||||
val jsonName = target.jsonName()
|
val jsonName = target.jsonName
|
||||||
val hasDefault = target.hasDefault
|
val hasDefault = target.hasDefault
|
||||||
|
|
||||||
lateinit var localName: String
|
lateinit var localName: String
|
@@ -13,15 +13,16 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package com.squareup.moshi.kotlin.codegen
|
package com.squareup.moshi.kotlin.codegen.api
|
||||||
|
|
||||||
import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.ValueParameter
|
import com.squareup.kotlinpoet.KModifier
|
||||||
import javax.lang.model.element.VariableElement
|
|
||||||
|
|
||||||
/** A parameter in user code that should be populated by generated code. */
|
/** A constructor in user code that should be called by generated code. */
|
||||||
internal data class TargetParameter(
|
internal data class TargetConstructor(
|
||||||
val name: String,
|
val parameters: Map<String, TargetParameter>,
|
||||||
val proto: ValueParameter,
|
val visibility: KModifier
|
||||||
val index: Int,
|
) {
|
||||||
val element: VariableElement
|
init {
|
||||||
)
|
visibility.checkIsVisibility()
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* 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.kotlin.codegen.api
|
||||||
|
|
||||||
|
import com.squareup.kotlinpoet.AnnotationSpec
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
/** A parameter in user code that should be populated by generated code. */
|
||||||
|
internal data class TargetParameter(
|
||||||
|
val name: String,
|
||||||
|
val index: Int,
|
||||||
|
val hasDefault: Boolean,
|
||||||
|
val jsonName: String = name,
|
||||||
|
val qualifiers: Set<AnnotationSpec>? = null,
|
||||||
|
private val tags: Map<KClass<*>, Any>
|
||||||
|
) {
|
||||||
|
/** Returns the tag attached with [type] as a key, or null if no tag is attached with that key. */
|
||||||
|
fun <T : Any> tag(type: KClass<out T>): T? {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
return tags[type] as T?
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the tag attached with [T] as a key, or null if no tag is attached with that key. */
|
||||||
|
inline fun <reified T : Any> tag(): T? = tag(T::class)
|
||||||
|
}
|
@@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* 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.kotlin.codegen.api
|
||||||
|
|
||||||
|
import com.squareup.kotlinpoet.FunSpec
|
||||||
|
import com.squareup.kotlinpoet.KModifier
|
||||||
|
import com.squareup.kotlinpoet.PropertySpec
|
||||||
|
import com.squareup.kotlinpoet.TypeName
|
||||||
|
|
||||||
|
/** A property in user code that maps to JSON. */
|
||||||
|
internal data class TargetProperty(
|
||||||
|
val name: String,
|
||||||
|
val type: TypeName,
|
||||||
|
val parameter: TargetParameter?,
|
||||||
|
val annotationHolder: FunSpec?,
|
||||||
|
val field: PropertySpec?,
|
||||||
|
val setter: FunSpec?,
|
||||||
|
val getter: FunSpec?,
|
||||||
|
val visibility: KModifier,
|
||||||
|
val jsonName: String
|
||||||
|
) {
|
||||||
|
val parameterIndex get() = parameter?.index ?: -1
|
||||||
|
val hasDefault get() = parameter?.hasDefault ?: true
|
||||||
|
|
||||||
|
override fun toString() = name
|
||||||
|
}
|
@@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* 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.kotlin.codegen.api
|
||||||
|
|
||||||
|
import com.squareup.kotlinpoet.KModifier
|
||||||
|
import com.squareup.kotlinpoet.TypeName
|
||||||
|
import com.squareup.kotlinpoet.TypeVariableName
|
||||||
|
|
||||||
|
/** A user type that should be decoded and encoded by generated code. */
|
||||||
|
internal data class TargetType(
|
||||||
|
val typeName: TypeName,
|
||||||
|
val constructor: TargetConstructor,
|
||||||
|
val properties: Map<String, TargetProperty>,
|
||||||
|
val typeVariables: List<TypeVariableName>,
|
||||||
|
val isDataClass: Boolean,
|
||||||
|
val visibility: KModifier
|
||||||
|
) {
|
||||||
|
|
||||||
|
init {
|
||||||
|
visibility.checkIsVisibility()
|
||||||
|
}
|
||||||
|
}
|
@@ -13,7 +13,7 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package com.squareup.moshi.kotlin.codegen
|
package com.squareup.moshi.kotlin.codegen.api
|
||||||
|
|
||||||
import com.squareup.kotlinpoet.ARRAY
|
import com.squareup.kotlinpoet.ARRAY
|
||||||
import com.squareup.kotlinpoet.BOOLEAN
|
import com.squareup.kotlinpoet.BOOLEAN
|
@@ -13,7 +13,7 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package com.squareup.moshi.kotlin.codegen
|
package com.squareup.moshi.kotlin.codegen.api
|
||||||
|
|
||||||
import com.squareup.kotlinpoet.ClassName
|
import com.squareup.kotlinpoet.ClassName
|
||||||
import com.squareup.kotlinpoet.ParameterizedTypeName
|
import com.squareup.kotlinpoet.ParameterizedTypeName
|
@@ -13,7 +13,7 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package com.squareup.moshi.kotlin.codegen
|
package com.squareup.moshi.kotlin.codegen.api
|
||||||
|
|
||||||
import com.squareup.kotlinpoet.BOOLEAN
|
import com.squareup.kotlinpoet.BOOLEAN
|
||||||
import com.squareup.kotlinpoet.BYTE
|
import com.squareup.kotlinpoet.BYTE
|
||||||
@@ -23,6 +23,7 @@ import com.squareup.kotlinpoet.CodeBlock
|
|||||||
import com.squareup.kotlinpoet.DOUBLE
|
import com.squareup.kotlinpoet.DOUBLE
|
||||||
import com.squareup.kotlinpoet.FLOAT
|
import com.squareup.kotlinpoet.FLOAT
|
||||||
import com.squareup.kotlinpoet.INT
|
import com.squareup.kotlinpoet.INT
|
||||||
|
import com.squareup.kotlinpoet.KModifier
|
||||||
import com.squareup.kotlinpoet.LONG
|
import com.squareup.kotlinpoet.LONG
|
||||||
import com.squareup.kotlinpoet.ParameterizedTypeName
|
import com.squareup.kotlinpoet.ParameterizedTypeName
|
||||||
import com.squareup.kotlinpoet.SHORT
|
import com.squareup.kotlinpoet.SHORT
|
||||||
@@ -51,3 +52,9 @@ internal fun TypeName.defaultPrimitiveValue(): CodeBlock =
|
|||||||
UNIT, Void::class.asTypeName() -> throw IllegalStateException("Parameter with void or Unit type is illegal")
|
UNIT, Void::class.asTypeName() -> throw IllegalStateException("Parameter with void or Unit type is illegal")
|
||||||
else -> CodeBlock.of("null")
|
else -> CodeBlock.of("null")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun KModifier.checkIsVisibility() {
|
||||||
|
require(ordinal <= ordinal) {
|
||||||
|
"Visibility must be one of ${(0..ordinal).joinToString { KModifier.values()[it].name }}. Is $name"
|
||||||
|
}
|
||||||
|
}
|
@@ -15,22 +15,90 @@
|
|||||||
*/
|
*/
|
||||||
package com.squareup.moshi.kotlin.codegen
|
package com.squareup.moshi.kotlin.codegen
|
||||||
|
|
||||||
|
import com.google.auto.common.AnnotationMirrors
|
||||||
|
import com.google.auto.common.MoreTypes
|
||||||
|
import com.squareup.kotlinpoet.AnnotationSpec
|
||||||
import com.squareup.kotlinpoet.ClassName
|
import com.squareup.kotlinpoet.ClassName
|
||||||
|
import com.squareup.kotlinpoet.CodeBlock
|
||||||
|
import com.squareup.kotlinpoet.FunSpec
|
||||||
import com.squareup.kotlinpoet.KModifier
|
import com.squareup.kotlinpoet.KModifier
|
||||||
|
import com.squareup.kotlinpoet.KModifier.PUBLIC
|
||||||
|
import com.squareup.kotlinpoet.KModifier.VARARG
|
||||||
|
import com.squareup.kotlinpoet.ParameterSpec
|
||||||
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
|
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
|
||||||
|
import com.squareup.kotlinpoet.PropertySpec
|
||||||
import com.squareup.kotlinpoet.STAR
|
import com.squareup.kotlinpoet.STAR
|
||||||
import com.squareup.kotlinpoet.TypeName
|
import com.squareup.kotlinpoet.TypeName
|
||||||
import com.squareup.kotlinpoet.TypeVariableName
|
import com.squareup.kotlinpoet.TypeVariableName
|
||||||
import com.squareup.kotlinpoet.WildcardTypeName
|
import com.squareup.kotlinpoet.WildcardTypeName
|
||||||
|
import com.squareup.kotlinpoet.asClassName
|
||||||
|
import com.squareup.kotlinpoet.asTypeName
|
||||||
|
import com.squareup.kotlinpoet.asTypeVariableName
|
||||||
|
import com.squareup.kotlinpoet.tag
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonQualifier
|
||||||
|
import com.squareup.moshi.kotlin.codegen.api.DelegateKey
|
||||||
|
import com.squareup.moshi.kotlin.codegen.api.PropertyGenerator
|
||||||
|
import com.squareup.moshi.kotlin.codegen.api.TargetConstructor
|
||||||
|
import com.squareup.moshi.kotlin.codegen.api.TargetParameter
|
||||||
|
import com.squareup.moshi.kotlin.codegen.api.TargetProperty
|
||||||
|
import com.squareup.moshi.kotlin.codegen.api.TargetType
|
||||||
|
import com.squareup.moshi.kotlin.codegen.api.TypeResolver
|
||||||
|
import me.eugeniomarletti.kotlin.metadata.KotlinClassMetadata
|
||||||
|
import me.eugeniomarletti.kotlin.metadata.KotlinMetadata
|
||||||
|
import me.eugeniomarletti.kotlin.metadata.classKind
|
||||||
|
import me.eugeniomarletti.kotlin.metadata.declaresDefaultValue
|
||||||
|
import me.eugeniomarletti.kotlin.metadata.getPropertyOrNull
|
||||||
|
import me.eugeniomarletti.kotlin.metadata.isDataClass
|
||||||
|
import me.eugeniomarletti.kotlin.metadata.isInnerClass
|
||||||
|
import me.eugeniomarletti.kotlin.metadata.isPrimary
|
||||||
|
import me.eugeniomarletti.kotlin.metadata.jvm.getJvmConstructorSignature
|
||||||
|
import me.eugeniomarletti.kotlin.metadata.kotlinMetadata
|
||||||
|
import me.eugeniomarletti.kotlin.metadata.modality
|
||||||
|
import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.Class
|
||||||
|
import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.Modality.ABSTRACT
|
||||||
import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.Type
|
import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.Type
|
||||||
import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.TypeParameter
|
import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.TypeParameter
|
||||||
import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.TypeParameter.Variance
|
import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.TypeParameter.Variance
|
||||||
|
import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.Visibility
|
||||||
|
import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.Visibility.INTERNAL
|
||||||
|
import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.Visibility.LOCAL
|
||||||
|
import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.Visibility.PRIVATE
|
||||||
|
import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.Visibility.PRIVATE_TO_THIS
|
||||||
|
import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.Visibility.PROTECTED
|
||||||
import me.eugeniomarletti.kotlin.metadata.shadow.metadata.deserialization.NameResolver
|
import me.eugeniomarletti.kotlin.metadata.shadow.metadata.deserialization.NameResolver
|
||||||
|
import me.eugeniomarletti.kotlin.metadata.shadow.util.capitalizeDecapitalize.decapitalizeAsciiOnly
|
||||||
|
import me.eugeniomarletti.kotlin.metadata.visibility
|
||||||
|
import java.lang.annotation.ElementType
|
||||||
|
import java.lang.annotation.Retention
|
||||||
|
import java.lang.annotation.RetentionPolicy
|
||||||
|
import java.lang.annotation.Target
|
||||||
|
import javax.annotation.processing.Messager
|
||||||
|
import javax.lang.model.element.AnnotationMirror
|
||||||
|
import javax.lang.model.element.Element
|
||||||
|
import javax.lang.model.element.ElementKind
|
||||||
|
import javax.lang.model.element.ExecutableElement
|
||||||
|
import javax.lang.model.element.Modifier
|
||||||
|
import javax.lang.model.element.Modifier.DEFAULT
|
||||||
|
import javax.lang.model.element.Modifier.FINAL
|
||||||
|
import javax.lang.model.element.Modifier.NATIVE
|
||||||
|
import javax.lang.model.element.Modifier.STATIC
|
||||||
|
import javax.lang.model.element.Modifier.SYNCHRONIZED
|
||||||
|
import javax.lang.model.element.Modifier.TRANSIENT
|
||||||
|
import javax.lang.model.element.Modifier.VOLATILE
|
||||||
|
import javax.lang.model.element.TypeElement
|
||||||
|
import javax.lang.model.element.VariableElement
|
||||||
|
import javax.lang.model.type.TypeVariable
|
||||||
|
import javax.lang.model.util.Elements
|
||||||
|
import javax.lang.model.util.Types
|
||||||
|
import javax.tools.Diagnostic
|
||||||
|
import javax.tools.Diagnostic.Kind.ERROR
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
internal fun TypeParameter.asTypeName(
|
private fun TypeParameter.asTypeName(
|
||||||
nameResolver: NameResolver,
|
nameResolver: NameResolver,
|
||||||
getTypeParameter: (index: Int) -> TypeParameter,
|
getTypeParameter: (index: Int) -> TypeParameter,
|
||||||
resolveAliases: Boolean = false
|
resolveAliases: Boolean = false
|
||||||
): TypeVariableName {
|
): TypeVariableName {
|
||||||
val possibleBounds = upperBoundList.map {
|
val possibleBounds = upperBoundList.map {
|
||||||
it.asTypeName(nameResolver, getTypeParameter, resolveAliases)
|
it.asTypeName(nameResolver, getTypeParameter, resolveAliases)
|
||||||
@@ -47,7 +115,7 @@ internal fun TypeParameter.asTypeName(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun TypeParameter.Variance.asKModifier(): KModifier? {
|
private fun TypeParameter.Variance.asKModifier(): KModifier? {
|
||||||
return when (this) {
|
return when (this) {
|
||||||
Variance.IN -> KModifier.IN
|
Variance.IN -> KModifier.IN
|
||||||
Variance.OUT -> KModifier.OUT
|
Variance.OUT -> KModifier.OUT
|
||||||
@@ -55,6 +123,18 @@ internal fun TypeParameter.Variance.asKModifier(): KModifier? {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun Visibility?.asKModifier(): KModifier {
|
||||||
|
return when (this) {
|
||||||
|
INTERNAL -> KModifier.INTERNAL
|
||||||
|
PRIVATE -> KModifier.PRIVATE
|
||||||
|
PROTECTED -> KModifier.PROTECTED
|
||||||
|
Visibility.PUBLIC -> KModifier.PUBLIC
|
||||||
|
PRIVATE_TO_THIS -> KModifier.PRIVATE
|
||||||
|
LOCAL -> KModifier.PRIVATE
|
||||||
|
else -> PUBLIC
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the TypeName of this type as it would be seen in the source code, including nullability
|
* Returns the TypeName of this type as it would be seen in the source code, including nullability
|
||||||
* and generic type parameters.
|
* and generic type parameters.
|
||||||
@@ -63,10 +143,10 @@ internal fun TypeParameter.Variance.asKModifier(): KModifier? {
|
|||||||
* @param [getTypeParameter] a function that returns the type parameter for the given index. **Only
|
* @param [getTypeParameter] a function that returns the type parameter for the given index. **Only
|
||||||
* called if [ProtoBuf.Type.hasTypeParameter] is true!**
|
* called if [ProtoBuf.Type.hasTypeParameter] is true!**
|
||||||
*/
|
*/
|
||||||
internal fun Type.asTypeName(
|
private fun Type.asTypeName(
|
||||||
nameResolver: NameResolver,
|
nameResolver: NameResolver,
|
||||||
getTypeParameter: (index: Int) -> TypeParameter,
|
getTypeParameter: (index: Int) -> TypeParameter,
|
||||||
useAbbreviatedType: Boolean = true
|
useAbbreviatedType: Boolean = true
|
||||||
): TypeName {
|
): TypeName {
|
||||||
|
|
||||||
val argumentList = when {
|
val argumentList = when {
|
||||||
@@ -123,3 +203,462 @@ internal fun Type.asTypeName(
|
|||||||
|
|
||||||
return typeName.copy(nullable = nullable)
|
return typeName.copy(nullable = nullable)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun primaryConstructor(metadata: KotlinClassMetadata,
|
||||||
|
elements: Elements): TargetConstructor {
|
||||||
|
val (nameResolver, classProto) = metadata.data
|
||||||
|
|
||||||
|
// TODO allow custom constructor
|
||||||
|
val proto = classProto.constructorList
|
||||||
|
.single { it.isPrimary }
|
||||||
|
val constructorJvmSignature = proto.getJvmConstructorSignature(
|
||||||
|
nameResolver, classProto.typeTable)
|
||||||
|
val element = classProto.fqName
|
||||||
|
.let(nameResolver::getString)
|
||||||
|
.replace('/', '.')
|
||||||
|
.let(elements::getTypeElement)
|
||||||
|
.enclosedElements
|
||||||
|
.mapNotNull {
|
||||||
|
it.takeIf { it.kind == ElementKind.CONSTRUCTOR }?.let { it as ExecutableElement }
|
||||||
|
}
|
||||||
|
.first()
|
||||||
|
// TODO Temporary until JVM method signature matching is better
|
||||||
|
// .single { it.jvmMethodSignature == constructorJvmSignature }
|
||||||
|
|
||||||
|
val parameters = mutableMapOf<String, TargetParameter>()
|
||||||
|
for (parameter in proto.valueParameterList) {
|
||||||
|
val name = nameResolver.getString(parameter.name)
|
||||||
|
val index = proto.valueParameterList.indexOf(parameter)
|
||||||
|
val paramElement = element.parameters[index]
|
||||||
|
parameters[name] = TargetParameter(
|
||||||
|
name = name,
|
||||||
|
index = index,
|
||||||
|
hasDefault = parameter.declaresDefaultValue,
|
||||||
|
qualifiers = paramElement.qualifiers.mapTo(mutableSetOf(), AnnotationMirror::asAnnotationSpec),
|
||||||
|
jsonName = paramElement.jsonName ?: name,
|
||||||
|
tags = mapOf(VariableElement::class to paramElement)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return TargetConstructor(parameters,
|
||||||
|
proto.visibility.asKModifier())
|
||||||
|
}
|
||||||
|
|
||||||
|
private val OBJECT_CLASS = ClassName("java.lang", "Object")
|
||||||
|
|
||||||
|
/** Returns a target type for `element`, or null if it cannot be used with code gen. */
|
||||||
|
internal fun targetType(messager: Messager,
|
||||||
|
elements: Elements,
|
||||||
|
types: Types,
|
||||||
|
element: Element): TargetType? {
|
||||||
|
val typeMetadata: KotlinMetadata? = element.kotlinMetadata
|
||||||
|
if (element !is TypeElement || typeMetadata !is KotlinClassMetadata) {
|
||||||
|
messager.printMessage(
|
||||||
|
ERROR, "@JsonClass can't be applied to $element: must be a Kotlin class", element)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val proto = typeMetadata.data.classProto
|
||||||
|
when {
|
||||||
|
proto.classKind == Class.Kind.ENUM_CLASS -> {
|
||||||
|
messager.printMessage(
|
||||||
|
ERROR,
|
||||||
|
"@JsonClass with 'generateAdapter = \"true\"' can't be applied to $element: code gen for enums is not supported or necessary",
|
||||||
|
element)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
proto.classKind != Class.Kind.CLASS -> {
|
||||||
|
messager.printMessage(
|
||||||
|
ERROR, "@JsonClass can't be applied to $element: must be a Kotlin class", element)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
proto.isInnerClass -> {
|
||||||
|
messager.printMessage(
|
||||||
|
ERROR, "@JsonClass can't be applied to $element: must not be an inner class", element)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
proto.modality == ABSTRACT -> {
|
||||||
|
messager.printMessage(
|
||||||
|
ERROR, "@JsonClass can't be applied to $element: must not be abstract", element)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
proto.visibility == LOCAL -> {
|
||||||
|
messager.printMessage(
|
||||||
|
ERROR, "@JsonClass can't be applied to $element: must not be local", element)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val typeVariables = genericTypeNames(proto, typeMetadata.data.nameResolver)
|
||||||
|
val appliedType = AppliedType.get(element)
|
||||||
|
|
||||||
|
val constructor = primaryConstructor(typeMetadata, elements)
|
||||||
|
if (constructor.visibility != KModifier.INTERNAL && constructor.visibility != KModifier.PUBLIC) {
|
||||||
|
messager.printMessage(ERROR, "@JsonClass can't be applied to $element: " +
|
||||||
|
"primary constructor is not internal or public", element)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val properties = mutableMapOf<String, TargetProperty>()
|
||||||
|
for (supertype in appliedType.supertypes(types)) {
|
||||||
|
if (supertype.element.asClassName() == OBJECT_CLASS) {
|
||||||
|
continue // Don't load properties for java.lang.Object.
|
||||||
|
}
|
||||||
|
if (supertype.element.kind != ElementKind.CLASS) {
|
||||||
|
continue // Don't load properties for interface types.
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
val supertypeProperties = declaredProperties(
|
||||||
|
supertype.element, supertype.resolver, constructor)
|
||||||
|
for ((name, property) in supertypeProperties) {
|
||||||
|
properties.putIfAbsent(name, property)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return TargetType(
|
||||||
|
typeName = element.asType().asTypeName(),
|
||||||
|
constructor = constructor,
|
||||||
|
properties = properties,
|
||||||
|
typeVariables = typeVariables,
|
||||||
|
isDataClass = proto.isDataClass,
|
||||||
|
visibility = proto.visibility.asKModifier())
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 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
|
||||||
|
val nameResolver = typeMetadata.data.nameResolver
|
||||||
|
val classProto = typeMetadata.data.classProto
|
||||||
|
|
||||||
|
val annotationHolders = mutableMapOf<String, ExecutableElement>()
|
||||||
|
val fields = mutableMapOf<String, VariableElement>()
|
||||||
|
val setters = mutableMapOf<String, ExecutableElement>()
|
||||||
|
val getters = mutableMapOf<String, ExecutableElement>()
|
||||||
|
for (element in typeElement.enclosedElements) {
|
||||||
|
if (element is VariableElement) {
|
||||||
|
fields[element.name] = element
|
||||||
|
} else if (element is ExecutableElement) {
|
||||||
|
when {
|
||||||
|
element.name.startsWith("get") -> {
|
||||||
|
val name = element.name.substring("get".length).decapitalizeAsciiOnly()
|
||||||
|
getters[name] = element
|
||||||
|
}
|
||||||
|
element.name.startsWith("is") -> {
|
||||||
|
val name = element.name.substring("is".length).decapitalizeAsciiOnly()
|
||||||
|
getters[name] = element
|
||||||
|
}
|
||||||
|
element.name.startsWith("set") -> {
|
||||||
|
val name = element.name.substring("set".length).decapitalizeAsciiOnly()
|
||||||
|
setters[name] = element
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val propertyProto = typeMetadata.data.getPropertyOrNull(element)
|
||||||
|
if (propertyProto != null) {
|
||||||
|
val name = nameResolver.getString(propertyProto.name)
|
||||||
|
annotationHolders[name] = element
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val result = mutableMapOf<String, TargetProperty>()
|
||||||
|
for (property in classProto.propertyList) {
|
||||||
|
val name = nameResolver.getString(property.name)
|
||||||
|
val type = typeResolver.resolve(property.returnType.asTypeName(
|
||||||
|
nameResolver, classProto::getTypeParameter, false))
|
||||||
|
val parameter = constructor.parameters[name]
|
||||||
|
val fieldElement = fields[name]
|
||||||
|
val annotationHolder = annotationHolders[name]
|
||||||
|
// Used for setter/getter/is lookups. Guaranteed to be safe because kotlin doesn't allow you to
|
||||||
|
// have both "AAA" and "aAA".
|
||||||
|
val decapitalizedName = name.decapitalizeAsciiOnly()
|
||||||
|
result[name] = TargetProperty(name = name,
|
||||||
|
type = type,
|
||||||
|
parameter = parameter,
|
||||||
|
annotationHolder = annotationHolder?.asFunSpec(),
|
||||||
|
field = fieldElement?.asPropertySpec(),
|
||||||
|
setter = setters[decapitalizedName]?.asFunSpec(),
|
||||||
|
getter = getters[decapitalizedName]?.asFunSpec(),
|
||||||
|
visibility = property.visibility.asKModifier(),
|
||||||
|
jsonName = jsonName(
|
||||||
|
fieldElement = fieldElement,
|
||||||
|
parameter = parameter,
|
||||||
|
annotationHolder = annotationHolder,
|
||||||
|
name = name
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the @Json name of this property, or this property's name if none is provided. */
|
||||||
|
private fun jsonName(
|
||||||
|
fieldElement: Element?,
|
||||||
|
parameter: TargetParameter?,
|
||||||
|
annotationHolder: ExecutableElement?,
|
||||||
|
name: String): String {
|
||||||
|
val fieldJsonName = fieldElement.jsonName
|
||||||
|
val annotationHolderJsonName = annotationHolder.jsonName
|
||||||
|
val parameterJsonName = parameter?.jsonName
|
||||||
|
|
||||||
|
return when {
|
||||||
|
fieldJsonName != null -> fieldJsonName
|
||||||
|
annotationHolderJsonName != null -> annotationHolderJsonName
|
||||||
|
parameterJsonName != null -> parameterJsonName
|
||||||
|
else -> name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val Element.name get() = simpleName.toString()
|
||||||
|
|
||||||
|
private fun genericTypeNames(proto: Class, nameResolver: NameResolver): List<TypeVariableName> {
|
||||||
|
return proto.typeParameterList.map { typeParameter ->
|
||||||
|
val possibleBounds = typeParameter.upperBoundList
|
||||||
|
.map { it.asTypeName(nameResolver, proto::getTypeParameter, false) }
|
||||||
|
val typeVar = if (possibleBounds.isEmpty()) {
|
||||||
|
TypeVariableName(
|
||||||
|
name = nameResolver.getString(typeParameter.name),
|
||||||
|
variance = typeParameter.varianceModifier)
|
||||||
|
} else {
|
||||||
|
TypeVariableName(
|
||||||
|
name = nameResolver.getString(typeParameter.name),
|
||||||
|
bounds = *possibleBounds.toTypedArray(),
|
||||||
|
variance = typeParameter.varianceModifier)
|
||||||
|
}
|
||||||
|
return@map typeVar.copy(reified = typeParameter.reified)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val TypeParameter.varianceModifier: KModifier?
|
||||||
|
get() {
|
||||||
|
return variance.asKModifier().let {
|
||||||
|
// We don't redeclare out variance here
|
||||||
|
if (it == KModifier.OUT) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a new [PropertySpec] representation of [this].
|
||||||
|
*
|
||||||
|
* This will copy its name, type, visibility modifiers, constant value, and annotations. Note that
|
||||||
|
* Java modifiers that correspond to annotations in kotlin will be added as well (`volatile`,
|
||||||
|
* `transient`, etc`.
|
||||||
|
*
|
||||||
|
* The original `field` ([this]) is stored in [PropertySpec.tag].
|
||||||
|
*/
|
||||||
|
internal fun VariableElement.asPropertySpec(asJvmField: Boolean = false): PropertySpec {
|
||||||
|
require(kind == ElementKind.FIELD) {
|
||||||
|
"Must be a field!"
|
||||||
|
}
|
||||||
|
val modifiers: Set<Modifier> = modifiers
|
||||||
|
val fieldName = simpleName.toString()
|
||||||
|
val propertyBuilder = PropertySpec.builder(fieldName, asType().asTypeName())
|
||||||
|
propertyBuilder.addModifiers(*modifiers.mapNotNull { it.asKModifier() }.toTypedArray())
|
||||||
|
constantValue?.let {
|
||||||
|
if (it is String) {
|
||||||
|
propertyBuilder.initializer(CodeBlock.of("%S", it))
|
||||||
|
} else {
|
||||||
|
propertyBuilder.initializer(CodeBlock.of("%L", it))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
propertyBuilder.addAnnotations(annotationMirrors.map(AnnotationMirror::asAnnotationSpec))
|
||||||
|
propertyBuilder.addAnnotations(modifiers.mapNotNull { it.asAnnotation() })
|
||||||
|
propertyBuilder.tag(this)
|
||||||
|
if (asJvmField && KModifier.PRIVATE !in propertyBuilder.modifiers) {
|
||||||
|
propertyBuilder.addAnnotation(JvmField::class)
|
||||||
|
}
|
||||||
|
return propertyBuilder.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a new [AnnotationSpec] representation of [this].
|
||||||
|
*
|
||||||
|
* Identical and delegates to [AnnotationSpec.get], but the original `mirror` is also stored
|
||||||
|
* in [AnnotationSpec.tag].
|
||||||
|
*/
|
||||||
|
internal fun AnnotationMirror.asAnnotationSpec(): AnnotationSpec {
|
||||||
|
return AnnotationSpec.get(this)
|
||||||
|
.toBuilder()
|
||||||
|
.tag(MoreTypes.asTypeElement(annotationType))
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a new [FunSpec] representation of [this].
|
||||||
|
*
|
||||||
|
* This will copy its visibility modifiers, type parameters, return type, name, parameters, and
|
||||||
|
* throws declarations.
|
||||||
|
*
|
||||||
|
* The original `method` ([this]) is stored in [FunSpec.tag].
|
||||||
|
*
|
||||||
|
* Nearly identical to [FunSpec.overriding], but no override modifier is added nor are checks around
|
||||||
|
* overridability done
|
||||||
|
*/
|
||||||
|
internal fun ExecutableElement.asFunSpec(): FunSpec {
|
||||||
|
var modifiers: Set<Modifier> = modifiers
|
||||||
|
val methodName = simpleName.toString()
|
||||||
|
val funBuilder = FunSpec.builder(methodName)
|
||||||
|
|
||||||
|
modifiers = modifiers.toMutableSet()
|
||||||
|
funBuilder.jvmModifiers(modifiers)
|
||||||
|
|
||||||
|
typeParameters
|
||||||
|
.map { it.asType() as TypeVariable }
|
||||||
|
.map { it.asTypeVariableName() }
|
||||||
|
.forEach { funBuilder.addTypeVariable(it) }
|
||||||
|
|
||||||
|
funBuilder.returns(returnType.asTypeName())
|
||||||
|
funBuilder.addParameters(ParameterSpec.parametersOf(this))
|
||||||
|
if (isVarArgs) {
|
||||||
|
funBuilder.parameters[funBuilder.parameters.lastIndex] = funBuilder.parameters.last()
|
||||||
|
.toBuilder()
|
||||||
|
.addModifiers(VARARG)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (thrownTypes.isNotEmpty()) {
|
||||||
|
val throwsValueString = thrownTypes.joinToString { "%T::class" }
|
||||||
|
funBuilder.addAnnotation(AnnotationSpec.builder(Throws::class)
|
||||||
|
.addMember(throwsValueString, *thrownTypes.toTypedArray())
|
||||||
|
.build())
|
||||||
|
}
|
||||||
|
|
||||||
|
funBuilder.tag(this)
|
||||||
|
return funBuilder.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
private val TargetProperty.isTransient get() = field != null && field.annotations.any { it.className == Transient::class.asClassName() }
|
||||||
|
private val TargetProperty.isSettable get() = setter != null || parameter != null
|
||||||
|
private val TargetProperty.isVisible: Boolean
|
||||||
|
get() {
|
||||||
|
return visibility == KModifier.INTERNAL
|
||||||
|
|| visibility == KModifier.PROTECTED
|
||||||
|
|| visibility == KModifier.PUBLIC
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a generator for this property, or null if either there is an error and this property
|
||||||
|
* cannot be used with code gen, or if no codegen is necessary for this property.
|
||||||
|
*/
|
||||||
|
internal fun TargetProperty.generator(messager: Messager): PropertyGenerator? {
|
||||||
|
val element = field?.tag<VariableElement>() ?: setter?.tag<ExecutableElement>()
|
||||||
|
?: getter!!.tag<ExecutableElement>()
|
||||||
|
if (isTransient) {
|
||||||
|
if (!hasDefault) {
|
||||||
|
element?.let {
|
||||||
|
messager.printMessage(
|
||||||
|
Diagnostic.Kind.ERROR, "No default value for transient property ${this}",
|
||||||
|
it)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return PropertyGenerator(this, DelegateKey(type, emptyList()), true)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isVisible) {
|
||||||
|
element?.let {
|
||||||
|
messager.printMessage(Diagnostic.Kind.ERROR, "property ${this} is not visible",
|
||||||
|
it)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isSettable) {
|
||||||
|
return null // This property is not settable. Ignore it.
|
||||||
|
}
|
||||||
|
|
||||||
|
val jsonQualifierMirrors = jsonQualifiers(element)
|
||||||
|
for (jsonQualifier in jsonQualifierMirrors) {
|
||||||
|
// Check Java types since that covers both Java and Kotlin annotations.
|
||||||
|
val annotationElement = jsonQualifier.tag<TypeElement>() ?: continue
|
||||||
|
annotationElement.getAnnotation(Retention::class.java)?.let {
|
||||||
|
if (it.value != RetentionPolicy.RUNTIME) {
|
||||||
|
messager.printMessage(Diagnostic.Kind.ERROR,
|
||||||
|
"JsonQualifier @${jsonQualifier.className.simpleName} must have RUNTIME retention")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
annotationElement.getAnnotation(Target::class.java)?.let {
|
||||||
|
if (ElementType.FIELD !in it.value) {
|
||||||
|
messager.printMessage(Diagnostic.Kind.ERROR,
|
||||||
|
"JsonQualifier @${jsonQualifier.className.simpleName} must support FIELD target")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val jsonQualifierSpecs = jsonQualifierMirrors.map {
|
||||||
|
it.toBuilder()
|
||||||
|
.useSiteTarget(AnnotationSpec.UseSiteTarget.FIELD)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
return PropertyGenerator(this,
|
||||||
|
DelegateKey(type, jsonQualifierSpecs))
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the JsonQualifiers on the field and parameter of this property. */
|
||||||
|
private fun TargetProperty.jsonQualifiers(element: Element?): Set<AnnotationSpec> {
|
||||||
|
val elementQualifiers = element.qualifiers
|
||||||
|
val annotationHolderQualifiers = annotationHolder?.tag<ExecutableElement>().qualifiers
|
||||||
|
val parameterQualifiers = parameter?.qualifiers.orEmpty()
|
||||||
|
|
||||||
|
// TODO(jwilson): union the qualifiers somehow?
|
||||||
|
return when {
|
||||||
|
elementQualifiers.isNotEmpty() -> elementQualifiers.mapTo(mutableSetOf(), AnnotationMirror::asAnnotationSpec)
|
||||||
|
annotationHolderQualifiers.isNotEmpty() -> annotationHolderQualifiers.mapTo(
|
||||||
|
mutableSetOf(), AnnotationMirror::asAnnotationSpec)
|
||||||
|
parameterQualifiers.isNotEmpty() -> parameterQualifiers
|
||||||
|
else -> setOf()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Modifier.asKModifier(): KModifier? {
|
||||||
|
return when (this) {
|
||||||
|
Modifier.PUBLIC -> KModifier.PUBLIC
|
||||||
|
Modifier.PROTECTED -> KModifier.PROTECTED
|
||||||
|
Modifier.PRIVATE -> KModifier.PRIVATE
|
||||||
|
Modifier.ABSTRACT -> KModifier.ABSTRACT
|
||||||
|
FINAL -> KModifier.FINAL
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Modifier.asAnnotation(): AnnotationSpec? {
|
||||||
|
return when (this) {
|
||||||
|
DEFAULT -> JvmDefault::class.asAnnotationSpec()
|
||||||
|
STATIC -> JvmStatic::class.asAnnotationSpec()
|
||||||
|
TRANSIENT -> Transient::class.asAnnotationSpec()
|
||||||
|
VOLATILE -> Volatile::class.asAnnotationSpec()
|
||||||
|
SYNCHRONIZED -> Synchronized::class.asAnnotationSpec()
|
||||||
|
NATIVE -> JvmDefault::class.asAnnotationSpec()
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun <T : Annotation> KClass<T>.asAnnotationSpec(): AnnotationSpec {
|
||||||
|
return AnnotationSpec.builder(this).build()
|
||||||
|
}
|
||||||
|
|
||||||
|
private val Element?.qualifiers: Set<AnnotationMirror>
|
||||||
|
get() {
|
||||||
|
if (this == null) return setOf()
|
||||||
|
return AnnotationMirrors.getAnnotatedAnnotations(this, JsonQualifier::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val Element?.jsonName: String?
|
||||||
|
get() {
|
||||||
|
if (this == null) return null
|
||||||
|
return getAnnotation(Json::class.java)?.name
|
||||||
|
}
|
||||||
|
@@ -19,6 +19,7 @@ import com.google.common.truth.Truth.assertThat
|
|||||||
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.plusParameter
|
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.plusParameter
|
||||||
import com.squareup.kotlinpoet.WildcardTypeName
|
import com.squareup.kotlinpoet.WildcardTypeName
|
||||||
import com.squareup.kotlinpoet.asClassName
|
import com.squareup.kotlinpoet.asClassName
|
||||||
|
import com.squareup.moshi.kotlin.codegen.api.TypeResolver
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
class TypeResolverTest {
|
class TypeResolverTest {
|
||||||
|
Reference in New Issue
Block a user