From 98c7d7e71c9efee77b955a2e06cf22d29707ac7a Mon Sep 17 00:00:00 2001 From: Zac Sweers Date: Sun, 24 Feb 2019 22:59:34 -0500 Subject: [PATCH] Finish extraction of PropertyGenerator to no-elements/no-kotlin-metadata --- .../codegen/JsonClassCodegenProcessor.kt | 3 +- .../moshi/kotlin/codegen/TargetProperty.kt | 158 -------------- .../kotlin/codegen/api/PropertyGenerator.kt | 3 +- .../kotlin/codegen/api/TargetParameter.kt | 15 +- .../kotlin/codegen/api/TargetProperty.kt | 39 ++++ .../moshi/kotlin/codegen/api/TargetType.kt | 1 - .../squareup/moshi/kotlin/codegen/metadata.kt | 202 +++++++++++++++--- 7 files changed, 230 insertions(+), 191 deletions(-) delete mode 100644 kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/TargetProperty.kt create mode 100644 kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/TargetProperty.kt diff --git a/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/JsonClassCodegenProcessor.kt b/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/JsonClassCodegenProcessor.kt index 90f9ece..354c5f3 100644 --- a/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/JsonClassCodegenProcessor.kt +++ b/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/JsonClassCodegenProcessor.kt @@ -30,6 +30,7 @@ import javax.annotation.processing.RoundEnvironment import javax.lang.model.SourceVersion import javax.lang.model.element.Element import javax.lang.model.element.TypeElement +import javax.lang.model.element.VariableElement import javax.tools.Diagnostic.Kind.ERROR /** @@ -111,7 +112,7 @@ class JsonClassCodegenProcessor : KotlinAbstractProcessor(), KotlinMetadataUtils for ((name, parameter) in type.constructor.parameters) { if (type.properties[parameter.name] == null && !parameter.hasDefault) { messager.printMessage( - ERROR, "No property for required constructor parameter $name" /*, parameter.element // TODO how do we pass this? */) + ERROR, "No property for required constructor parameter $name", parameter.tag()) return null } } diff --git a/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/TargetProperty.kt b/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/TargetProperty.kt deleted file mode 100644 index 8973454..0000000 --- a/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/TargetProperty.kt +++ /dev/null @@ -1,158 +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.squareup.kotlinpoet.AnnotationSpec -import com.squareup.kotlinpoet.KModifier -import com.squareup.kotlinpoet.TypeName -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.TargetParameter -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.VariableElement -import javax.tools.Diagnostic - -/** A property in user code that maps to JSON. */ -internal data class TargetProperty constructor( - val name: String, - val type: TypeName, - private val parameter: TargetParameter?, - private val annotationHolder: ExecutableElement?, - private val field: VariableElement?, - private val setter: ExecutableElement?, - private val getter: ExecutableElement?, - private val visibility: KModifier -) { - val parameterIndex get() = parameter?.index ?: -1 - val hasDefault get() = parameter?.hasDefault ?: true - - private val isTransient get() = field != null && Modifier.TRANSIENT in field.modifiers - - private val element get() = field ?: setter ?: getter!! - - private val isSettable get() = setter != null || parameter != null - - private val 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. - */ - 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 null // This property is transient and has a default value. Ignore it. - } - - 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() - // TODO Can we check this after? Should we move this check to jsonQualifiers? -// for (jsonQualifier in jsonQualifierMirrors) { -// // Check Java types since that covers both Java and Kotlin annotations. -// 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 { - 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 { - val elementQualifiers = element.qualifiers - val annotationHolderQualifiers = annotationHolder.qualifiers - val parameterQualifiers = parameter?.qualifiers.orEmpty() - - // TODO(jwilson): union the qualifiers somehow? - return when { - elementQualifiers.isNotEmpty() -> elementQualifiers.mapTo(mutableSetOf()) { AnnotationSpec.get(it) } - annotationHolderQualifiers.isNotEmpty() -> annotationHolderQualifiers.mapTo(mutableSetOf()) { AnnotationSpec.get(it) } - parameterQualifiers.isNotEmpty() -> parameterQualifiers - else -> setOf() - } - } - - private val Element?.qualifiers: Set - 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?.jsonName - - return when { - fieldJsonName != null -> fieldJsonName - annotationHolderJsonName != null -> annotationHolderJsonName - parameterJsonName != null -> parameterJsonName - else -> name - } - } - -// private val AnnotationMirror.simpleName: Name -// get() = MoreTypes.asTypeElement(annotationType).simpleName!! - - override fun toString() = name -} - -internal val Element?.jsonName: String? - get() { - if (this == null) return null - return getAnnotation(Json::class.java)?.name - } diff --git a/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/PropertyGenerator.kt b/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/PropertyGenerator.kt index 77d08ec..3539181 100644 --- a/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/PropertyGenerator.kt +++ b/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/PropertyGenerator.kt @@ -18,7 +18,6 @@ package com.squareup.moshi.kotlin.codegen.api import com.squareup.kotlinpoet.BOOLEAN import com.squareup.kotlinpoet.NameAllocator import com.squareup.kotlinpoet.PropertySpec -import com.squareup.moshi.kotlin.codegen.TargetProperty /** Generates functions to encode and decode a property as JSON. */ internal class PropertyGenerator( @@ -26,7 +25,7 @@ internal class PropertyGenerator( val delegateKey: DelegateKey ) { val name = target.name - val jsonName = target.jsonName() + val jsonName = target.jsonName val hasDefault = target.hasDefault lateinit var localName: String diff --git a/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/TargetParameter.kt b/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/TargetParameter.kt index b9e10bd..9c07707 100644 --- a/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/TargetParameter.kt +++ b/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/TargetParameter.kt @@ -16,6 +16,7 @@ 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( @@ -23,5 +24,15 @@ internal data class TargetParameter( val index: Int, val hasDefault: Boolean, val jsonName: String = name, - val qualifiers: Set? = null -) + val qualifiers: Set? = null, + private val tags: Map, Any> +) { + /** Returns the tag attached with [type] as a key, or null if no tag is attached with that key. */ + fun tag(type: KClass): 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 tag(): T? = tag(T::class) +} diff --git a/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/TargetProperty.kt b/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/TargetProperty.kt new file mode 100644 index 0000000..20c5958 --- /dev/null +++ b/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/TargetProperty.kt @@ -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 +} diff --git a/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/TargetType.kt b/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/TargetType.kt index c403d00..ac2a7c6 100644 --- a/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/TargetType.kt +++ b/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/TargetType.kt @@ -18,7 +18,6 @@ package com.squareup.moshi.kotlin.codegen.api import com.squareup.kotlinpoet.KModifier import com.squareup.kotlinpoet.TypeName import com.squareup.kotlinpoet.TypeVariableName -import com.squareup.moshi.kotlin.codegen.TargetProperty /** A user type that should be decoded and encoded by generated code. */ internal data class TargetType( diff --git a/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/metadata.kt b/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/metadata.kt index 48b6a5f..2f56a1c 100644 --- a/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/metadata.kt +++ b/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/metadata.kt @@ -15,6 +15,8 @@ */ package com.squareup.moshi.kotlin.codegen +import com.google.auto.common.AnnotationMirrors +import com.squareup.kotlinpoet.AnnotationSpec import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.KModifier import com.squareup.kotlinpoet.KModifier.PUBLIC @@ -25,10 +27,18 @@ import com.squareup.kotlinpoet.TypeVariableName import com.squareup.kotlinpoet.WildcardTypeName import com.squareup.kotlinpoet.asClassName import com.squareup.kotlinpoet.asTypeName +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 com.squareup.moshi.kotlin.codegen.api.asAnnotationSpec +import com.squareup.moshi.kotlin.codegen.api.asFunSpec +import com.squareup.moshi.kotlin.codegen.api.asPropertySpec import me.eugeniomarletti.kotlin.metadata.KotlinClassMetadata import me.eugeniomarletti.kotlin.metadata.KotlinMetadata import me.eugeniomarletti.kotlin.metadata.classKind @@ -54,7 +64,12 @@ import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.Visibility.PR 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 @@ -62,12 +77,13 @@ 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 import javax.tools.Diagnostic.Kind.ERROR -internal fun TypeParameter.asTypeName( - nameResolver: NameResolver, - getTypeParameter: (index: Int) -> TypeParameter, - resolveAliases: Boolean = false +private fun TypeParameter.asTypeName( + nameResolver: NameResolver, + getTypeParameter: (index: Int) -> TypeParameter, + resolveAliases: Boolean = false ): TypeVariableName { val possibleBounds = upperBoundList.map { it.asTypeName(nameResolver, getTypeParameter, resolveAliases) @@ -84,7 +100,7 @@ internal fun TypeParameter.asTypeName( } } -internal fun TypeParameter.Variance.asKModifier(): KModifier? { +private fun TypeParameter.Variance.asKModifier(): KModifier? { return when (this) { Variance.IN -> KModifier.IN Variance.OUT -> KModifier.OUT @@ -92,7 +108,7 @@ internal fun TypeParameter.Variance.asKModifier(): KModifier? { } } -internal fun Visibility?.asKModifier(): KModifier { +private fun Visibility?.asKModifier(): KModifier { return when (this) { INTERNAL -> KModifier.INTERNAL PRIVATE -> KModifier.PRIVATE @@ -112,10 +128,10 @@ internal fun Visibility?.asKModifier(): KModifier { * @param [getTypeParameter] a function that returns the type parameter for the given index. **Only * called if [ProtoBuf.Type.hasTypeParameter] is true!** */ -internal fun Type.asTypeName( - nameResolver: NameResolver, - getTypeParameter: (index: Int) -> TypeParameter, - useAbbreviatedType: Boolean = true +private fun Type.asTypeName( + nameResolver: NameResolver, + getTypeParameter: (index: Int) -> TypeParameter, + useAbbreviatedType: Boolean = true ): TypeName { val argumentList = when { @@ -173,7 +189,8 @@ internal fun Type.asTypeName( return typeName.copy(nullable = nullable) } -internal fun primaryConstructor(metadata: KotlinClassMetadata, elements: Elements): TargetConstructor { +internal fun primaryConstructor(metadata: KotlinClassMetadata, + elements: Elements): TargetConstructor { val (nameResolver, classProto) = metadata.data // TODO allow custom constructor @@ -202,8 +219,9 @@ internal fun primaryConstructor(metadata: KotlinClassMetadata, elements: Element name = name, index = index, hasDefault = parameter.declaresDefaultValue, - jsonName = paramElement.jsonName ?: name - + qualifiers = paramElement.qualifiers.mapTo(mutableSetOf(), AnnotationMirror::asAnnotationSpec), + jsonName = paramElement.jsonName ?: name, + tags = mapOf(VariableElement::class to paramElement) ) } @@ -214,7 +232,10 @@ internal fun primaryConstructor(metadata: KotlinClassMetadata, elements: Element 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? { +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( @@ -226,7 +247,9 @@ internal fun targetType(messager: Messager, elements: Elements, types: Types, el 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) + 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 -> { @@ -337,37 +360,66 @@ private fun declaredProperties( 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 = annotationHolders[name], - field = fields[name], - setter = setters[name], - getter = getters[name], - visibility = property.visibility.asKModifier() + 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 { - return proto.typeParameterList.map { - val possibleBounds = it.upperBoundList + 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(it.name), - variance = it.varianceModifier) + name = nameResolver.getString(typeParameter.name), + variance = typeParameter.varianceModifier) } else { TypeVariableName( - name = nameResolver.getString(it.name), + name = nameResolver.getString(typeParameter.name), bounds = *possibleBounds.toTypedArray(), - variance = it.varianceModifier) + variance = typeParameter.varianceModifier) } - return@map typeVar.copy(reified = it.reified) + return@map typeVar.copy(reified = typeParameter.reified) } } @@ -382,3 +434,99 @@ private val TypeParameter.varianceModifier: KModifier? } } } + +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() ?: setter?.tag() + ?: getter!!.tag() + if (isTransient) { + if (!hasDefault) { + element?.let { + messager.printMessage( + Diagnostic.Kind.ERROR, "No default value for transient property ${this}", + it) + } + return null + } + return null // This property is transient and has a default value. Ignore it. + } + + 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() ?: 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 { + val elementQualifiers = element.qualifiers + val annotationHolderQualifiers = annotationHolder?.tag().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 val Element?.qualifiers: Set + 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 + }