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.ARRAY
19 import com.squareup.kotlinpoet.BOOLEAN
20 import com.squareup.kotlinpoet.BYTE
21 import com.squareup.kotlinpoet.CHAR
22 import com.squareup.kotlinpoet.ClassName
23 import com.squareup.kotlinpoet.CodeBlock
24 import com.squareup.kotlinpoet.DOUBLE
25 import com.squareup.kotlinpoet.FLOAT
26 import com.squareup.kotlinpoet.INT
27 import com.squareup.kotlinpoet.LONG
28 import com.squareup.kotlinpoet.ParameterizedTypeName
29 import com.squareup.kotlinpoet.SHORT
30 import com.squareup.kotlinpoet.TypeName
31 import com.squareup.kotlinpoet.TypeVariableName
32 import com.squareup.kotlinpoet.WildcardTypeName
33 import com.squareup.moshi.Types
34 
35 /**
36  * Renders literals like `Types.newParameterizedType(List::class.java, String::class.java)`.
37  * Rendering is pluggable so that type variables can either be resolved or emitted as other code
38  * blocks.
39  */
40 internal abstract class TypeRenderer {
renderTypeVariablenull41   abstract fun renderTypeVariable(typeVariable: TypeVariableName): CodeBlock
42 
43   fun render(typeName: TypeName, forceBox: Boolean = false): CodeBlock {
44     if (typeName.annotations.isNotEmpty()) {
45       return render(typeName.copy(annotations = emptyList()), forceBox)
46     }
47     if (typeName.isNullable) {
48       return renderObjectType(typeName.copy(nullable = false))
49     }
50 
51     return when (typeName) {
52       is ClassName -> {
53         if (forceBox) {
54           renderObjectType(typeName)
55         } else {
56           CodeBlock.of("%T::class.java", typeName)
57         }
58       }
59 
60       is ParameterizedTypeName -> {
61         // If it's an Array type, we shortcut this to return Types.arrayOf()
62         if (typeName.rawType == ARRAY) {
63           CodeBlock.of(
64             "%T.arrayOf(%L)",
65             Types::class,
66             renderObjectType(typeName.typeArguments[0])
67           )
68         } else {
69           val builder = CodeBlock.builder().apply {
70             add("%T.", Types::class)
71             val enclosingClassName = typeName.rawType.enclosingClassName()
72             if (enclosingClassName != null) {
73               add("newParameterizedTypeWithOwner(%L, ", render(enclosingClassName))
74             } else {
75               add("newParameterizedType(")
76             }
77             add("%T::class.java", typeName.rawType)
78             for (typeArgument in typeName.typeArguments) {
79               add(", %L", renderObjectType(typeArgument))
80             }
81             add(")")
82           }
83           builder.build()
84         }
85       }
86 
87       is WildcardTypeName -> {
88         val target: TypeName
89         val method: String
90         when {
91           typeName.inTypes.size == 1 -> {
92             target = typeName.inTypes[0]
93             method = "supertypeOf"
94           }
95           typeName.outTypes.size == 1 -> {
96             target = typeName.outTypes[0]
97             method = "subtypeOf"
98           }
99           else -> throw IllegalArgumentException(
100             "Unrepresentable wildcard type. Cannot have more than one bound: $typeName"
101           )
102         }
103         CodeBlock.of("%T.%L(%L)", Types::class, method, render(target, forceBox = true))
104       }
105 
106       is TypeVariableName -> renderTypeVariable(typeName)
107 
108       else -> throw IllegalArgumentException("Unrepresentable type: $typeName")
109     }
110   }
111 
renderObjectTypenull112   private fun renderObjectType(typeName: TypeName): CodeBlock {
113     return if (typeName.isPrimitive()) {
114       CodeBlock.of("%T::class.javaObjectType", typeName)
115     } else {
116       render(typeName)
117     }
118   }
119 
TypeNamenull120   private fun TypeName.isPrimitive(): Boolean {
121     return when (this) {
122       BOOLEAN, BYTE, SHORT, INT, LONG, CHAR, FLOAT, DOUBLE -> true
123       else -> false
124     }
125   }
126 }
127