summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Ashley Rose <ashleyrose@google.com> 2018-12-13 18:06:30 -0500
committer Ashley Rose <ashleyrose@google.com> 2019-01-18 16:47:29 -0500
commitc1a4decb3945604583badf4b5eb5b5f0c6c39375 (patch)
tree7b8fe72bac7459acd84382def4aaf772cfdafbd8
parent61ae7ed2c6697d95c06422cad6aa4d4063dd4b24 (diff)
Annotation processing for @InspectableProperty
Test: atest --host view-inspector-annotation-processor-test Bug: 117616612 Change-Id: Ia9641b4efae5f6945084849309f81f2d31faf2ec
-rw-r--r--api/test-current.txt27
-rw-r--r--core/java/android/view/inspector/InspectableNodeName.java3
-rw-r--r--core/java/android/view/inspector/InspectableProperty.java20
-rw-r--r--tools/processors/view_inspector/Android.bp6
-rw-r--r--tools/processors/view_inspector/TEST_MAPPING2
-rw-r--r--tools/processors/view_inspector/src/java/android/processor/view/inspector/AnnotationUtils.java121
-rw-r--r--tools/processors/view_inspector/src/java/android/processor/view/inspector/InspectableClassModel.java23
-rw-r--r--tools/processors/view_inspector/src/java/android/processor/view/inspector/InspectableNodeNameProcessor.java7
-rw-r--r--tools/processors/view_inspector/src/java/android/processor/view/inspector/InspectablePropertyProcessor.java408
-rw-r--r--tools/processors/view_inspector/src/java/android/processor/view/inspector/InspectionCompanionGenerator.java16
-rw-r--r--tools/processors/view_inspector/src/java/android/processor/view/inspector/ModelProcessor.java2
-rw-r--r--tools/processors/view_inspector/src/java/android/processor/view/inspector/PlatformInspectableProcessor.java12
-rw-r--r--tools/processors/view_inspector/src/java/android/processor/view/inspector/ProcessingException.java2
-rw-r--r--tools/processors/view_inspector/src/resources/META-INF/services/javax.annotation.processing.Processor2
-rw-r--r--tools/processors/view_inspector/test/java/android/processor/view/inspector/InspectionCompanionGeneratorTest.java48
-rw-r--r--tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/NoAttributeId.java.txt3
16 files changed, 622 insertions, 80 deletions
diff --git a/api/test-current.txt b/api/test-current.txt
index fbba73c46ab4..08531829cba1 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -2124,6 +2124,33 @@ package android.view.inputmethod {
}
+package android.view.inspector {
+
+ public abstract class InspectableNodeName implements java.lang.annotation.Annotation {
+ }
+
+ public abstract class InspectableProperty implements java.lang.annotation.Annotation {
+ }
+
+ public static abstract class InspectableProperty.EnumMap implements java.lang.annotation.Annotation {
+ }
+
+ public static abstract class InspectableProperty.FlagMap implements java.lang.annotation.Annotation {
+ }
+
+ public static final class InspectableProperty.ValueType extends java.lang.Enum {
+ method public static android.view.inspector.InspectableProperty.ValueType valueOf(java.lang.String);
+ method public static final android.view.inspector.InspectableProperty.ValueType[] values();
+ enum_constant public static final android.view.inspector.InspectableProperty.ValueType COLOR;
+ enum_constant public static final android.view.inspector.InspectableProperty.ValueType GRAVITY;
+ enum_constant public static final android.view.inspector.InspectableProperty.ValueType INFERRED;
+ enum_constant public static final android.view.inspector.InspectableProperty.ValueType INT_ENUM;
+ enum_constant public static final android.view.inspector.InspectableProperty.ValueType INT_FLAG;
+ enum_constant public static final android.view.inspector.InspectableProperty.ValueType NONE;
+ }
+
+}
+
package android.widget {
public abstract class AbsListView extends android.widget.AdapterView implements android.widget.Filter.FilterListener android.text.TextWatcher android.view.ViewTreeObserver.OnGlobalLayoutListener android.view.ViewTreeObserver.OnTouchModeChangeListener {
diff --git a/core/java/android/view/inspector/InspectableNodeName.java b/core/java/android/view/inspector/InspectableNodeName.java
index ea94ad4c5df8..7b9a507ee45d 100644
--- a/core/java/android/view/inspector/InspectableNodeName.java
+++ b/core/java/android/view/inspector/InspectableNodeName.java
@@ -19,6 +19,8 @@ package android.view.inspector;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.SOURCE;
+import android.annotation.TestApi;
+
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
@@ -39,6 +41,7 @@ import java.lang.annotation.Target;
*/
@Target({TYPE})
@Retention(SOURCE)
+@TestApi
public @interface InspectableNodeName {
/**
* The display name for nodes of this type.
diff --git a/core/java/android/view/inspector/InspectableProperty.java b/core/java/android/view/inspector/InspectableProperty.java
index a57470c7c908..355ff1d85e1f 100644
--- a/core/java/android/view/inspector/InspectableProperty.java
+++ b/core/java/android/view/inspector/InspectableProperty.java
@@ -20,6 +20,7 @@ import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.SOURCE;
+import android.annotation.TestApi;
import android.content.res.Resources;
import java.lang.annotation.Retention;
@@ -40,6 +41,7 @@ import java.lang.annotation.Target;
*/
@Target({METHOD})
@Retention(SOURCE)
+@TestApi
public @interface InspectableProperty {
/**
* The name of the property.
@@ -86,7 +88,6 @@ public @interface InspectableProperty {
*
* @return An array of {@link EnumMap}, empty if not applicable
* @see android.annotation.IntDef
- * @see IntEnumMapping
*/
EnumMap[] enumMapping() default {};
@@ -109,6 +110,7 @@ public @interface InspectableProperty {
*/
@Target({TYPE})
@Retention(SOURCE)
+ @TestApi
@interface EnumMap {
/**
* The string name of this enumeration value.
@@ -133,6 +135,7 @@ public @interface InspectableProperty {
*/
@Target({TYPE})
@Retention(SOURCE)
+ @TestApi
@interface FlagMap {
/**
* The string name of this flag.
@@ -167,15 +170,22 @@ public @interface InspectableProperty {
*
* @hide
*/
+ @TestApi
enum ValueType {
/**
* No special handling, property is considered to be a numeric value.
+ *
+ * @hide
*/
+ @TestApi
NONE,
/**
* The default the annotation processor infers the value type from context.
+ *
+ * @hide
*/
+ @TestApi
INFERRED,
/**
@@ -184,7 +194,9 @@ public @interface InspectableProperty {
* This is inferred if {@link #enumMapping()} is specified.
*
* @see EnumMap
+ * @hide
*/
+ @TestApi
INT_ENUM,
/**
@@ -193,7 +205,9 @@ public @interface InspectableProperty {
* This is inferred if {@link #flagMapping()} is specified.
*
* @see FlagMap
+ * @hide
*/
+ @TestApi
INT_FLAG,
/**
@@ -203,7 +217,9 @@ public @interface InspectableProperty {
* {@link android.annotation.ColorLong} on the getter method.
*
* @see android.graphics.Color
+ * @hide
*/
+ @TestApi
COLOR,
/**
@@ -212,7 +228,9 @@ public @interface InspectableProperty {
* This type is not inferred, and is non-trivial to represent using {@link FlagMap}.
*
* @see android.view.Gravity
+ * @hide
*/
+ @TestApi
GRAVITY
}
}
diff --git a/tools/processors/view_inspector/Android.bp b/tools/processors/view_inspector/Android.bp
index ca6b3c4572f5..9b5df56e3987 100644
--- a/tools/processors/view_inspector/Android.bp
+++ b/tools/processors/view_inspector/Android.bp
@@ -5,7 +5,7 @@ java_library_host {
java_resource_dirs: ["src/resources"],
static_libs: [
- "javapoet",
+ "javapoet",
],
use_tools_jar: true,
@@ -18,9 +18,9 @@ java_test_host {
java_resource_dirs: ["test/resources"],
static_libs: [
- "guava",
"junit",
- "view-inspector-annotation-processor",
+ "guava",
+ "view-inspector-annotation-processor"
],
test_suites: ["general-tests"],
diff --git a/tools/processors/view_inspector/TEST_MAPPING b/tools/processors/view_inspector/TEST_MAPPING
index a91b5b452c39..b4c9cab6916e 100644
--- a/tools/processors/view_inspector/TEST_MAPPING
+++ b/tools/processors/view_inspector/TEST_MAPPING
@@ -2,6 +2,8 @@
"presubmit": [
{
"name": "view-inspector-annotation-processor-test"
+ }, {
+ "name": "CtsViewInspectorAnnotationProcessorTestCases"
}
]
}
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
index f157949f4d1b..fc4cd01a5a2a 100644
--- 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
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright 2019 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.
@@ -24,6 +24,7 @@ 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.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
@@ -78,40 +79,126 @@ final class AnnotationUtils {
}
/**
- * Extract a string-valued property from an {@link AnnotationMirror}.
+ * Determine if an annotation with the supplied qualified name is present on the element.
*
- * @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
+ * @param element The element to check for the presence of an annotation
+ * @param annotationQualifiedName The name of the annotation to check for
+ * @return True if the annotation is present, false otherwise
*/
- 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();
+ boolean hasAnnotation(Element element, String annotationQualifiedName) {
+ final TypeElement namedElement = mElementUtils.getTypeElement(annotationQualifiedName);
+
+ if (namedElement != null) {
+ final TypeMirror annotationType = namedElement.asType();
+
+ for (AnnotationMirror annotation : element.getAnnotationMirrors()) {
+ if (mTypeUtils.isSubtype(annotation.getAnnotationType(), annotationType)) {
+ return true;
+ }
+ }
}
+
+ return false;
}
+ /**
+ * Get the typed value of an annotation property by name.
+ *
+ * The returned optional will be empty if the value was left at the default, or if the value
+ * of the property is null.
+ *
+ * @param propertyName The name of the property to search for
+ * @param valueClass The expected class of the property value
+ * @param element The element the annotation is on, used for exceptions
+ * @param annotationMirror An annotation mirror to search for the property
+ * @param <T> The type of the value
+ * @return An optional containing the typed value of the named property
+ */
+ <T> Optional<T> typedValueByName(
+ String propertyName,
+ Class<T> valueClass,
+ Element element,
+ AnnotationMirror annotationMirror) {
+ return valueByName(propertyName, annotationMirror).map(annotationValue -> {
+ final Object value = annotationValue.getValue();
+
+ if (value == null) {
+ throw new ProcessingException(
+ String.format(
+ "Unexpected null value for annotation property \"%s\".",
+ propertyName),
+ element,
+ annotationMirror,
+ annotationValue);
+ }
+
+ if (valueClass.isAssignableFrom(value.getClass())) {
+ return valueClass.cast(value);
+ } else {
+ throw new ProcessingException(
+ String.format(
+ "Expected annotation property \"%s\" to have type %s, but got %s.",
+ propertyName,
+ valueClass.getCanonicalName(),
+ value.getClass().getCanonicalName()),
+ element,
+ annotationMirror,
+ annotationValue);
+ }
+ });
+ }
+
+ /**
+ * Get the untyped value of an annotation property by name.
+ *
+ * The returned optional will be empty if the value was left at the default, or if the value
+ * of the property is null.
+ *
+ * @param propertyName The name of the property to search for
+ * @param element The element the annotation is on, used for exceptions
+ * @param annotationMirror An annotation mirror to search for the property
+ * @return An optional containing the untyped value of the named property
+ * @see AnnotationValue#getValue()
+ */
+ Optional<Object> untypedValueByName(
+ String propertyName,
+ Element element,
+ AnnotationMirror annotationMirror) {
+ return valueByName(propertyName, annotationMirror).map(annotationValue -> {
+ final Object value = annotationValue.getValue();
+
+ if (value == null) {
+ throw new ProcessingException(
+ String.format(
+ "Unexpected null value for annotation property \"%s\".",
+ propertyName),
+ element,
+ annotationMirror,
+ annotationValue);
+ }
+
+ return value;
+ });
+ }
/**
* Extract a {@link AnnotationValue} from a mirror by string property name.
*
* @param propertyName The name of the property requested property
- * @param annotationMirror
- * @return
+ * @param annotationMirror The mirror to search for the property
+ * @return The value of the property
*/
- AnnotationValue valueByName(String propertyName, AnnotationMirror annotationMirror) {
+ Optional<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 Optional.ofNullable(valueMap.get(method));
}
}
- return null;
+ // Property not explicitly defined, use default value.
+ return Optional.empty();
}
-
}
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
index 579745d2aaef..f1ebb87fed4d 100644
--- 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
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright 2019 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.
@@ -21,6 +21,7 @@ import com.squareup.javapoet.ClassName;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
+import java.util.Objects;
import java.util.Optional;
/**
@@ -70,7 +71,7 @@ public final class InspectableClassModel {
* @return The property or an empty optional
*/
public Optional<Property> getProperty(String name) {
- return Optional.of(mPropertyMap.get(name));
+ return Optional.ofNullable(mPropertyMap.get(name));
}
/**
@@ -87,13 +88,15 @@ public final class InspectableClassModel {
*/
public static final class Property {
private final String mName;
- private String mGetter;
- private Type mType;
+ private final String mGetter;
+ private final Type mType;
private boolean mAttributeIdInferrableFromR = true;
private int mAttributeId = 0;
- public Property(String name) {
- mName = name;
+ public Property(String name, String getter, Type type) {
+ mName = Objects.requireNonNull(name, "Name must not be null");
+ mGetter = Objects.requireNonNull(getter, "Getter must not be null");
+ mType = Objects.requireNonNull(type, "Type must not be null");
}
public int getAttributeId() {
@@ -126,18 +129,10 @@ public final class InspectableClassModel {
return mGetter;
}
- public void setGetter(String getter) {
- mGetter = getter;
- }
-
public Type getType() {
return mType;
}
- public void setType(Type type) {
- mType = type;
- }
-
public enum Type {
/** Primitive or boxed {@code boolean} */
BOOLEAN,
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
index a186a82af160..46819b28c1e8 100644
--- 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
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright 2019 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.
@@ -23,7 +23,7 @@ import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
/**
- * Process {InspectableNodeName} annotations
+ * Process {@code @InspectableNodeName} annotations.
*
* @see android.view.inspector.InspectableNodeName
*/
@@ -58,7 +58,8 @@ public final class InspectableNodeNameProcessor implements ModelProcessor {
try {
final AnnotationMirror mirror =
mAnnotationUtils.exactlyOneMirror(mQualifiedName, element);
- final Optional<String> nodeName = mAnnotationUtils.stringProperty("value", mirror);
+ final Optional<String> nodeName = mAnnotationUtils
+ .typedValueByName("value", String.class, element, mirror);
if (!model.getNodeName().isPresent() || model.getNodeName().equals(nodeName)) {
model.setNodeName(nodeName);
diff --git a/tools/processors/view_inspector/src/java/android/processor/view/inspector/InspectablePropertyProcessor.java b/tools/processors/view_inspector/src/java/android/processor/view/inspector/InspectablePropertyProcessor.java
new file mode 100644
index 000000000000..f666be7a2a61
--- /dev/null
+++ b/tools/processors/view_inspector/src/java/android/processor/view/inspector/InspectablePropertyProcessor.java
@@ -0,0 +1,408 @@
+/*
+ * Copyright 2019 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 android.processor.view.inspector.InspectableClassModel.Property;
+
+import java.util.Set;
+import java.util.regex.Pattern;
+
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.NoType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+
+/**
+ * Process {@code @InspectableProperty} annotations.
+ *
+ * @see android.view.inspector.InspectableProperty
+ */
+public final class InspectablePropertyProcessor implements ModelProcessor {
+ private final String mQualifiedName;
+ private final ProcessingEnvironment mProcessingEnv;
+ private final AnnotationUtils mAnnotationUtils;
+
+ /**
+ * Regex that matches methods names of the form {@code #getValue()}.
+ */
+ private static final Pattern GETTER_GET_PREFIX = Pattern.compile("\\Aget[A-Z]");
+
+ /**
+ * Regex that matches method name of the form {@code #isPredicate()}.
+ */
+ private static final Pattern GETTER_IS_PREFIX = Pattern.compile("\\Ais[A-Z]");
+
+ /**
+ * Set of android and androidx annotation qualified names for colors packed into {@code int}.
+ *
+ * @see android.annotation.ColorInt
+ */
+ private static final String[] COLOR_INT_ANNOTATION_NAMES = {
+ "android.annotation.ColorInt",
+ "androidx.annotation.ColorInt"};
+
+ /**
+ * Set of android and androidx annotation qualified names for colors packed into {@code long}.
+ * @see android.annotation.ColorLong
+ */
+ private static final String[] COLOR_LONG_ANNOTATION_NAMES = {
+ "android.annotation.ColorLong",
+ "androidx.annotation.ColorLong"};
+
+ /**
+ * @param annotationQualifiedName The qualified name of the annotation to process
+ * @param processingEnv The processing environment from the parent processor
+ */
+ public InspectablePropertyProcessor(
+ String annotationQualifiedName,
+ ProcessingEnvironment processingEnv) {
+ mQualifiedName = annotationQualifiedName;
+ mProcessingEnv = processingEnv;
+ mAnnotationUtils = new AnnotationUtils(processingEnv);
+ }
+
+ @Override
+ public void process(Element element, InspectableClassModel model) {
+ try {
+ final AnnotationMirror annotation =
+ mAnnotationUtils.exactlyOneMirror(mQualifiedName, element);
+ final ExecutableElement getter = ensureGetter(element);
+ final Property property = buildProperty(getter, annotation);
+
+ model.getProperty(property.getName()).ifPresent(p -> {
+ throw new ProcessingException(
+ String.format(
+ "Property \"%s\" is already defined on #%s().",
+ p.getName(),
+ p.getGetter()),
+ getter,
+ annotation);
+ });
+
+ model.putProperty(property);
+ } catch (ProcessingException processingException) {
+ processingException.print(mProcessingEnv.getMessager());
+ }
+ }
+
+ /**
+ * Check that an element is shaped like a getter.
+ *
+ * @param element An element that hopefully represents a getter
+ * @throws ProcessingException if the element isn't a getter
+ * @return An {@link ExecutableElement} that represents a getter method.
+ */
+ private ExecutableElement ensureGetter(Element element) {
+ if (element.getKind() != ElementKind.METHOD) {
+ throw new ProcessingException(
+ String.format("Expected a method, got a %s", element.getKind()),
+ element);
+ }
+
+ final ExecutableElement method = (ExecutableElement) element;
+ final Set<Modifier> modifiers = method.getModifiers();
+
+ if (modifiers.contains(Modifier.PRIVATE)) {
+ throw new ProcessingException(
+ "Property getter methods must not be private.",
+ element);
+ }
+
+ if (modifiers.contains(Modifier.ABSTRACT)) {
+ throw new ProcessingException(
+ "Property getter methods must not be abstract.",
+ element);
+ }
+
+ if (modifiers.contains(Modifier.STATIC)) {
+ throw new ProcessingException(
+ "Property getter methods must not be static.",
+ element);
+ }
+
+ if (!method.getParameters().isEmpty()) {
+ throw new ProcessingException(
+ String.format(
+ "Expected a getter method to take no parameters, "
+ + "but got %d parameters.",
+ method.getParameters().size()),
+ element);
+ }
+
+ if (method.isVarArgs()) {
+ throw new ProcessingException(
+ "Expected a getter method to take no arguments, but got a var args method.",
+ element);
+ }
+
+ if (method.getReturnType() instanceof NoType) {
+ throw new ProcessingException(
+ "Expected a getter to have a return type, got void.",
+ element);
+ }
+
+ return method;
+ }
+
+ /**
+ * Build a {@link Property} from a getter and an inspectable property annotation.
+ *
+ * @param getter An element representing the getter to build from
+ * @param annotation A mirror of an inspectable property-shaped annotation
+ * @throws ProcessingException If the supplied data is invalid and a property cannot be modeled
+ * @return A property for the getter and annotation
+ */
+ private Property buildProperty(ExecutableElement getter, AnnotationMirror annotation) {
+ final String name = mAnnotationUtils
+ .typedValueByName("name", String.class, getter, annotation)
+ .orElseGet(() -> inferPropertyNameFromGetter(getter));
+
+ final Property property = new Property(
+ name,
+ getter.getSimpleName().toString(),
+ determinePropertyType(getter, annotation));
+
+ mAnnotationUtils
+ .typedValueByName("hasAttributeId", Boolean.class, getter, annotation)
+ .ifPresent(property::setAttributeIdInferrableFromR);
+
+ mAnnotationUtils
+ .typedValueByName("attributeId", Integer.class, getter, annotation)
+ .ifPresent(property::setAttributeId);
+
+ return property;
+ }
+
+ /**
+ * Determine the property type from the annotation, return type, or context clues.
+ *
+ * @param getter An element representing the getter to build from
+ * @param annotation A mirror of an inspectable property-shaped annotation
+ * @return The resolved property type
+ * @throws ProcessingException If the property type cannot be resolved
+ * @see android.view.inspector.InspectableProperty#valueType()
+ */
+ private Property.Type determinePropertyType(
+ ExecutableElement getter,
+ AnnotationMirror annotation) {
+
+ final String valueType = mAnnotationUtils
+ .untypedValueByName("valueType", getter, annotation)
+ .map(Object::toString)
+ .orElse("INFERRED");
+
+ final Property.Type returnType = convertReturnTypeToPropertyType(getter);
+
+ switch (valueType) {
+ case "INFERRED":
+ if (hasColorAnnotation(getter)) {
+ return Property.Type.COLOR;
+ } else {
+ return returnType;
+ }
+ case "NONE":
+ return returnType;
+ case "COLOR":
+ switch (returnType) {
+ case COLOR:
+ case INT:
+ case LONG:
+ return Property.Type.COLOR;
+ default:
+ throw new ProcessingException(
+ "Color must be a long, integer, or android.graphics.Color",
+ getter,
+ annotation);
+ }
+ case "GRAVITY":
+ if (returnType == Property.Type.INT) {
+ return Property.Type.GRAVITY;
+ } else {
+ throw new ProcessingException(
+ String.format("Gravity must be an integer, got %s", returnType),
+ getter,
+ annotation);
+ }
+ case "INT_ENUM":
+ case "INT_FLAG":
+ throw new ProcessingException("Not implemented", getter, annotation);
+ default:
+ throw new ProcessingException(
+ String.format("Unknown value type enumeration value: %s", valueType),
+ getter,
+ annotation);
+ }
+ }
+
+ /**
+ * Get a property type from the return type of a getter.
+ *
+ * @param getter The getter to extract the return type of
+ * @throws ProcessingException If the return type is not a primitive or an object
+ * @return The property type returned by the getter
+ */
+ private Property.Type convertReturnTypeToPropertyType(ExecutableElement getter) {
+ final TypeMirror returnType = getter.getReturnType();
+
+ switch (unboxType(returnType)) {
+ case BOOLEAN:
+ return Property.Type.BOOLEAN;
+ case BYTE:
+ return Property.Type.BYTE;
+ case CHAR:
+ return Property.Type.CHAR;
+ case DOUBLE:
+ return Property.Type.DOUBLE;
+ case FLOAT:
+ return Property.Type.FLOAT;
+ case INT:
+ return Property.Type.INT;
+ case LONG:
+ return Property.Type.LONG;
+ case SHORT:
+ return Property.Type.SHORT;
+ case DECLARED:
+ if (isColorType(returnType)) {
+ return Property.Type.COLOR;
+ } else {
+ return Property.Type.OBJECT;
+ }
+ default:
+ throw new ProcessingException(
+ String.format("Unsupported return type %s.", returnType),
+ getter);
+ }
+ }
+
+ /**
+ * Determine if a getter is annotated with color annotation matching its return type.
+ *
+ * Note that an {@code int} return value annotated with {@link android.annotation.ColorLong} is
+ * not considered to be annotated, nor is a {@code long} annotated with
+ * {@link android.annotation.ColorInt}.
+ *
+ * @param getter The getter to query
+ * @return True if the getter has a color annotation, false otherwise
+ *
+ */
+ private boolean hasColorAnnotation(ExecutableElement getter) {
+ switch (unboxType(getter.getReturnType())) {
+ case INT:
+ for (String name : COLOR_INT_ANNOTATION_NAMES) {
+ if (mAnnotationUtils.hasAnnotation(getter, name)) {
+ return true;
+ }
+ }
+ return false;
+ case LONG:
+ for (String name : COLOR_LONG_ANNOTATION_NAMES) {
+ if (mAnnotationUtils.hasAnnotation(getter, name)) {
+ return true;
+ }
+ }
+ return false;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Infer a property name from a getter method.
+ *
+ * If the method is prefixed with {@code get}, the prefix will be stripped, and the
+ * capitalization fixed. E.g.: {@code getSomeProperty} to {@code someProperty}.
+ *
+ * Additionally, if the method's return type is a boolean, an {@code is} prefix will also be
+ * stripped. E.g.: {@code isPropertyEnabled} to {@code propertyEnabled}.
+ *
+ * Failing that, this method will just return the full name of the getter.
+ *
+ * @param getter An element representing a getter
+ * @return A string property name
+ */
+ private String inferPropertyNameFromGetter(ExecutableElement getter) {
+ final String name = getter.getSimpleName().toString();
+
+ if (GETTER_GET_PREFIX.matcher(name).find()) {
+ return name.substring(3, 4).toLowerCase() + name.substring(4);
+ } else if (isBoolean(getter.getReturnType()) && GETTER_IS_PREFIX.matcher(name).find()) {
+ return name.substring(2, 3).toLowerCase() + name.substring(3);
+ } else {
+ return name;
+ }
+ }
+
+ /**
+ * Determine if a {@link TypeMirror} is a boxed or unboxed boolean.
+ *
+ * @param type The type mirror to check
+ * @return True if the type is a boolean
+ */
+ private boolean isBoolean(TypeMirror type) {
+ if (type.getKind() == TypeKind.DECLARED) {
+ return mProcessingEnv.getTypeUtils().unboxedType(type).getKind() == TypeKind.BOOLEAN;
+ } else {
+ return type.getKind() == TypeKind.BOOLEAN;
+ }
+ }
+
+ /**
+ * Unbox a type mirror if it represents a boxed type, otherwise pass it through.
+ *
+ * @param typeMirror The type mirror to unbox
+ * @return The same type mirror, or an unboxed primitive version
+ */
+ private TypeKind unboxType(TypeMirror typeMirror) {
+ final TypeKind typeKind = typeMirror.getKind();
+
+ if (typeKind.isPrimitive()) {
+ return typeKind;
+ } else if (typeKind == TypeKind.DECLARED) {
+ try {
+ return mProcessingEnv.getTypeUtils().unboxedType(typeMirror).getKind();
+ } catch (IllegalArgumentException e) {
+ return typeKind;
+ }
+ } else {
+ return typeKind;
+ }
+ }
+
+ /**
+ * Determine if a type mirror represents a subtype of {@link android.graphics.Color}.
+ *
+ * @param typeMirror The type mirror to test
+ * @return True if it represents a subclass of color, false otherwise
+ */
+ private boolean isColorType(TypeMirror typeMirror) {
+ final TypeElement colorType = mProcessingEnv
+ .getElementUtils()
+ .getTypeElement("android.graphics.Color");
+
+ if (colorType == null) {
+ return false;
+ } else {
+ return mProcessingEnv.getTypeUtils().isSubtype(typeMirror, colorType.asType());
+ }
+ }
+}
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
index 8b2cc847c0b3..dd4d8f54fb68 100644
--- 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
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright 2019 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.
@@ -51,12 +51,6 @@ public final class InspectionCompanionGenerator {
private static final ClassName R_CLASS_NAME = ClassName.get("android", "R");
/**
- * The class name of {@link android.content.res.ResourceId}.
- */
- private static final ClassName RESOURCE_ID_CLASS_NAME = ClassName.get(
- "android.content.res", "ResourceId");
-
- /**
* The class name of {@link android.view.inspector.InspectionCompanion}.
*/
private static final ClassName INSPECTION_COMPANION = ClassName.get(
@@ -91,11 +85,11 @@ public final class InspectionCompanionGenerator {
private static final String GENERATED_CLASS_SUFFIX = "$$InspectionCompanion";
/**
- * The null resource ID.
+ * The null resource ID, copied to avoid a host dependency on platform code.
*
* @see android.content.res.Resources#ID_NULL
*/
- private static final int NO_ID = 0;
+ private static final int ID_NULL = 0;
/**
* @param filer A filer to write the generated source to
@@ -289,8 +283,8 @@ public final class InspectionCompanionGenerator {
if (property.isAttributeIdInferrableFromR()) {
builder.add("$T.attr.$L", R_CLASS_NAME, property.getName());
} else {
- if (property.getAttributeId() == NO_ID) {
- builder.add("$T.ID_NULL", RESOURCE_ID_CLASS_NAME);
+ if (property.getAttributeId() == ID_NULL) {
+ builder.add("$L", ID_NULL);
} else {
builder.add("$L", String.format("0x%08x", property.getAttributeId()));
}
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
index 3ffcff8a87d3..6f522608a9b4 100644
--- 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
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright 2019 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.
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
index e531b67d9ea2..455f5b08e49e 100644
--- 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
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright 2019 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.
@@ -46,11 +46,14 @@ import javax.lang.model.element.TypeElement;
* @see android.view.inspector.InspectableProperty
*/
@SupportedAnnotationTypes({
- PlatformInspectableProcessor.NODE_NAME_QUALIFIED_NAME
+ PlatformInspectableProcessor.NODE_NAME_QUALIFIED_NAME,
+ PlatformInspectableProcessor.PROPERTY_QUALIFIED_NAME
})
public final class PlatformInspectableProcessor extends AbstractProcessor {
static final String NODE_NAME_QUALIFIED_NAME =
"android.view.inspector.InspectableNodeName";
+ static final String PROPERTY_QUALIFIED_NAME =
+ "android.view.inspector.InspectableProperty";
@Override
public SourceVersion getSupportedSourceVersion() {
@@ -68,6 +71,11 @@ public final class PlatformInspectableProcessor extends AbstractProcessor {
new InspectableNodeNameProcessor(NODE_NAME_QUALIFIED_NAME, processingEnv),
modelMap);
+ } else if (annotation.getQualifiedName().contentEquals(PROPERTY_QUALIFIED_NAME)) {
+ runModelProcessor(
+ roundEnv.getElementsAnnotatedWith(annotation),
+ new InspectablePropertyProcessor(PROPERTY_QUALIFIED_NAME, processingEnv),
+ modelMap);
} else {
fail("Unexpected annotation type", annotation);
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
index 6360e0a2de39..b4c6466b2b2f 100644
--- 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
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright 2019 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.
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
index fa4f71ffd0fa..79185cc80f04 100644
--- 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
@@ -1 +1 @@
-android.processor.inspector.view.PlatformInspectableProcessor
+android.processor.view.inspector.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
index f639719800f6..b0775dc77f45 100644
--- 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
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright 2019 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.
@@ -20,7 +20,7 @@ import android.processor.view.inspector.InspectableClassModel.Property;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
-import static junit.framework.TestCase.fail;
+import static junit.framework.Assert.fail;
import com.google.common.base.Charsets;
import com.google.common.io.Resources;
@@ -65,27 +65,28 @@ public class InspectionCompanionGeneratorTest {
@Test
public void testSimpleProperties() {
- addProperty("boolean", Property.Type.BOOLEAN, "getBoolean");
- addProperty("byte", Property.Type.BYTE, "getByte");
- addProperty("char", Property.Type.CHAR, "getChar");
- addProperty("double", Property.Type.DOUBLE, "getDouble");
- addProperty("float", Property.Type.FLOAT, "getFloat");
- addProperty("int", Property.Type.INT, "getInt");
- addProperty("long", Property.Type.LONG, "getLong");
- addProperty("short", Property.Type.SHORT, "getShort");
-
- addProperty("object", Property.Type.OBJECT, "getObject");
- addProperty("color", Property.Type.COLOR, "getColor");
- addProperty("gravity", Property.Type.GRAVITY, "getGravity");
+ addProperty("boolean", "getBoolean", Property.Type.BOOLEAN);
+ addProperty("byte", "getByte", Property.Type.BYTE);
+ addProperty("char", "getChar", Property.Type.CHAR);
+ addProperty("double", "getDouble", Property.Type.DOUBLE);
+ addProperty("float", "getFloat", Property.Type.FLOAT);
+ addProperty("int", "getInt", Property.Type.INT);
+ addProperty("long", "getLong", Property.Type.LONG);
+ addProperty("short", "getShort", Property.Type.SHORT);
+
+ addProperty("object", "getObject", Property.Type.OBJECT);
+ addProperty("color", "getColor", Property.Type.COLOR);
+ addProperty("gravity", "getGravity", Property.Type.GRAVITY);
assertGeneratedFileEquals("SimpleProperties");
}
@Test
public void testNoAttributeId() {
- final Property property = new Property("noAttributeProperty");
- property.setType(Property.Type.INT);
- property.setGetter("getNoAttributeProperty");
+ final Property property = new Property(
+ "noAttributeProperty",
+ "getNoAttributeProperty",
+ Property.Type.INT);
property.setAttributeIdInferrableFromR(false);
mModel.putProperty(property);
@@ -94,19 +95,18 @@ public class InspectionCompanionGeneratorTest {
@Test
public void testSuppliedAttributeId() {
- final Property property = new Property("suppliedAttributeProperty");
- property.setType(Property.Type.INT);
- property.setGetter("getSuppliedAttributeProperty");
+ final Property property = new Property(
+ "suppliedAttributeProperty",
+ "getSuppliedAttributeProperty",
+ Property.Type.INT);
property.setAttributeId(0xdecafbad);
mModel.putProperty(property);
assertGeneratedFileEquals("SuppliedAttributeId");
}
- private Property addProperty(String name, Property.Type type, String getter) {
- final Property property = new Property(name);
- property.setType(type);
- property.setGetter(getter);
+ private Property addProperty(String name, String getter, Property.Type type) {
+ final Property property = new Property(name, getter, type);
mModel.putProperty(property);
return property;
}
diff --git a/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/NoAttributeId.java.txt b/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/NoAttributeId.java.txt
index 277e84065fb7..23d0f7807aa5 100644
--- a/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/NoAttributeId.java.txt
+++ b/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/NoAttributeId.java.txt
@@ -1,6 +1,5 @@
package com.android.inspectable;
-import android.content.res.ResourceId;
import android.view.inspector.InspectionCompanion;
import android.view.inspector.PropertyMapper;
import android.view.inspector.PropertyReader;
@@ -25,7 +24,7 @@ public final class TestInspectable$$InspectionCompanion implements InspectionCom
@Override
public void mapProperties(PropertyMapper propertyMapper) {
- mNoAttributePropertyId = propertyMapper.mapInt("noAttributeProperty", ResourceId.ID_NULL);
+ mNoAttributePropertyId = propertyMapper.mapInt("noAttributeProperty", 0);
mPropertiesMapped = true;
}