xref: /aosp_15_r20/external/moshi/moshi-kotlin-codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/DelegateKey.kt (revision 238ab3e782f339ab327592a602fa7df0a3f729ad)
1 /*
2  * Copyright (C) 2018 Square, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *    https://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.squareup.moshi.kotlin.codegen.api
17 
18 import com.squareup.kotlinpoet.AnnotationSpec
19 import com.squareup.kotlinpoet.ClassName
20 import com.squareup.kotlinpoet.CodeBlock
21 import com.squareup.kotlinpoet.KModifier
22 import com.squareup.kotlinpoet.MemberName
23 import com.squareup.kotlinpoet.NameAllocator
24 import com.squareup.kotlinpoet.ParameterSpec
25 import com.squareup.kotlinpoet.ParameterizedTypeName
26 import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
27 import com.squareup.kotlinpoet.PropertySpec
28 import com.squareup.kotlinpoet.TypeName
29 import com.squareup.kotlinpoet.TypeVariableName
30 import com.squareup.kotlinpoet.WildcardTypeName
31 import com.squareup.kotlinpoet.asClassName
32 import com.squareup.kotlinpoet.joinToCode
33 import com.squareup.moshi.JsonAdapter
34 import java.util.Locale
35 
36 /** A JsonAdapter that can be used to encode and decode a particular field. */
37 @InternalMoshiCodegenApi
38 public data class DelegateKey(
39   private val type: TypeName,
40   private val jsonQualifiers: List<AnnotationSpec>,
41 ) {
42   public val nullable: Boolean get() = type.isNullable
43 
44   /** Returns an adapter to use when encoding and decoding this property. */
generatePropertynull45   internal fun generateProperty(
46     nameAllocator: NameAllocator,
47     typeRenderer: TypeRenderer,
48     moshiParameter: ParameterSpec,
49     propertyName: String
50   ): PropertySpec {
51     val qualifierNames = jsonQualifiers.joinToString("") {
52       "At${it.typeName.rawType().simpleName}"
53     }
54     val adapterName = nameAllocator.newName(
55       "${type.toVariableName().replaceFirstChar { it.lowercase(Locale.US) }}${qualifierNames}Adapter",
56       this
57     )
58 
59     val adapterTypeName = JsonAdapter::class.asClassName().parameterizedBy(type)
60     val standardArgs = arrayOf(
61       moshiParameter,
62       typeRenderer.render(type)
63     )
64 
65     val (initializerString, args) = when {
66       jsonQualifiers.isEmpty() -> ", %M()" to arrayOf(MemberName("kotlin.collections", "emptySet"))
67       else -> {
68         ", setOf(%L)" to arrayOf(jsonQualifiers.map { it.asInstantiationExpression() }.joinToCode())
69       }
70     }
71     val finalArgs = arrayOf(*standardArgs, *args, propertyName)
72 
73     return PropertySpec.builder(adapterName, adapterTypeName, KModifier.PRIVATE)
74       .initializer("%N.adapter(%L$initializerString, %S)", *finalArgs)
75       .build()
76   }
77 }
78 
asInstantiationExpressionnull79 private fun AnnotationSpec.asInstantiationExpression(): CodeBlock {
80   // <Type>(args)
81   return CodeBlock.of(
82     "%T(%L)",
83     typeName,
84     members.joinToCode()
85   )
86 }
87 
88 /**
89  * Returns a suggested variable name derived from a list of type names. This just concatenates,
90  * yielding types like MapOfStringLong.
91  */
<lambda>null92 private fun List<TypeName>.toVariableNames() = joinToString("") { it.toVariableName() }
93 
94 /** Returns a suggested variable name derived from a type name, like nullableListOfString. */
TypeNamenull95 private fun TypeName.toVariableName(): String {
96   val base = when (this) {
97     is ClassName -> simpleName
98     is ParameterizedTypeName -> rawType.simpleName + "Of" + typeArguments.toVariableNames()
99     is WildcardTypeName -> (inTypes + outTypes).toVariableNames()
100     is TypeVariableName -> name + bounds.toVariableNames()
101     else -> throw IllegalArgumentException("Unrecognized type! $this")
102   }
103 
104   return if (isNullable) {
105     "Nullable$base"
106   } else {
107     base
108   }
109 }
110