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