Change DelegateKey to use AnnotationSpec instead of AnnotationMirror
AnnotationSpec implements equals() in the way we need, but AnnotationMirror doesn't. As a consequence this fixes a problem where we were generating redundant adapters. Closes: https://github.com/square/moshi/issues/563
This commit is contained in:
parent
4f3c418202
commit
7382145318
5 changed files with 70 additions and 41 deletions
|
@ -135,7 +135,7 @@ internal class AdapterGenerator(
|
|||
result.addProperty(optionsProperty)
|
||||
for (uniqueAdapter in propertyList.distinctBy { it.delegateKey }) {
|
||||
result.addProperty(uniqueAdapter.delegateKey.generateProperty(
|
||||
nameAllocator, typeRenderer, moshiParam, messager))
|
||||
nameAllocator, typeRenderer, moshiParam))
|
||||
}
|
||||
|
||||
result.addFunction(generateToStringFun())
|
||||
|
|
|
@ -15,9 +15,7 @@
|
|||
*/
|
||||
package com.squareup.moshi.kotlin.codegen
|
||||
|
||||
import com.google.auto.common.MoreTypes
|
||||
import com.squareup.kotlinpoet.AnnotationSpec
|
||||
import com.squareup.kotlinpoet.AnnotationSpec.UseSiteTarget.FIELD
|
||||
import com.squareup.kotlinpoet.ClassName
|
||||
import com.squareup.kotlinpoet.CodeBlock
|
||||
import com.squareup.kotlinpoet.KModifier
|
||||
|
@ -33,16 +31,11 @@ import com.squareup.kotlinpoet.asClassName
|
|||
import com.squareup.kotlinpoet.asTypeName
|
||||
import com.squareup.moshi.JsonAdapter
|
||||
import com.squareup.moshi.Types
|
||||
import java.lang.annotation.ElementType
|
||||
import java.lang.annotation.RetentionPolicy
|
||||
import javax.annotation.processing.Messager
|
||||
import javax.lang.model.element.AnnotationMirror
|
||||
import javax.tools.Diagnostic.Kind.ERROR
|
||||
|
||||
/** A JsonAdapter that can be used to encode and decode a particular field. */
|
||||
internal data class DelegateKey(
|
||||
private val type: TypeName,
|
||||
private val jsonQualifiers: Set<AnnotationMirror>
|
||||
private val jsonQualifiers: List<AnnotationSpec>
|
||||
) {
|
||||
val nullable get() = type.nullable
|
||||
|
||||
|
@ -50,36 +43,17 @@ internal data class DelegateKey(
|
|||
fun generateProperty(
|
||||
nameAllocator: NameAllocator,
|
||||
typeRenderer: TypeRenderer,
|
||||
moshiParameter: ParameterSpec,
|
||||
messager: Messager): PropertySpec {
|
||||
fun AnnotationMirror.validate(): AnnotationMirror {
|
||||
// Check java types since that covers both java and kotlin annotations
|
||||
val annotationElement = MoreTypes.asTypeElement(annotationType)
|
||||
annotationElement.getAnnotation(java.lang.annotation.Retention::class.java)?.let {
|
||||
if (it.value != RetentionPolicy.RUNTIME) {
|
||||
messager.printMessage(ERROR, "JsonQualifier " +
|
||||
"@${MoreTypes.asTypeElement(annotationType).simpleName} must have RUNTIME retention")
|
||||
}
|
||||
}
|
||||
annotationElement.getAnnotation(java.lang.annotation.Target::class.java)?.let {
|
||||
if (ElementType.FIELD !in it.value) {
|
||||
messager.printMessage(ERROR, "JsonQualifier " +
|
||||
"@${MoreTypes.asTypeElement(annotationType).simpleName} must support FIELD target")
|
||||
}
|
||||
}
|
||||
return this
|
||||
}
|
||||
jsonQualifiers.forEach { it.validate() }
|
||||
moshiParameter: ParameterSpec
|
||||
): PropertySpec {
|
||||
val qualifierNames = jsonQualifiers.joinToString("") {
|
||||
"At${it.annotationType.asElement().simpleName}"
|
||||
"At${(it.type as ClassName).simpleName}"
|
||||
}
|
||||
val adapterName = nameAllocator.newName(
|
||||
"${type.toVariableName().decapitalize()}${qualifierNames}Adapter", this)
|
||||
|
||||
val adapterTypeName = JsonAdapter::class.asClassName().parameterizedBy(type)
|
||||
val qualifiers = jsonQualifiers
|
||||
val standardArgs = arrayOf(moshiParameter,
|
||||
if (type is ClassName && qualifiers.isEmpty()) {
|
||||
if (type is ClassName && jsonQualifiers.isEmpty()) {
|
||||
""
|
||||
} else {
|
||||
CodeBlock.of("<%T>", type)
|
||||
|
@ -87,7 +61,7 @@ internal data class DelegateKey(
|
|||
typeRenderer.render(type))
|
||||
val standardArgsSize = standardArgs.size + 1
|
||||
val (initializerString, args) = when {
|
||||
qualifiers.isEmpty() -> "" to emptyArray()
|
||||
jsonQualifiers.isEmpty() -> "" to emptyArray()
|
||||
else -> {
|
||||
", %${standardArgsSize}T.getFieldJsonQualifierAnnotations(javaClass, " +
|
||||
"%${standardArgsSize + 1}S)" to arrayOf(Types::class.asTypeName(), adapterName)
|
||||
|
@ -98,9 +72,7 @@ internal data class DelegateKey(
|
|||
val nullModifier = if (nullable) ".nullSafe()" else ".nonNull()"
|
||||
|
||||
return PropertySpec.builder(adapterName, adapterTypeName, KModifier.PRIVATE)
|
||||
.addAnnotations(qualifiers.map {
|
||||
AnnotationSpec.get(it).toBuilder().useSiteTarget(FIELD).build()
|
||||
})
|
||||
.addAnnotations(jsonQualifiers)
|
||||
.initializer("%1N.adapter%2L(%3L$initializerString)$nullModifier", *finalArgs)
|
||||
.build()
|
||||
}
|
||||
|
|
|
@ -20,8 +20,10 @@ import com.squareup.kotlinpoet.NameAllocator
|
|||
import com.squareup.kotlinpoet.PropertySpec
|
||||
|
||||
/** Generates functions to encode and decode a property as JSON. */
|
||||
internal class PropertyGenerator(val target: TargetProperty) {
|
||||
val delegateKey = target.delegateKey()
|
||||
internal class PropertyGenerator(
|
||||
val target: TargetProperty,
|
||||
val delegateKey: DelegateKey
|
||||
) {
|
||||
val name = target.name
|
||||
val jsonName = target.jsonName()
|
||||
val hasDefault = target.hasDefault
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
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
|
||||
|
@ -26,11 +28,16 @@ import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.Visibility.IN
|
|||
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
|
||||
|
||||
|
@ -85,10 +92,32 @@ internal data class TargetProperty(
|
|||
return null // This property is not settable. Ignore it.
|
||||
}
|
||||
|
||||
return PropertyGenerator(this)
|
||||
}
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun delegateKey() = DelegateKey(type, jsonQualifiers())
|
||||
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> {
|
||||
|
@ -131,5 +160,8 @@ internal data class TargetProperty(
|
|||
return getAnnotation(Json::class.java)?.name?.replace("$", "\\$")
|
||||
}
|
||||
|
||||
private val AnnotationMirror.simpleName: Name
|
||||
get() = MoreTypes.asTypeElement(annotationType).simpleName!!
|
||||
|
||||
override fun toString() = name
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@ import org.junit.Assert.fail
|
|||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
import java.util.Locale
|
||||
import kotlin.reflect.full.memberProperties
|
||||
|
||||
class GeneratedAdaptersTest {
|
||||
|
||||
|
@ -857,6 +858,28 @@ class GeneratedAdaptersTest {
|
|||
throw AssertionError()
|
||||
}
|
||||
|
||||
/** https://github.com/square/moshi/issues/563 */
|
||||
@Test fun qualifiedAdaptersAreShared() {
|
||||
val moshi = Moshi.Builder()
|
||||
.add(UppercaseJsonAdapter())
|
||||
.build()
|
||||
val jsonAdapter = moshi.adapter(MultiplePropertiesShareAdapter::class.java)
|
||||
|
||||
val encoded = MultiplePropertiesShareAdapter("Android", "Banana")
|
||||
assertThat(jsonAdapter.toJson(encoded)).isEqualTo("""{"a":"ANDROID","b":"BANANA"}""")
|
||||
|
||||
val delegateAdapters = jsonAdapter::class.memberProperties.filter {
|
||||
it.returnType.classifier == JsonAdapter::class
|
||||
}
|
||||
assertThat(delegateAdapters).hasSize(1)
|
||||
}
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
class MultiplePropertiesShareAdapter(
|
||||
@Uppercase(true) var a: String,
|
||||
@Uppercase(true) var b: String
|
||||
)
|
||||
|
||||
@Test fun toJsonOnly() {
|
||||
val moshi = Moshi.Builder()
|
||||
.add(CustomToJsonOnlyAdapter())
|
||||
|
|
Loading…
Reference in a new issue