Annotation processor for @InspectableNodeName
Bug: 117616612 Test: atest --host view-inspector-annotation-processor-test Change-Id: I48f62544655adbc33e3ccdd3301d6dc471fe4163
This commit is contained in:
12
Android.bp
12
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 {
|
||||
|
||||
27
tools/processors/view_inspector/Android.bp
Normal file
27
tools/processors/view_inspector/Android.bp
Normal file
@@ -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"],
|
||||
}
|
||||
7
tools/processors/view_inspector/TEST_MAPPING
Normal file
7
tools/processors/view_inspector/TEST_MAPPING
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"presubmit": [
|
||||
{
|
||||
"name": "view-inspector-annotation-processor-test"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
android.processor.inspector.view.PlatformInspectableProcessor
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user