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