diff options
author | Ashley Rose <ashleyrose@google.com> | 2018-12-07 17:20:25 -0500 |
---|---|---|
committer | Ashley Rose <ashleyrose@google.com> | 2018-12-10 23:30:43 +0000 |
commit | de080eb7b0374882cfe3dbea01540f477ff59e5d (patch) | |
tree | 328ec34832a38f40de0a9a8c037062b76d77fb6c | |
parent | 52fe5dd97fb749aad4f570914a22aebf8d0de1c1 (diff) |
Annotation processor for @InspectableNodeName
Bug: 117616612
Test: atest --host view-inspector-annotation-processor-test
Change-Id: I48f62544655adbc33e3ccdd3301d6dc471fe4163
14 files changed, 861 insertions, 12 deletions
diff --git a/Android.bp b/Android.bp index 0210bb3c1323..4762cba7ecf9 100644 --- a/Android.bp +++ b/Android.bp @@ -784,18 +784,6 @@ java_library { ], } -// A host library containing the inspector annotations for inspector-annotation-processor. -java_library_host { - name: "inspector-annotation", - srcs: [ - "core/java/android/view/inspector/InspectableNodeName.java", - "core/java/android/view/inspector/InspectableProperty.java", - // Needed for the ResourceId.ID_NULL constant - "core/java/android/content/res/ResourceId.java", - "core/java/android/annotation/AnyRes.java", - ], -} - // A host library including just UnsupportedAppUsage.java so that the annotation // processor can also use this annotation. java_library_host { diff --git a/tools/processors/view_inspector/Android.bp b/tools/processors/view_inspector/Android.bp new file mode 100644 index 000000000000..ca6b3c4572f5 --- /dev/null +++ b/tools/processors/view_inspector/Android.bp @@ -0,0 +1,27 @@ +java_library_host { + name: "view-inspector-annotation-processor", + + srcs: ["src/java/**/*.java"], + java_resource_dirs: ["src/resources"], + + static_libs: [ + "javapoet", + ], + + use_tools_jar: true, +} + +java_test_host { + name: "view-inspector-annotation-processor-test", + + srcs: ["test/java/**/*.java"], + java_resource_dirs: ["test/resources"], + + static_libs: [ + "guava", + "junit", + "view-inspector-annotation-processor", + ], + + test_suites: ["general-tests"], +} diff --git a/tools/processors/view_inspector/TEST_MAPPING b/tools/processors/view_inspector/TEST_MAPPING new file mode 100644 index 000000000000..a91b5b452c39 --- /dev/null +++ b/tools/processors/view_inspector/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "presubmit": [ + { + "name": "view-inspector-annotation-processor-test" + } + ] +} diff --git a/tools/processors/view_inspector/src/java/android/processor/view/inspector/AnnotationUtils.java b/tools/processors/view_inspector/src/java/android/processor/view/inspector/AnnotationUtils.java new file mode 100644 index 000000000000..f157949f4d1b --- /dev/null +++ b/tools/processors/view_inspector/src/java/android/processor/view/inspector/AnnotationUtils.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.processor.view.inspector; + +import java.util.Map; +import java.util.Optional; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Elements; +import javax.lang.model.util.Types; + +/** + * Utilities for working with {@link AnnotationMirror}. + */ +final class AnnotationUtils { + private final Elements mElementUtils; + private final Types mTypeUtils; + + AnnotationUtils(ProcessingEnvironment processingEnv) { + mElementUtils = processingEnv.getElementUtils(); + mTypeUtils = processingEnv.getTypeUtils(); + } + + /** + * Get a {@link AnnotationMirror} specified by name from an {@link Element}. + * + * @param qualifiedName The fully qualified name of the annotation to search for + * @param element The element to search for annotations on + * @return The mirror of the requested annotation + * @throws ProcessingException If there is not exactly one of the requested annotation. + */ + AnnotationMirror exactlyOneMirror(String qualifiedName, Element element) { + final Element targetTypeElment = mElementUtils.getTypeElement(qualifiedName); + final TypeMirror targetType = targetTypeElment.asType(); + AnnotationMirror result = null; + + for (AnnotationMirror annotation : element.getAnnotationMirrors()) { + final TypeMirror annotationType = annotation.getAnnotationType().asElement().asType(); + if (mTypeUtils.isSameType(annotationType, targetType)) { + if (result == null) { + result = annotation; + } else { + final String message = String.format( + "Element had multiple instances of @%s, expected exactly one", + targetTypeElment.getSimpleName()); + + throw new ProcessingException(message, element, annotation); + } + } + } + + if (result == null) { + final String message = String.format( + "Expected an @%s annotation, found none", targetTypeElment.getSimpleName()); + throw new ProcessingException(message, element); + } else { + return result; + } + } + + /** + * Extract a string-valued property from an {@link AnnotationMirror}. + * + * @param propertyName The name of the requested property + * @param annotationMirror The mirror to search for the property + * @return The String value of the annotation or null + */ + Optional<String> stringProperty(String propertyName, AnnotationMirror annotationMirror) { + final AnnotationValue value = valueByName(propertyName, annotationMirror); + if (value != null) { + return Optional.of((String) value.getValue()); + } else { + return Optional.empty(); + } + } + + + /** + * Extract a {@link AnnotationValue} from a mirror by string property name. + * + * @param propertyName The name of the property requested property + * @param annotationMirror + * @return + */ + AnnotationValue valueByName(String propertyName, AnnotationMirror annotationMirror) { + final Map<? extends ExecutableElement, ? extends AnnotationValue> valueMap = + annotationMirror.getElementValues(); + + for (ExecutableElement method : valueMap.keySet()) { + if (method.getSimpleName().contentEquals(propertyName)) { + return valueMap.get(method); + } + } + + return null; + } + +} diff --git a/tools/processors/view_inspector/src/java/android/processor/view/inspector/InspectableClassModel.java b/tools/processors/view_inspector/src/java/android/processor/view/inspector/InspectableClassModel.java new file mode 100644 index 000000000000..f0b0ff6b979f --- /dev/null +++ b/tools/processors/view_inspector/src/java/android/processor/view/inspector/InspectableClassModel.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.processor.view.inspector; + +import com.squareup.javapoet.ClassName; + +import java.util.Optional; + +/** + * Model of an inspectable class derived from annotations. + * + * This class does not use any {javax.lang.model} objects to facilitate building models for testing + * {@link InspectionCompanionGenerator}. + */ +public final class InspectableClassModel { + private final ClassName mClassName; + private Optional<String> mNodeName = Optional.empty(); + + /** + * @param className The name of the modeled class + */ + public InspectableClassModel(ClassName className) { + mClassName = className; + } + + public ClassName getClassName() { + return mClassName; + } + + public Optional<String> getNodeName() { + return mNodeName; + } + + public void setNodeName(Optional<String> nodeName) { + mNodeName = nodeName; + } +} diff --git a/tools/processors/view_inspector/src/java/android/processor/view/inspector/InspectableNodeNameProcessor.java b/tools/processors/view_inspector/src/java/android/processor/view/inspector/InspectableNodeNameProcessor.java new file mode 100644 index 000000000000..a186a82af160 --- /dev/null +++ b/tools/processors/view_inspector/src/java/android/processor/view/inspector/InspectableNodeNameProcessor.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.processor.view.inspector; + +import java.util.Optional; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; + +/** + * Process {InspectableNodeName} annotations + * + * @see android.view.inspector.InspectableNodeName + */ +public final class InspectableNodeNameProcessor implements ModelProcessor { + private final String mQualifiedName; + private final ProcessingEnvironment mProcessingEnv; + private final AnnotationUtils mAnnotationUtils; + + /** + * @param annotationQualifiedName The qualified name of the annotation to process + * @param processingEnv The processing environment from the parent processor + */ + public InspectableNodeNameProcessor( + String annotationQualifiedName, + ProcessingEnvironment processingEnv) { + mQualifiedName = annotationQualifiedName; + mProcessingEnv = processingEnv; + mAnnotationUtils = new AnnotationUtils(processingEnv); + } + + /** + * Set the node name on the model if one is supplied. + * + * If the model already has a different node name, the node name will not be updated, and + * the processor will print an error the the messager. + * + * @param element The annotated element to operate on + * @param model The model this element should be merged into + */ + @Override + public void process(Element element, InspectableClassModel model) { + try { + final AnnotationMirror mirror = + mAnnotationUtils.exactlyOneMirror(mQualifiedName, element); + final Optional<String> nodeName = mAnnotationUtils.stringProperty("value", mirror); + + if (!model.getNodeName().isPresent() || model.getNodeName().equals(nodeName)) { + model.setNodeName(nodeName); + } else { + final String message = String.format( + "Node name was already set to \"%s\", refusing to change it to \"%s\".", + model.getNodeName().get(), + nodeName); + throw new ProcessingException(message, element, mirror); + } + } catch (ProcessingException processingException) { + processingException.print(mProcessingEnv.getMessager()); + } + } +} diff --git a/tools/processors/view_inspector/src/java/android/processor/view/inspector/InspectionCompanionGenerator.java b/tools/processors/view_inspector/src/java/android/processor/view/inspector/InspectionCompanionGenerator.java new file mode 100644 index 000000000000..fe0153d7f9af --- /dev/null +++ b/tools/processors/view_inspector/src/java/android/processor/view/inspector/InspectionCompanionGenerator.java @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.processor.view.inspector; + +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.JavaFile; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.ParameterizedTypeName; +import com.squareup.javapoet.TypeSpec; + +import java.io.IOException; +import java.util.Optional; + +import javax.annotation.processing.Filer; +import javax.lang.model.element.Modifier; + +/** + * Generates a source file defining a {@link android.view.inspector.InspectionCompanion}. + */ +public final class InspectionCompanionGenerator { + private final Filer mFiler; + private final Class mRequestingClass; + + /** + * @param filer A filer to write the generated source to + * @param requestingClass A class object representing the class that invoked the generator + */ + public InspectionCompanionGenerator(final Filer filer, final Class requestingClass) { + mFiler = filer; + mRequestingClass = requestingClass; + } + + /** + * Generate and write an inspection companion. + * + * @param model The model to generated + * @throws IOException From the Filer + */ + public void generate(InspectableClassModel model) throws IOException { + generateFile(model).writeTo(mFiler); + } + + /** + * Generate a {@link JavaFile} from a model. + * + * This is package-public for testing. + * + * @param model The model to generate from + * @return A generated file of an {@link android.view.inspector.InspectionCompanion} + */ + JavaFile generateFile(InspectableClassModel model) { + return JavaFile + .builder(model.getClassName().packageName(), generateTypeSpec(model)) + .indent(" ") + .build(); + } + + /** + * Generate a {@link TypeSpec} for the {@link android.view.inspector.InspectionCompanion} + * for the supplied model. + * + * @param model The model to generate from + * @return A TypeSpec of the inspection companion + */ + private TypeSpec generateTypeSpec(InspectableClassModel model) { + TypeSpec.Builder builder = TypeSpec + .classBuilder(generateClassName(model)) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addSuperinterface(ParameterizedTypeName.get( + ClassName.get("android.view.inspector", "InspectionCompanion"), + model.getClassName())) + .addJavadoc("Inspection companion for {@link $T}.\n\n", model.getClassName()) + .addJavadoc("Generated by {@link $T}\n", getClass()) + .addJavadoc("on behalf of {@link $T}.\n", mRequestingClass) + .addMethod(generateMapProperties(model)) + .addMethod(generateReadProperties(model)); + + generateGetNodeName(model).ifPresent(builder::addMethod); + + return builder.build(); + } + + /** + * Generate a method definition for + * {@link android.view.inspector.InspectionCompanion#getNodeName()}, if needed. + * + * If {@link InspectableClassModel#getNodeName()} is empty, This method returns an empty + * optional, otherwise, it generates a simple method that returns the string value of the + * node name. + * + * @param model The model to generate from + * @return The method definition or an empty Optional + */ + private Optional<MethodSpec> generateGetNodeName(InspectableClassModel model) { + return model.getNodeName().map(nodeName -> MethodSpec.methodBuilder("getNodeName") + .addAnnotation(Override.class) + .addModifiers(Modifier.PUBLIC) + .returns(String.class) + .addStatement("return $S", nodeName) + .build()); + } + + /** + * Generate a method definition for + * {@link android.view.inspector.InspectionCompanion#mapProperties( + * android.view.inspector.PropertyMapper)}. + * + * TODO: implement + * + * @param model The model to generate from + * @return The method definition + */ + private MethodSpec generateMapProperties(InspectableClassModel model) { + final ClassName propertyMapper = ClassName.get( + "android.view.inspector", "PropertyMapper"); + + return MethodSpec.methodBuilder("mapProperties") + .addAnnotation(Override.class) + .addModifiers(Modifier.PUBLIC) + .addParameter(propertyMapper, "propertyMapper") + // TODO: add method body + .build(); + } + + /** + * Generate a method definition for + * {@link android.view.inspector.InspectionCompanion#readProperties( + * Object, android.view.inspector.PropertyReader)}. + * + * TODO: implement + * + * @param model The model to generate from + * @return The method definition + */ + private MethodSpec generateReadProperties(InspectableClassModel model) { + final ClassName propertyReader = ClassName.get( + "android.view.inspector", "PropertyReader"); + + return MethodSpec.methodBuilder("readProperties") + .addAnnotation(Override.class) + .addModifiers(Modifier.PUBLIC) + .addParameter(model.getClassName(), "inspectable") + .addParameter(propertyReader, "propertyReader") + // TODO: add method body + .build(); + } + + /** + * Generate the final class name for the inspection companion from the model's class name. + * + * The generated class is added to the same package as the source class. If the class in the + * model is a nested class, the nested class names are joined with {"$"}. The suffix + * {"$$InspectionCompanion"} is always added the the generated name. E.g.: For modeled class + * {com.example.Outer.Inner}, the generated class name will be + * {com.example.Outer$Inner$$InspectionCompanion}. + * + * @param model The model to generate from + * @return A class name for the generated inspection companion class + */ + private ClassName generateClassName(final InspectableClassModel model) { + final ClassName className = model.getClassName(); + + return ClassName.get( + className.packageName(), + String.join("$", className.simpleNames()) + "$$InspectionCompanion"); + } +} diff --git a/tools/processors/view_inspector/src/java/android/processor/view/inspector/ModelProcessor.java b/tools/processors/view_inspector/src/java/android/processor/view/inspector/ModelProcessor.java new file mode 100644 index 000000000000..3ffcff8a87d3 --- /dev/null +++ b/tools/processors/view_inspector/src/java/android/processor/view/inspector/ModelProcessor.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.processor.view.inspector; + +import javax.lang.model.element.Element; + +/** + * An interface for annotation processors that operate on a single element and a class model. + */ +public interface ModelProcessor { + /** + * Process the supplied element, mutating the model as needed. + * + * @param element The annotated element to operate on + * @param model The model this element should be merged into + */ + void process(Element element, InspectableClassModel model); +} diff --git a/tools/processors/view_inspector/src/java/android/processor/view/inspector/PlatformInspectableProcessor.java b/tools/processors/view_inspector/src/java/android/processor/view/inspector/PlatformInspectableProcessor.java new file mode 100644 index 000000000000..e531b67d9ea2 --- /dev/null +++ b/tools/processors/view_inspector/src/java/android/processor/view/inspector/PlatformInspectableProcessor.java @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.processor.view.inspector; + +import static javax.tools.Diagnostic.Kind.ERROR; + +import com.squareup.javapoet.ClassName; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.RoundEnvironment; +import javax.annotation.processing.SupportedAnnotationTypes; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.TypeElement; + + +/** + * An annotation processor for the platform inspectable annotations. + * + * It mostly delegates to {@link ModelProcessor} and {@link InspectionCompanionGenerator}. This + * modular architecture allows the core generation code to be reused for comparable annotations + * outside the platform, such as in AndroidX. + * + * @see android.view.inspector.InspectableNodeName + * @see android.view.inspector.InspectableProperty + */ +@SupportedAnnotationTypes({ + PlatformInspectableProcessor.NODE_NAME_QUALIFIED_NAME +}) +public final class PlatformInspectableProcessor extends AbstractProcessor { + static final String NODE_NAME_QUALIFIED_NAME = + "android.view.inspector.InspectableNodeName"; + + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latest(); + } + + @Override + public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { + final Map<String, InspectableClassModel> modelMap = new HashMap<>(); + + for (TypeElement annotation : annotations) { + if (annotation.getQualifiedName().contentEquals(NODE_NAME_QUALIFIED_NAME)) { + runModelProcessor( + roundEnv.getElementsAnnotatedWith(annotation), + new InspectableNodeNameProcessor(NODE_NAME_QUALIFIED_NAME, processingEnv), + modelMap); + + + } else { + fail("Unexpected annotation type", annotation); + } + } + + final InspectionCompanionGenerator generator = + new InspectionCompanionGenerator(processingEnv.getFiler(), getClass()); + + for (InspectableClassModel model : modelMap.values()) { + try { + generator.generate(model); + } catch (IOException ioException) { + fail(String.format( + "Unable to generate inspection companion for %s due to %s", + model.getClassName().toString(), + ioException.getMessage())); + } + } + + return true; + } + + /** + * Run a {@link ModelProcessor} for a set of elements + * + * @param elements Elements to process, should be annotated correctly + * @param processor The processor to use + * @param modelMap A map of qualified class names to models + */ + private void runModelProcessor( + Set<? extends Element> elements, + ModelProcessor processor, + Map<String, InspectableClassModel> modelMap) { + for (Element element : elements) { + final Optional<TypeElement> classElement = enclosingClassElement(element); + + if (!classElement.isPresent()) { + fail("Element not contained in a class", element); + break; + } + + final InspectableClassModel model = modelMap.computeIfAbsent( + classElement.get().getQualifiedName().toString(), + k -> new InspectableClassModel(ClassName.get(classElement.get()))); + + processor.process(element, model); + } + } + + /** + * Get the nearest enclosing class if there is one. + * + * If {@param element} represents a class, it will be returned wrapped in an optional. + * + * @param element An element to search from + * @return A TypeElement of the nearest enclosing class or an empty optional + */ + private static Optional<TypeElement> enclosingClassElement(Element element) { + Element cursor = element; + + while (cursor != null) { + if (cursor.getKind() == ElementKind.CLASS) { + return Optional.of((TypeElement) cursor); + } + + cursor = cursor.getEnclosingElement(); + } + + return Optional.empty(); + } + + /** + * Print message and fail the build. + * + * @param message Message to print + */ + private void fail(String message) { + processingEnv.getMessager().printMessage(ERROR, message); + } + + /** + * Print message and fail the build. + * + * @param message Message to print + * @param element The element that failed + */ + private void fail(String message, Element element) { + processingEnv.getMessager().printMessage(ERROR, message, element); + } +} diff --git a/tools/processors/view_inspector/src/java/android/processor/view/inspector/ProcessingException.java b/tools/processors/view_inspector/src/java/android/processor/view/inspector/ProcessingException.java new file mode 100644 index 000000000000..6360e0a2de39 --- /dev/null +++ b/tools/processors/view_inspector/src/java/android/processor/view/inspector/ProcessingException.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.processor.view.inspector; + +import static javax.tools.Diagnostic.Kind.ERROR; + +import javax.annotation.processing.Messager; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.Element; + +/** + * Internal exception used to signal an error processing an annotation. + */ +final class ProcessingException extends RuntimeException { + private final Element mElement; + private final AnnotationMirror mAnnotationMirror; + private final AnnotationValue mAnnotationValue; + + ProcessingException(String message) { + this(message, null, null, null); + } + + ProcessingException(String message, Element element) { + this(message, element, null, null); + } + + ProcessingException(String message, Element element, AnnotationMirror annotationMirror) { + this(message, element, annotationMirror, null); + } + + ProcessingException( + String message, + Element element, + AnnotationMirror annotationMirror, + AnnotationValue annotationValue) { + super(message); + mElement = element; + mAnnotationMirror = annotationMirror; + mAnnotationValue = annotationValue; + } + + /** + * Prints the exception to a Messager. + * + * @param messager A Messager to print to + */ + void print(Messager messager) { + if (mElement != null) { + if (mAnnotationMirror != null) { + if (mAnnotationValue != null) { + messager.printMessage( + ERROR, getMessage(), mElement, mAnnotationMirror, mAnnotationValue); + } else { + messager.printMessage(ERROR, getMessage(), mElement, mAnnotationMirror); + } + } else { + messager.printMessage(ERROR, getMessage(), mElement); + } + } else { + messager.printMessage(ERROR, getMessage()); + } + } +} diff --git a/tools/processors/view_inspector/src/resources/META-INF/services/javax.annotation.processing.Processor b/tools/processors/view_inspector/src/resources/META-INF/services/javax.annotation.processing.Processor new file mode 100644 index 000000000000..fa4f71ffd0fa --- /dev/null +++ b/tools/processors/view_inspector/src/resources/META-INF/services/javax.annotation.processing.Processor @@ -0,0 +1 @@ +android.processor.inspector.view.PlatformInspectableProcessor diff --git a/tools/processors/view_inspector/test/java/android/processor/view/inspector/InspectionCompanionGeneratorTest.java b/tools/processors/view_inspector/test/java/android/processor/view/inspector/InspectionCompanionGeneratorTest.java new file mode 100644 index 000000000000..c02b0bdba1cf --- /dev/null +++ b/tools/processors/view_inspector/test/java/android/processor/view/inspector/InspectionCompanionGeneratorTest.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.processor.view.inspector; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNotNull; +import static junit.framework.TestCase.fail; + +import com.google.common.base.Charsets; +import com.google.common.io.Resources; +import com.squareup.javapoet.ClassName; + +import org.junit.Before; +import org.junit.Test; + +import java.io.IOException; +import java.net.URL; +import java.util.Optional; + +/** + * Tests for {@link InspectionCompanionGenerator} + */ +public class InspectionCompanionGeneratorTest { + private static final String RESOURCE_PATH_TEMPLATE = + "android/processor/view/inspector/InspectionCompanionGeneratorTest/%s.java.txt"; + private static final ClassName TEST_CLASS_NAME = + ClassName.get("com.android.inspectable", "TestInspectable"); + private InspectableClassModel mModel; + private InspectionCompanionGenerator mGenerator; + + @Before + public void setup() { + mModel = new InspectableClassModel(TEST_CLASS_NAME); + mGenerator = new InspectionCompanionGenerator(null, getClass()); + } + + @Test + public void testNodeName() { + mModel.setNodeName(Optional.of("NodeName")); + assertGeneratedFileEquals("NodeName"); + } + + @Test + public void testNestedClass() { + mModel = new InspectableClassModel( + ClassName.get("com.android.inspectable", "Outer", "Inner")); + assertGeneratedFileEquals("NestedClass"); + } + + private void assertGeneratedFileEquals(String fileName) { + assertEquals( + loadTextResource(String.format(RESOURCE_PATH_TEMPLATE, fileName)), + mGenerator.generateFile(mModel).toString()); + } + + private String loadTextResource(String path) { + try { + final URL url = Resources.getResource(path); + assertNotNull(String.format("Resource file not found: %s", path), url); + return Resources.toString(url, Charsets.UTF_8); + } catch (IOException e) { + fail(e.getMessage()); + return null; + } + } +} diff --git a/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/NestedClass.java.txt b/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/NestedClass.java.txt new file mode 100644 index 000000000000..e5fb6a2129f5 --- /dev/null +++ b/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/NestedClass.java.txt @@ -0,0 +1,22 @@ +package com.android.inspectable; + +import android.view.inspector.InspectionCompanion; +import android.view.inspector.PropertyMapper; +import android.view.inspector.PropertyReader; +import java.lang.Override; + +/** + * Inspection companion for {@link Outer.Inner}. + * + * Generated by {@link android.processor.view.inspector.InspectionCompanionGenerator} + * on behalf of {@link android.processor.view.inspector.InspectionCompanionGeneratorTest}. + */ +public final class Outer$Inner$$InspectionCompanion implements InspectionCompanion<Outer.Inner> { + @Override + public void mapProperties(PropertyMapper propertyMapper) { + } + + @Override + public void readProperties(Outer.Inner inspectable, PropertyReader propertyReader) { + } +} diff --git a/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/NodeName.java.txt b/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/NodeName.java.txt new file mode 100644 index 000000000000..a334f50bbdf5 --- /dev/null +++ b/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/NodeName.java.txt @@ -0,0 +1,28 @@ +package com.android.inspectable; + +import android.view.inspector.InspectionCompanion; +import android.view.inspector.PropertyMapper; +import android.view.inspector.PropertyReader; +import java.lang.Override; +import java.lang.String; + +/** + * Inspection companion for {@link TestInspectable}. + * + * Generated by {@link android.processor.view.inspector.InspectionCompanionGenerator} + * on behalf of {@link android.processor.view.inspector.InspectionCompanionGeneratorTest}. + */ +public final class TestInspectable$$InspectionCompanion implements InspectionCompanion<TestInspectable> { + @Override + public void mapProperties(PropertyMapper propertyMapper) { + } + + @Override + public void readProperties(TestInspectable inspectable, PropertyReader propertyReader) { + } + + @Override + public String getNodeName() { + return "NodeName"; + } +} |