1 /*
2  * Copyright (C) 2020 The Android Open Source Project
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  *      http://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 
17 package android.processor
18 
19 import android.annotation.IntDef
20 import com.sun.source.tree.IdentifierTree
21 import com.sun.source.tree.MemberSelectTree
22 import com.sun.source.tree.NewArrayTree
23 import com.sun.source.util.SimpleTreeVisitor
24 import com.sun.source.util.Trees
25 import java.io.IOException
26 import java.io.Writer
27 import javax.annotation.processing.AbstractProcessor
28 import javax.annotation.processing.RoundEnvironment
29 import javax.lang.model.SourceVersion
30 import javax.lang.model.element.AnnotationValue
31 import javax.lang.model.element.TypeElement
32 import javax.tools.Diagnostic.Kind
33 import javax.tools.StandardLocation.CLASS_OUTPUT
34 import kotlin.collections.set
35 
36 /**
37  * The IntDefProcessor is intended to generate a mapping from ints to their respective string
38  * identifier for each IntDef for use by Winscope or any other tool which requires such a mapping.
39  *
40  * The processor will run when building :framework-minus-apex-intdefs and dump all the IntDef
41  * mappings found in the files that make up the build target as json to outputPath.
42  */
43 class IntDefProcessor : AbstractProcessor() {
44     private val outputName = "intDefMapping.json"
45 
46     override fun getSupportedSourceVersion(): SourceVersion = SourceVersion.latest()
47 
48     // Define what the annotation we care about are for compiler optimization
49     override fun getSupportedAnnotationTypes() = LinkedHashSet<String>().apply {
50         add(IntDef::class.java.name)
51     }
52 
53     override fun process(annotations: Set<TypeElement>, roundEnv: RoundEnvironment): Boolean {
54         // There should only be one matching annotation definition for intDef
55         val annotationType = annotations.firstOrNull() ?: return false
56         val annotatedElements = roundEnv.getElementsAnnotatedWith(annotationType)
57 
58         val annotationTypeToIntDefMapping = annotatedElements.associate { annotatedElement ->
59             val type = (annotatedElement as TypeElement).qualifiedName.toString()
60             val mapping = generateIntDefMapping(annotatedElement, annotationType)
61             val intDef = annotatedElement.getAnnotation(IntDef::class.java)
62             type to IntDefMapping(mapping, intDef.flag)
63         }
64 
65         try {
66             outputToFile(annotationTypeToIntDefMapping)
67         } catch (e: IOException) {
68             error("Failed to write IntDef mappings :: $e")
69         }
70         return false
71     }
72 
73     private fun generateIntDefMapping(
74         annotatedElement: TypeElement,
75         annotationType: TypeElement
76     ): Map<Int, String> {
77         // LinkedHashMap makes sure ordering is the same as in the code
78         val mapping = LinkedHashMap<Int, String>()
79 
80         val annotationMirror = annotatedElement.annotationMirrors
81                 // Should only ever be one matching this condition
82                 .first { it.annotationType.asElement() == annotationType }
83 
84         val value = annotationMirror.elementValues.entries
85                 .first { entry -> entry.key.simpleName.contentEquals("value") }
86                 .value
87 
88         val trees = Trees.instance(processingEnv)
89         val tree = trees.getTree(annotatedElement, annotationMirror, value)
90 
91         val identifiers = ArrayList<String>()
92         tree.accept(IdentifierVisitor(), identifiers)
93 
94         val values = value.value as List<AnnotationValue>
95 
96         for (i in identifiers.indices) {
97             mapping[values[i].value as Int] = identifiers[i]
98         }
99 
100         return mapping
101     }
102 
103     private class IdentifierVisitor : SimpleTreeVisitor<Void, ArrayList<String>>() {
104         override fun visitNewArray(node: NewArrayTree, indentifiers: ArrayList<String>): Void? {
105             for (initializer in node.initializers) {
106                 initializer.accept(this, indentifiers)
107             }
108 
109             return null
110         }
111 
112         override fun visitMemberSelect(node: MemberSelectTree, indentifiers: ArrayList<String>):
113                 Void? {
114             indentifiers.add(node.identifier.toString())
115 
116             return null
117         }
118 
119         override fun visitIdentifier(node: IdentifierTree, indentifiers: ArrayList<String>): Void? {
120             indentifiers.add(node.name.toString())
121 
122             return null
123         }
124     }
125 
126     @Throws(IOException::class)
127     private fun outputToFile(annotationTypeToIntDefMapping: Map<String, IntDefMapping>) {
128         val resource = processingEnv.filer.createResource(
129                 CLASS_OUTPUT, "com.android.winscope", outputName)
130         val writer = resource.openWriter()
131         serializeTo(annotationTypeToIntDefMapping, writer)
132         writer.close()
133     }
134 
135     private fun error(message: String) {
136         processingEnv.messager.printMessage(Kind.ERROR, message)
137     }
138 
139     private fun note(message: String) {
140         processingEnv.messager.printMessage(Kind.NOTE, message)
141     }
142 
143     class IntDefMapping(val mapping: Map<Int, String>, val flag: Boolean) {
144         val size
145             get() = this.mapping.size
146 
147         val entries
148             get() = this.mapping.entries
149     }
150 
151     companion object {
152         fun serializeTo(
153             annotationTypeToIntDefMapping: Map<String, IntDefMapping>,
154             writer: Writer
155         ) {
156             val indent = "  "
157 
158             writer.appendln("{")
159 
160             val intDefTypesCount = annotationTypeToIntDefMapping.size
161             var currentIntDefTypesCount = 0
162             for ((field, intDefMapping) in annotationTypeToIntDefMapping) {
163                 writer.appendln("""$indent"$field": {""")
164 
165                 // Start IntDef
166 
167                 writer.appendln("""$indent$indent"flag": ${intDefMapping.flag},""")
168 
169                 writer.appendln("""$indent$indent"values": {""")
170                 intDefMapping.entries.joinTo(writer, separator = ",\n") { (value, identifier) ->
171                     """$indent$indent$indent"$value": "$identifier""""
172                 }
173                 writer.appendln()
174                 writer.appendln("$indent$indent}")
175 
176                 // End IntDef
177 
178                 writer.append("$indent}")
179                 if (++currentIntDefTypesCount < intDefTypesCount) {
180                     writer.appendln(",")
181                 } else {
182                     writer.appendln("")
183                 }
184             }
185 
186             writer.appendln("}")
187         }
188     }
189 }
190