blob: 01d94307f871a3695be7047d07cd4cc5ee1213ff [file] [log] [blame]
Ashley Rosede080eb2018-12-07 17:20:25 -05001/*
Ashley Rosec1a4dec2018-12-13 18:06:30 -05002 * Copyright 2019 The Android Open Source Project
Ashley Rosede080eb2018-12-07 17:20:25 -05003 *
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
17package android.processor.view.inspector;
18
19import static javax.tools.Diagnostic.Kind.ERROR;
20
21import com.squareup.javapoet.ClassName;
22
23import java.io.IOException;
24import java.util.HashMap;
25import java.util.Map;
26import java.util.Optional;
27import java.util.Set;
28
29import javax.annotation.processing.AbstractProcessor;
30import javax.annotation.processing.RoundEnvironment;
31import javax.annotation.processing.SupportedAnnotationTypes;
32import javax.lang.model.SourceVersion;
33import javax.lang.model.element.Element;
34import javax.lang.model.element.ElementKind;
Ashley Rose0b671da2019-01-25 15:41:29 -050035import javax.lang.model.element.Modifier;
Ashley Rosede080eb2018-12-07 17:20:25 -050036import javax.lang.model.element.TypeElement;
37
38
39/**
40 * An annotation processor for the platform inspectable annotations.
41 *
42 * It mostly delegates to {@link ModelProcessor} and {@link InspectionCompanionGenerator}. This
43 * modular architecture allows the core generation code to be reused for comparable annotations
44 * outside the platform, such as in AndroidX.
45 *
46 * @see android.view.inspector.InspectableNodeName
47 * @see android.view.inspector.InspectableProperty
48 */
49@SupportedAnnotationTypes({
Ashley Rosec1a4dec2018-12-13 18:06:30 -050050 PlatformInspectableProcessor.NODE_NAME_QUALIFIED_NAME,
51 PlatformInspectableProcessor.PROPERTY_QUALIFIED_NAME
Ashley Rosede080eb2018-12-07 17:20:25 -050052})
53public final class PlatformInspectableProcessor extends AbstractProcessor {
54 static final String NODE_NAME_QUALIFIED_NAME =
55 "android.view.inspector.InspectableNodeName";
Ashley Rosec1a4dec2018-12-13 18:06:30 -050056 static final String PROPERTY_QUALIFIED_NAME =
57 "android.view.inspector.InspectableProperty";
Ashley Rosede080eb2018-12-07 17:20:25 -050058
59 @Override
60 public SourceVersion getSupportedSourceVersion() {
61 return SourceVersion.latest();
62 }
63
64 @Override
65 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
66 final Map<String, InspectableClassModel> modelMap = new HashMap<>();
67
68 for (TypeElement annotation : annotations) {
69 if (annotation.getQualifiedName().contentEquals(NODE_NAME_QUALIFIED_NAME)) {
70 runModelProcessor(
71 roundEnv.getElementsAnnotatedWith(annotation),
72 new InspectableNodeNameProcessor(NODE_NAME_QUALIFIED_NAME, processingEnv),
73 modelMap);
74
Ashley Rosec1a4dec2018-12-13 18:06:30 -050075 } else if (annotation.getQualifiedName().contentEquals(PROPERTY_QUALIFIED_NAME)) {
76 runModelProcessor(
77 roundEnv.getElementsAnnotatedWith(annotation),
78 new InspectablePropertyProcessor(PROPERTY_QUALIFIED_NAME, processingEnv),
79 modelMap);
Ashley Rosede080eb2018-12-07 17:20:25 -050080
81 } else {
82 fail("Unexpected annotation type", annotation);
83 }
84 }
85
86 final InspectionCompanionGenerator generator =
87 new InspectionCompanionGenerator(processingEnv.getFiler(), getClass());
88
89 for (InspectableClassModel model : modelMap.values()) {
90 try {
91 generator.generate(model);
92 } catch (IOException ioException) {
93 fail(String.format(
94 "Unable to generate inspection companion for %s due to %s",
95 model.getClassName().toString(),
96 ioException.getMessage()));
97 }
98 }
99
100 return true;
101 }
102
103 /**
104 * Run a {@link ModelProcessor} for a set of elements
105 *
106 * @param elements Elements to process, should be annotated correctly
107 * @param processor The processor to use
108 * @param modelMap A map of qualified class names to models
109 */
110 private void runModelProcessor(
111 Set<? extends Element> elements,
112 ModelProcessor processor,
113 Map<String, InspectableClassModel> modelMap) {
114 for (Element element : elements) {
115 final Optional<TypeElement> classElement = enclosingClassElement(element);
116
117 if (!classElement.isPresent()) {
118 fail("Element not contained in a class", element);
119 break;
120 }
121
Ashley Rose0b671da2019-01-25 15:41:29 -0500122 final Set<Modifier> classModifiers = classElement.get().getModifiers();
123
124 if (classModifiers.contains(Modifier.PRIVATE)) {
125 fail("Enclosing class cannot be private", element);
126 }
127
Ashley Rosede080eb2018-12-07 17:20:25 -0500128 final InspectableClassModel model = modelMap.computeIfAbsent(
129 classElement.get().getQualifiedName().toString(),
130 k -> new InspectableClassModel(ClassName.get(classElement.get())));
131
132 processor.process(element, model);
133 }
134 }
135
136 /**
137 * Get the nearest enclosing class if there is one.
138 *
139 * If {@param element} represents a class, it will be returned wrapped in an optional.
140 *
141 * @param element An element to search from
142 * @return A TypeElement of the nearest enclosing class or an empty optional
143 */
144 private static Optional<TypeElement> enclosingClassElement(Element element) {
145 Element cursor = element;
146
147 while (cursor != null) {
148 if (cursor.getKind() == ElementKind.CLASS) {
149 return Optional.of((TypeElement) cursor);
150 }
151
152 cursor = cursor.getEnclosingElement();
153 }
154
155 return Optional.empty();
156 }
157
158 /**
159 * Print message and fail the build.
160 *
161 * @param message Message to print
162 */
163 private void fail(String message) {
164 processingEnv.getMessager().printMessage(ERROR, message);
165 }
166
167 /**
168 * Print message and fail the build.
169 *
170 * @param message Message to print
171 * @param element The element that failed
172 */
173 private void fail(String message, Element element) {
174 processingEnv.getMessager().printMessage(ERROR, message, element);
175 }
176}