1 /*
<lambda>null2  * 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.apt
17 
18 import com.google.auto.service.AutoService
19 import com.squareup.kotlinpoet.AnnotationSpec
20 import com.squareup.kotlinpoet.ClassName
21 import com.squareup.kotlinpoet.metadata.classinspectors.ElementsClassInspector
22 import com.squareup.moshi.JsonClass
23 import com.squareup.moshi.kotlin.codegen.api.AdapterGenerator
24 import com.squareup.moshi.kotlin.codegen.api.Options.OPTION_GENERATED
25 import com.squareup.moshi.kotlin.codegen.api.Options.OPTION_GENERATE_PROGUARD_RULES
26 import com.squareup.moshi.kotlin.codegen.api.Options.OPTION_INSTANTIATE_ANNOTATIONS
27 import com.squareup.moshi.kotlin.codegen.api.Options.POSSIBLE_GENERATED_NAMES
28 import com.squareup.moshi.kotlin.codegen.api.ProguardConfig
29 import com.squareup.moshi.kotlin.codegen.api.PropertyGenerator
30 import javax.annotation.processing.AbstractProcessor
31 import javax.annotation.processing.Filer
32 import javax.annotation.processing.Messager
33 import javax.annotation.processing.ProcessingEnvironment
34 import javax.annotation.processing.Processor
35 import javax.annotation.processing.RoundEnvironment
36 import javax.lang.model.SourceVersion
37 import javax.lang.model.element.Element
38 import javax.lang.model.element.TypeElement
39 import javax.lang.model.util.Elements
40 import javax.lang.model.util.Types
41 import javax.tools.Diagnostic
42 import javax.tools.StandardLocation
43 
44 /**
45  * An annotation processor that reads Kotlin data classes and generates Moshi JsonAdapters for them.
46  * This generates Kotlin code, and understands basic Kotlin language features like default values
47  * and companion objects.
48  *
49  * The generated class will match the visibility of the given data class (i.e. if it's internal, the
50  * adapter will also be internal).
51  */
52 @AutoService(Processor::class)
53 public class JsonClassCodegenProcessor : AbstractProcessor() {
54 
55   private lateinit var types: Types
56   private lateinit var elements: Elements
57   private lateinit var filer: Filer
58   private lateinit var messager: Messager
59   private lateinit var cachedClassInspector: MoshiCachedClassInspector
60   private val annotation = JsonClass::class.java
61   private var generatedType: ClassName? = null
62   private var generateProguardRules: Boolean = true
63   private var instantiateAnnotations: Boolean = true
64 
65   override fun getSupportedAnnotationTypes(): Set<String> = setOf(annotation.canonicalName)
66 
67   override fun getSupportedSourceVersion(): SourceVersion = SourceVersion.latest()
68 
69   override fun getSupportedOptions(): Set<String> = setOf(OPTION_GENERATED)
70 
71   override fun init(processingEnv: ProcessingEnvironment) {
72     super.init(processingEnv)
73     processingEnv.messager.printMessage(Diagnostic.Kind.WARNING, "Kapt support in Moshi Kotlin Code Gen is deprecated and will be removed in 2.0. Please migrate to KSP. https://github.com/square/moshi#codegen")
74     generatedType = processingEnv.options[OPTION_GENERATED]?.let {
75       POSSIBLE_GENERATED_NAMES[it] ?: error(
76         "Invalid option value for $OPTION_GENERATED. Found $it, " +
77           "allowable values are $POSSIBLE_GENERATED_NAMES."
78       )
79     }
80 
81     generateProguardRules = processingEnv.options[OPTION_GENERATE_PROGUARD_RULES]?.toBooleanStrictOrNull() ?: true
82     instantiateAnnotations = processingEnv.options[OPTION_INSTANTIATE_ANNOTATIONS]?.toBooleanStrictOrNull() ?: true
83 
84     this.types = processingEnv.typeUtils
85     this.elements = processingEnv.elementUtils
86     this.filer = processingEnv.filer
87     this.messager = processingEnv.messager
88     cachedClassInspector = MoshiCachedClassInspector(ElementsClassInspector.create(elements, types))
89   }
90 
91   override fun process(annotations: Set<TypeElement>, roundEnv: RoundEnvironment): Boolean {
92     if (roundEnv.errorRaised()) {
93       // An error was raised in the previous round. Don't try anything for now to avoid adding
94       // possible more noise.
95       return false
96     }
97     for (type in roundEnv.getElementsAnnotatedWith(annotation)) {
98       if (type !is TypeElement) {
99         messager.printMessage(
100           Diagnostic.Kind.ERROR,
101           "@JsonClass can't be applied to $type: must be a Kotlin class",
102           type
103         )
104         continue
105       }
106       val jsonClass = type.getAnnotation(annotation)
107       if (jsonClass.generateAdapter && jsonClass.generator.isEmpty()) {
108         val generator = adapterGenerator(type, cachedClassInspector) ?: continue
109         val preparedAdapter = generator
110           .prepare(generateProguardRules) { spec ->
111             spec.toBuilder()
112               .apply {
113                 @Suppress("DEPRECATION") // This is a Java type
114                 generatedType?.let { generatedClassName ->
115                   addAnnotation(
116                     AnnotationSpec.builder(generatedClassName)
117                       .addMember(
118                         "value = [%S]",
119                         JsonClassCodegenProcessor::class.java.canonicalName
120                       )
121                       .addMember("comments = %S", "https://github.com/square/moshi")
122                       .build()
123                   )
124                 }
125               }
126               .addOriginatingElement(type)
127               .build()
128           }
129 
130         preparedAdapter.spec.writeTo(filer)
131         preparedAdapter.proguardConfig?.writeTo(filer, type)
132       }
133     }
134 
135     return false
136   }
137 
138   private fun adapterGenerator(
139     element: TypeElement,
140     cachedClassInspector: MoshiCachedClassInspector
141   ): AdapterGenerator? {
142     val type = targetType(
143       messager,
144       elements,
145       types,
146       element,
147       cachedClassInspector,
148     ) ?: return null
149 
150     val properties = mutableMapOf<String, PropertyGenerator>()
151     for (property in type.properties.values) {
152       val generator = property.generator(messager, element, elements)
153       if (generator != null) {
154         properties[property.name] = generator
155       }
156     }
157 
158     for ((name, parameter) in type.constructor.parameters) {
159       if (type.properties[parameter.name] == null && !parameter.hasDefault) {
160         messager.printMessage(
161           Diagnostic.Kind.ERROR,
162           "No property for required constructor parameter $name",
163           element
164         )
165         return null
166       }
167     }
168 
169     // Sort properties so that those with constructor parameters come first.
170     val sortedProperties = properties.values.sortedBy {
171       if (it.hasConstructorParameter) {
172         it.target.parameterIndex
173       } else {
174         Integer.MAX_VALUE
175       }
176     }
177 
178     return AdapterGenerator(type, sortedProperties)
179   }
180 }
181 
182 /** Writes this config to a [filer]. */
ProguardConfignull183 private fun ProguardConfig.writeTo(filer: Filer, vararg originatingElements: Element) {
184   filer.createResource(StandardLocation.CLASS_OUTPUT, "", "${outputFilePathWithoutExtension(targetClass.canonicalName)}.pro", *originatingElements)
185     .openWriter()
186     .use(::writeTo)
187 }
188