Annotation processor for @InspectableNodeName

Bug: 117616612
Test: atest --host view-inspector-annotation-processor-test
Change-Id: I48f62544655adbc33e3ccdd3301d6dc471fe4163
This commit is contained in:
Ashley Rose
2018-12-07 17:20:25 -05:00
parent 52fe5dd97f
commit de080eb7b0
14 changed files with 861 additions and 12 deletions

View File

@@ -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 {

View 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"],
}

View File

@@ -0,0 +1,7 @@
{
"presubmit": [
{
"name": "view-inspector-annotation-processor-test"
}
]
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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());
}
}
}

View File

@@ -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");
}
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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());
}
}
}

View File

@@ -0,0 +1 @@
android.processor.inspector.view.PlatformInspectableProcessor

View File

@@ -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;
}
}
}

View File

@@ -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) {
}
}

View File

@@ -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";
}
}