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