1 /* 2 * Copyright 2019 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.view.inspector; 18 19 import static javax.tools.Diagnostic.Kind.ERROR; 20 21 import androidx.annotation.NonNull; 22 23 import com.squareup.javapoet.ClassName; 24 25 import java.io.IOException; 26 import java.util.HashMap; 27 import java.util.Map; 28 import java.util.Optional; 29 import java.util.Set; 30 31 import javax.annotation.processing.AbstractProcessor; 32 import javax.annotation.processing.RoundEnvironment; 33 import javax.annotation.processing.SupportedAnnotationTypes; 34 import javax.lang.model.SourceVersion; 35 import javax.lang.model.element.Element; 36 import javax.lang.model.element.ElementKind; 37 import javax.lang.model.element.Modifier; 38 import javax.lang.model.element.TypeElement; 39 import javax.lang.model.util.ElementFilter; 40 41 /** 42 * An annotation processor for the platform inspectable annotations. 43 * 44 * It mostly delegates to {@link InspectablePropertyProcessor} and 45 * {@link InspectionCompanionGenerator}. This modular architecture allows the core generation code 46 * to be reused for comparable annotations outside the platform. 47 * 48 * @see android.view.inspector.InspectableProperty 49 */ 50 @SupportedAnnotationTypes({PlatformInspectableProcessor.ANNOTATION_QUALIFIED_NAME}) 51 public final class PlatformInspectableProcessor extends AbstractProcessor { 52 static final String ANNOTATION_QUALIFIED_NAME = 53 "android.view.inspector.InspectableProperty"; 54 55 @Override getSupportedSourceVersion()56 public SourceVersion getSupportedSourceVersion() { 57 return SourceVersion.latest(); 58 } 59 60 @Override process( @onNull Set<? extends TypeElement> annotations, @NonNull RoundEnvironment roundEnv)61 public boolean process( 62 @NonNull Set<? extends TypeElement> annotations, 63 @NonNull RoundEnvironment roundEnv) { 64 final Map<String, InspectableClassModel> modelMap = new HashMap<>(); 65 66 for (TypeElement annotation : annotations) { 67 if (annotation.getQualifiedName().contentEquals(ANNOTATION_QUALIFIED_NAME)) { 68 processProperties(roundEnv.getElementsAnnotatedWith(annotation), modelMap); 69 } else { 70 fail("Unexpected annotation type", annotation); 71 } 72 } 73 74 final InspectionCompanionGenerator generator = 75 new InspectionCompanionGenerator(processingEnv.getFiler(), getClass()); 76 77 for (InspectableClassModel model : modelMap.values()) { 78 try { 79 generator.generate(model); 80 } catch (IOException ioException) { 81 fail(String.format( 82 "Unable to generate inspection companion for %s due to %s", 83 model.getClassName().toString(), 84 ioException.getMessage())); 85 } 86 } 87 88 return true; 89 } 90 91 /** 92 * Runs {@link PlatformInspectableProcessor} on a set of annotated elements. 93 * 94 * @param elements A set of annotated elements to process 95 * @param modelMap A map of qualified class names to class models to update 96 */ processProperties( @onNull Set<? extends Element> elements, @NonNull Map<String, InspectableClassModel> modelMap)97 private void processProperties( 98 @NonNull Set<? extends Element> elements, 99 @NonNull Map<String, InspectableClassModel> modelMap) { 100 final InspectablePropertyProcessor processor = 101 new InspectablePropertyProcessor(ANNOTATION_QUALIFIED_NAME, processingEnv); 102 103 for (Element element : elements) { 104 final Optional<TypeElement> classElement = enclosingClassElement(element); 105 106 if (!classElement.isPresent()) { 107 fail("Element not contained in a class", element); 108 break; 109 } 110 111 final Set<Modifier> classModifiers = classElement.get().getModifiers(); 112 113 if (classModifiers.contains(Modifier.PRIVATE)) { 114 fail("Enclosing class cannot be private", element); 115 } 116 117 final InspectableClassModel model = modelMap.computeIfAbsent( 118 classElement.get().getQualifiedName().toString(), 119 k -> { 120 if (hasNestedInspectionCompanion(classElement.get())) { 121 fail( 122 String.format( 123 "Class %s already has an inspection companion.", 124 classElement.get().getQualifiedName().toString()), 125 element); 126 } 127 return new InspectableClassModel(ClassName.get(classElement.get())); 128 }); 129 130 processor.process(element, model); 131 } 132 } 133 134 /** 135 * Determine if a class has a nested class named {@code InspectionCompanion}. 136 * 137 * @param typeElement A type element representing the class to check 138 * @return f the class contains a class named {@code InspectionCompanion} 139 */ hasNestedInspectionCompanion(@onNull TypeElement typeElement)140 private static boolean hasNestedInspectionCompanion(@NonNull TypeElement typeElement) { 141 for (TypeElement nestedClass : ElementFilter.typesIn(typeElement.getEnclosedElements())) { 142 if (nestedClass.getSimpleName().toString().equals("InspectionCompanion")) { 143 return true; 144 } 145 } 146 147 return false; 148 } 149 150 /** 151 * Get the nearest enclosing class if there is one. 152 * 153 * If {@param element} represents a class, it will be returned wrapped in an optional. 154 * 155 * @param element An element to search from 156 * @return A TypeElement of the nearest enclosing class or an empty optional 157 */ 158 @NonNull enclosingClassElement(@onNull Element element)159 private static Optional<TypeElement> enclosingClassElement(@NonNull Element element) { 160 Element cursor = element; 161 162 while (cursor != null) { 163 if (cursor.getKind() == ElementKind.CLASS) { 164 return Optional.of((TypeElement) cursor); 165 } 166 167 cursor = cursor.getEnclosingElement(); 168 } 169 170 return Optional.empty(); 171 } 172 173 /** 174 * Print message and fail the build. 175 * 176 * @param message Message to print 177 */ fail(@onNull String message)178 private void fail(@NonNull String message) { 179 processingEnv.getMessager().printMessage(ERROR, message); 180 } 181 182 /** 183 * Print message and fail the build. 184 * 185 * @param message Message to print 186 * @param element The element that failed 187 */ fail(@onNull String message, @NonNull Element element)188 private void fail(@NonNull String message, @NonNull Element element) { 189 processingEnv.getMessager().printMessage(ERROR, message, element); 190 } 191 } 192