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