OSDN Git Service

Annotation processing for int enum and flag mapping
authorAshley Rose <ashleyrose@google.com>
Fri, 25 Jan 2019 20:41:29 +0000 (15:41 -0500)
committerAshley Rose <ashleyrose@google.com>
Mon, 28 Jan 2019 18:21:30 +0000 (18:21 +0000)
Bug: 117616612
Test: atest --host view-inspector-annotation-processor-test
Change-Id: I791ffd8ce6bf6ec3ba408bb2a781fd91871b0ed6

13 files changed:
tools/processors/view_inspector/src/java/android/processor/view/inspector/AnnotationUtils.java
tools/processors/view_inspector/src/java/android/processor/view/inspector/InspectableClassModel.java
tools/processors/view_inspector/src/java/android/processor/view/inspector/InspectablePropertyProcessor.java
tools/processors/view_inspector/src/java/android/processor/view/inspector/InspectionCompanionGenerator.java
tools/processors/view_inspector/src/java/android/processor/view/inspector/PlatformInspectableProcessor.java
tools/processors/view_inspector/test/java/android/processor/view/inspector/InspectionCompanionGeneratorTest.java
tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/IntEnum.java.txt [new file with mode: 0644]
tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/IntFlag.java.txt [new file with mode: 0644]
tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/NestedClass.java.txt
tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/NoAttributeId.java.txt
tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/NodeName.java.txt
tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/SimpleProperties.java.txt
tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/SuppliedAttributeId.java.txt

index fc4cd01..2690ee8 100644 (file)
 
 package android.processor.view.inspector;
 
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
 import java.util.Map;
 import java.util.Optional;
+import java.util.stream.Collectors;
 
 import javax.annotation.processing.ProcessingEnvironment;
 import javax.lang.model.element.AnnotationMirror;
@@ -102,6 +106,83 @@ final class AnnotationUtils {
     }
 
     /**
+     * Get a typed list of values for an annotation array property by name.
+     *
+     * The returned list will be empty if the value was left at the default.
+     *
+     * @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 A list containing the requested types
+     */
+    <T> List<T> typedArrayValuesByName(
+            String propertyName,
+            Class<T> valueClass,
+            Element element,
+            AnnotationMirror annotationMirror) {
+        return untypedArrayValuesByName(propertyName, element, annotationMirror)
+                .stream()
+                .map(annotationValue -> {
+                    final Object value = annotationValue.getValue();
+
+                    if (value == null) {
+                        throw new ProcessingException(
+                                "Unexpected null in array.",
+                                element,
+                                annotationMirror,
+                                annotationValue);
+                    }
+
+                    if (valueClass.isAssignableFrom(value.getClass())) {
+                        return valueClass.cast(value);
+                    } else {
+                        throw new ProcessingException(
+                                String.format(
+                                        "Expected array entry to have type %s, but got %s.",
+                                        valueClass.getCanonicalName(),
+                                        value.getClass().getCanonicalName()),
+                                element,
+                                annotationMirror,
+                                annotationValue);
+                    }
+                })
+                .collect(Collectors.toList());
+    }
+
+    /**
+     * Get a list of values for an annotation array property by name.
+     *
+     * @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 A list of annotation values, empty list if none found
+     */
+    List<AnnotationValue> untypedArrayValuesByName(
+            String propertyName,
+            Element element,
+            AnnotationMirror annotationMirror) {
+        return typedValueByName(propertyName, List.class, element, annotationMirror)
+                .map(untypedValues -> {
+                    List<AnnotationValue> typedValues = new ArrayList<>(untypedValues.size());
+
+                    for (Object untypedValue : untypedValues) {
+                        if (untypedValue instanceof AnnotationValue) {
+                            typedValues.add((AnnotationValue) untypedValue);
+                        } else {
+                            throw new ProcessingException(
+                                    "Unable to convert array entry to AnnotationValue",
+                                    element,
+                                    annotationMirror);
+                        }
+                    }
+
+                    return typedValues;
+                }).orElseGet(Collections::emptyList);
+    }
+
+    /**
      * 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
index f1ebb87..6f58893 100644 (file)
@@ -19,7 +19,9 @@ package android.processor.view.inspector;
 import com.squareup.javapoet.ClassName;
 
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
@@ -92,6 +94,8 @@ public final class InspectableClassModel {
         private final Type mType;
         private boolean mAttributeIdInferrableFromR = true;
         private int mAttributeId = 0;
+        private List<IntEnumEntry> mIntEnumEntries;
+        private List<IntFlagEntry> mIntFlagEntries;
 
         public Property(String name, String getter, Type type) {
             mName = Objects.requireNonNull(name, "Name must not be null");
@@ -133,6 +137,40 @@ public final class InspectableClassModel {
             return mType;
         }
 
+        /**
+         * Get the mapping for an {@code int} enumeration, if present.
+         *
+         * @return A list of mapping entries, empty if absent
+         */
+        public List<IntEnumEntry> getIntEnumEntries() {
+            if (mIntEnumEntries != null) {
+                return mIntEnumEntries;
+            } else {
+                return Collections.emptyList();
+            }
+        }
+
+        public void setIntEnumEntries(List<IntEnumEntry> intEnumEntries) {
+            mIntEnumEntries = intEnumEntries;
+        }
+
+        /**
+         * Get the mapping of {@code int} flags, if present.
+         *
+         * @return A list of mapping entries, empty if absent
+         */
+        public List<IntFlagEntry> getIntFlagEntries() {
+            if (mIntFlagEntries != null) {
+                return mIntFlagEntries;
+            } else {
+                return Collections.emptyList();
+            }
+        }
+
+        public void setIntFlagEntries(List<IntFlagEntry> intFlagEntries) {
+            mIntFlagEntries = intFlagEntries;
+        }
+
         public enum Type {
             /** Primitive or boxed {@code boolean} */
             BOOLEAN,
@@ -181,6 +219,7 @@ public final class InspectableClassModel {
              * An enumeration packed into an {@code int}.
              *
              * @see android.view.inspector.IntEnumMapping
+             * @see IntEnumEntry
              */
             INT_ENUM,
 
@@ -188,8 +227,74 @@ public final class InspectableClassModel {
              * Non-exclusive or partially-exclusive flags packed into an {@code int}.
              *
              * @see android.view.inspector.IntFlagMapping
+             * @see IntFlagEntry
              */
             INT_FLAG
         }
     }
+
+    /**
+     * Model one entry in a int enum mapping.
+     *
+     * @see android.view.inspector.IntEnumMapping
+     */
+    public static final class IntEnumEntry {
+        private final String mName;
+        private final int mValue;
+
+        public IntEnumEntry(String name, int value) {
+            mName = Objects.requireNonNull(name, "Name must not be null");
+            mValue = value;
+        }
+
+        public String getName() {
+            return mName;
+        }
+
+        public int getValue() {
+            return mValue;
+        }
+    }
+
+    /**
+     * Model one entry in an int flag mapping.
+     *
+     * @see android.view.inspector.IntFlagMapping
+     */
+    public static final class IntFlagEntry {
+        private final String mName;
+        private final int mTarget;
+        private final int mMask;
+
+        public IntFlagEntry(String name, int target, int mask) {
+            mName = Objects.requireNonNull(name, "Name must not be null");
+            mTarget = target;
+            mMask = mask;
+        }
+
+        public IntFlagEntry(String name, int target) {
+            this(name, target, target);
+        }
+
+        /**
+         * Determine if this entry has a bitmask.
+         *
+         * @return True if the bitmask and target are different, false otherwise
+         */
+        public boolean hasMask() {
+            return mTarget != mMask;
+        }
+
+        public String getName() {
+            return mName;
+        }
+
+        public int getTarget() {
+            return mTarget;
+        }
+
+        public int getMask() {
+            return mMask;
+        }
+    }
 }
index f666be7..42ae890 100644 (file)
 
 package android.processor.view.inspector;
 
+import android.processor.view.inspector.InspectableClassModel.IntEnumEntry;
+import android.processor.view.inspector.InspectableClassModel.IntFlagEntry;
 import android.processor.view.inspector.InspectableClassModel.Property;
 
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
 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.AnnotationValue;
 import javax.lang.model.element.Element;
 import javax.lang.model.element.ElementKind;
 import javax.lang.model.element.ExecutableElement;
@@ -63,6 +69,7 @@ public final class InspectablePropertyProcessor implements ModelProcessor {
 
     /**
      * 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 = {
@@ -71,7 +78,7 @@ public final class InspectablePropertyProcessor implements ModelProcessor {
 
     /**
      * @param annotationQualifiedName The qualified name of the annotation to process
-     * @param processingEnv The processing environment from the parent processor
+     * @param processingEnv           The processing environment from the parent processor
      */
     public InspectablePropertyProcessor(
             String annotationQualifiedName,
@@ -109,8 +116,8 @@ public final class InspectablePropertyProcessor implements ModelProcessor {
      * 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.
+     * @throws ProcessingException if the element isn't a getter
      */
     private ExecutableElement ensureGetter(Element element) {
         if (element.getKind() != ElementKind.METHOD) {
@@ -144,7 +151,7 @@ public final class InspectablePropertyProcessor implements ModelProcessor {
             throw new ProcessingException(
                     String.format(
                             "Expected a getter method to take no parameters, "
-                            + "but got %d parameters.",
+                                    + "but got %d parameters.",
                             method.getParameters().size()),
                     element);
         }
@@ -167,10 +174,10 @@ public final class InspectablePropertyProcessor implements ModelProcessor {
     /**
      * Build a {@link Property} from a getter and an inspectable property annotation.
      *
-     * @param getter An element representing the getter to build from
+     * @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
+     * @throws ProcessingException If the supplied data is invalid and a property cannot be modeled
      */
     private Property buildProperty(ExecutableElement getter, AnnotationMirror annotation) {
         final String name = mAnnotationUtils
@@ -190,16 +197,25 @@ public final class InspectablePropertyProcessor implements ModelProcessor {
                 .typedValueByName("attributeId", Integer.class, getter, annotation)
                 .ifPresent(property::setAttributeId);
 
+        switch (property.getType()) {
+            case INT_ENUM:
+                property.setIntEnumEntries(processEnumMapping(getter, annotation));
+                break;
+            case INT_FLAG:
+                property.setIntFlagEntries(processFlagMapping(getter, annotation));
+                break;
+        }
+
         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 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
+     * @throws ProcessingException If the property type cannot be resolved or is invalid
      * @see android.view.inspector.InspectableProperty#valueType()
      */
     private Property.Type determinePropertyType(
@@ -213,10 +229,62 @@ public final class InspectablePropertyProcessor implements ModelProcessor {
 
         final Property.Type returnType = convertReturnTypeToPropertyType(getter);
 
+        final boolean hasColor = hasColorAnnotation(getter);
+        final Optional<AnnotationValue> enumMapping =
+                mAnnotationUtils.valueByName("enumMapping", annotation);
+        final Optional<AnnotationValue> flagMapping =
+                mAnnotationUtils.valueByName("flagMapping", annotation);
+
+        if (returnType != Property.Type.INT) {
+            enumMapping.ifPresent(value -> {
+                throw new ProcessingException(
+                        String.format(
+                                "Can only use enumMapping on int types, got %s.",
+                                returnType.toString().toLowerCase()),
+                        getter,
+                        annotation,
+                        value);
+            });
+            flagMapping.ifPresent(value -> {
+                throw new ProcessingException(
+                        String.format(
+                                "Can only use flagMapping on int types, got %s.",
+                                returnType.toString().toLowerCase()),
+                        getter,
+                        annotation,
+                        value);
+            });
+        }
+
         switch (valueType) {
             case "INFERRED":
-                if (hasColorAnnotation(getter)) {
+                if (hasColor) {
+                    enumMapping.ifPresent(value -> {
+                        throw new ProcessingException(
+                                "Cannot use enumMapping on a color type.",
+                                getter,
+                                annotation,
+                                value);
+                    });
+                    flagMapping.ifPresent(value -> {
+                        throw new ProcessingException(
+                                "Cannot use flagMapping on a color type.",
+                                getter,
+                                annotation,
+                                value);
+                    });
                     return Property.Type.COLOR;
+                } else if (enumMapping.isPresent()) {
+                    flagMapping.ifPresent(value -> {
+                        throw new ProcessingException(
+                                "Cannot use flagMapping and enumMapping simultaneously.",
+                                getter,
+                                annotation,
+                                value);
+                    });
+                    return Property.Type.INT_ENUM;
+                } else if (flagMapping.isPresent()) {
+                    return Property.Type.INT_FLAG;
                 } else {
                     return returnType;
                 }
@@ -235,17 +303,14 @@ public final class InspectablePropertyProcessor implements ModelProcessor {
                                 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);
-                }
+                requirePackedIntToReturnInt("Gravity", returnType, getter, annotation);
+                return Property.Type.GRAVITY;
             case "INT_ENUM":
+                requirePackedIntToReturnInt("IntEnum", returnType, getter, annotation);
+                return Property.Type.INT_ENUM;
             case "INT_FLAG":
-                throw new ProcessingException("Not implemented", getter, annotation);
+                requirePackedIntToReturnInt("IntFlag", returnType, getter, annotation);
+                return Property.Type.INT_FLAG;
             default:
                 throw new ProcessingException(
                         String.format("Unknown value type enumeration value: %s", valueType),
@@ -258,8 +323,8 @@ public final class InspectablePropertyProcessor implements ModelProcessor {
      * 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
+     * @throws ProcessingException If the return type is not a primitive or an object
      */
     private Property.Type convertReturnTypeToPropertyType(ExecutableElement getter) {
         final TypeMirror returnType = getter.getReturnType();
@@ -295,6 +360,31 @@ public final class InspectablePropertyProcessor implements ModelProcessor {
     }
 
     /**
+     * Require that a value type packed into an integer be on a getter that returns an int.
+     *
+     * @param typeName   The name of the type to use in the exception
+     * @param returnType The return type of the getter to check
+     * @param getter     The getter, to use in the exception
+     * @param annotation The annotation, to use in the exception
+     * @throws ProcessingException If the return type is not an int
+     */
+    private static void requirePackedIntToReturnInt(
+            String typeName,
+            Property.Type returnType,
+            ExecutableElement getter,
+            AnnotationMirror annotation) {
+        if (returnType != Property.Type.INT) {
+            throw new ProcessingException(
+                    String.format(
+                            "%s can only be defined on a method that returns int, got %s.",
+                            typeName,
+                            returnType.toString().toLowerCase()),
+                    getter,
+                    annotation);
+        }
+    }
+
+    /**
      * 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
@@ -303,7 +393,6 @@ public final class InspectablePropertyProcessor implements ModelProcessor {
      *
      * @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())) {
@@ -353,6 +442,117 @@ public final class InspectablePropertyProcessor implements ModelProcessor {
     }
 
     /**
+     * Build a model of an {@code int} enumeration mapping from annotation values.
+     *
+     * This method only handles the one-to-one mapping of mirrors of
+     * {@link android.view.inspector.InspectableProperty.EnumMap} annotations into
+     * {@link IntEnumEntry} objects. Further validation should be handled elsewhere
+     *
+     * @see android.view.inspector.IntEnumMapping
+     * @see android.view.inspector.InspectableProperty#enumMapping()
+     * @param getter The getter of the property, used for exceptions
+     * @param annotation The {@link android.view.inspector.InspectableProperty} annotation to
+     *                   extract enum mapping values from.
+     * @return A list of int enum entries, in the order specified in source
+     * @throws ProcessingException if mapping doesn't exist or is invalid
+     */
+    private List<IntEnumEntry> processEnumMapping(
+            ExecutableElement getter,
+            AnnotationMirror annotation) {
+        List<AnnotationMirror> enumAnnotations = mAnnotationUtils.typedArrayValuesByName(
+                "enumMapping", AnnotationMirror.class, getter, annotation);
+        List<IntEnumEntry> enumEntries = new ArrayList<>(enumAnnotations.size());
+
+        if (enumAnnotations.isEmpty()) {
+            throw new ProcessingException(
+                    "Encountered an empty array for enumMapping", getter, annotation);
+        }
+
+        for (AnnotationMirror enumAnnotation : enumAnnotations) {
+            final String name = mAnnotationUtils.typedValueByName(
+                    "name", String.class, getter, enumAnnotation)
+                    .orElseThrow(() -> {
+                        throw new ProcessingException(
+                                "Name is required for @EnumMap",
+                                getter,
+                                enumAnnotation);
+                    });
+
+            final int value = mAnnotationUtils.typedValueByName(
+                    "value", Integer.class, getter, enumAnnotation)
+                    .orElseThrow(() -> {
+                        throw new ProcessingException(
+                                "Value is required for @EnumMap",
+                                getter,
+                                enumAnnotation);
+                    });
+
+            enumEntries.add(new IntEnumEntry(name, value));
+        }
+
+        return enumEntries;
+    }
+
+    /**
+     * Build a model of an {@code int} flag mapping from annotation values.
+     *
+     * This method only handles the one-to-one mapping of mirrors of
+     * {@link android.view.inspector.InspectableProperty.FlagMap} annotations into
+     * {@link IntFlagEntry} objects. Further validation should be handled elsewhere
+     *
+     * @see android.view.inspector.IntFlagMapping
+     * @see android.view.inspector.InspectableProperty#flagMapping()
+     * @param getter The getter of the property, used for exceptions
+     * @param annotation The {@link android.view.inspector.InspectableProperty} annotation to
+     *                   extract flag mapping values from.
+     * @return A list of int flags entries, in the order specified in source
+     * @throws ProcessingException if mapping doesn't exist or is invalid
+     */
+    private List<IntFlagEntry> processFlagMapping(
+            ExecutableElement getter,
+            AnnotationMirror annotation) {
+        List<AnnotationMirror> flagAnnotations = mAnnotationUtils.typedArrayValuesByName(
+                "flagMapping", AnnotationMirror.class, getter, annotation);
+        List<IntFlagEntry> flagEntries = new ArrayList<>(flagAnnotations.size());
+
+        if (flagAnnotations.isEmpty()) {
+            throw new ProcessingException(
+                    "Encountered an empty array for flagMapping", getter, annotation);
+        }
+
+        for (AnnotationMirror flagAnnotation : flagAnnotations) {
+            final String name = mAnnotationUtils.typedValueByName(
+                    "name", String.class, getter, flagAnnotation)
+                    .orElseThrow(() -> {
+                        throw new ProcessingException(
+                                "Name is required for @FlagMap",
+                                getter,
+                                flagAnnotation);
+                    });
+
+            final int target = mAnnotationUtils.typedValueByName(
+                    "target", Integer.class, getter, flagAnnotation)
+                    .orElseThrow(() -> {
+                        throw new ProcessingException(
+                                "Target is required for @FlagMap",
+                                getter,
+                                flagAnnotation);
+                    });
+
+            final Optional<Integer> mask = mAnnotationUtils.typedValueByName(
+                    "mask", Integer.class, getter, flagAnnotation);
+
+            if (mask.isPresent()) {
+                flagEntries.add(new IntFlagEntry(name, target, mask.get()));
+            } else {
+                flagEntries.add(new IntFlagEntry(name, target));
+            }
+        }
+
+        return flagEntries;
+    }
+
+    /**
      * Determine if a {@link TypeMirror} is a boxed or unboxed boolean.
      *
      * @param type The type mirror to check
index dd4d8f5..7b04645 100644 (file)
@@ -16,6 +16,8 @@
 
 package android.processor.view.inspector;
 
+import android.processor.view.inspector.InspectableClassModel.IntEnumEntry;
+import android.processor.view.inspector.InspectableClassModel.IntFlagEntry;
 import android.processor.view.inspector.InspectableClassModel.Property;
 
 import com.squareup.javapoet.ClassName;
@@ -69,6 +71,18 @@ public final class InspectionCompanionGenerator {
             "android.view.inspector", "PropertyReader");
 
     /**
+     * The class name of {@link android.view.inspector.IntEnumMapping}.
+     */
+    private static final ClassName INT_ENUM_MAPPING = ClassName.get(
+            "android.view.inspector", "IntEnumMapping");
+
+    /**
+     * The class name of {@link android.view.inspector.IntFlagMapping}.
+     */
+    private static final ClassName INT_FLAG_MAPPING = ClassName.get(
+            "android.view.inspector", "IntFlagMapping");
+
+    /**
      * The {@code mPropertiesMapped} field.
      */
     private static final FieldSpec M_PROPERTIES_MAPPED = FieldSpec
@@ -248,13 +262,13 @@ public final class InspectionCompanionGenerator {
         final MethodSpec.Builder builder =  MethodSpec.methodBuilder("readProperties")
                 .addAnnotation(Override.class)
                 .addModifiers(Modifier.PUBLIC)
-                .addParameter(model.getClassName(), "inspectable")
+                .addParameter(model.getClassName(), "node")
                 .addParameter(PROPERTY_READER, "propertyReader")
                 .addCode(generatePropertyMapInitializationCheck());
 
         for (PropertyIdField propertyIdField : propertyIdFields) {
             builder.addStatement(
-                    "propertyReader.read$L($N, inspectable.$L())",
+                    "propertyReader.read$L($N, node.$L())",
                     methodSuffixForPropertyType(propertyIdField.mProperty.getType()),
                     propertyIdField.mFieldSpec,
                     propertyIdField.mProperty.getGetter());
@@ -286,21 +300,22 @@ public final class InspectionCompanionGenerator {
             if (property.getAttributeId() == ID_NULL) {
                 builder.add("$L", ID_NULL);
             } else {
-                builder.add("$L", String.format("0x%08x", property.getAttributeId()));
+                builder.add("$L", hexLiteral(property.getAttributeId()));
             }
         }
 
         switch (property.getType()) {
             case INT_ENUM:
-                throw new RuntimeException("IntEnumMapping generation not implemented");
+                builder.add(",$W");
+                builder.add(generateIntEnumMappingBuilder(property.getIntEnumEntries()));
+                break;
             case INT_FLAG:
-                throw new RuntimeException("IntFlagMapping generation not implemented");
-            default:
-                builder.add(")");
+                builder.add(",$W");
+                builder.add(generateIntFlagMappingBuilder(property.getIntFlagEntries()));
                 break;
         }
 
-        return builder.build();
+        return builder.add(")").build();
     }
 
     /**
@@ -327,6 +342,56 @@ public final class InspectionCompanionGenerator {
     }
 
     /**
+     * Generate an invocation of {@link android.view.inspector.IntEnumMapping.Builder}.
+     *
+     * <pre>
+     *      new IntEnumMapping.Builder()
+     *          .addValue("ONE", 1)
+     *          .build()
+     * </pre>
+     *
+     * @return A codeblock containing the an int enum mapping builder
+     */
+    private CodeBlock generateIntEnumMappingBuilder(List<IntEnumEntry> intEnumEntries) {
+        final ArrayList<IntEnumEntry> sortedEntries = new ArrayList<>(intEnumEntries);
+        sortedEntries.sort(Comparator.comparing(IntEnumEntry::getValue));
+
+        final CodeBlock.Builder builder = CodeBlock.builder()
+                .add("new $T()$>", INT_ENUM_MAPPING.nestedClass("Builder"));
+
+        for (IntEnumEntry entry : sortedEntries) {
+            builder.add("\n.addValue($S, $L)", entry.getName(), entry.getValue());
+        }
+
+        return builder.add("\n.build()$<").build();
+    }
+
+    private CodeBlock generateIntFlagMappingBuilder(List<IntFlagEntry> intFlagEntries) {
+        final ArrayList<IntFlagEntry> sortedEntries = new ArrayList<>(intFlagEntries);
+        sortedEntries.sort(Comparator.comparing(IntFlagEntry::getName));
+
+        final CodeBlock.Builder builder = CodeBlock.builder()
+                .add("new $T()$>", INT_FLAG_MAPPING.nestedClass("Builder"));
+
+        for (IntFlagEntry entry : sortedEntries) {
+            if (entry.hasMask()) {
+                builder.add(
+                        "\n.addFlag($S, $L, $L)",
+                        entry.getName(),
+                        hexLiteral(entry.getTarget()),
+                        hexLiteral(entry.getMask()));
+            } else {
+                builder.add(
+                        "\n.addFlag($S, $L)",
+                        entry.getName(),
+                        hexLiteral(entry.getTarget()));
+            }
+        }
+
+        return builder.add("\n.build()$<").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
@@ -385,6 +450,10 @@ public final class InspectionCompanionGenerator {
         }
     }
 
+    private static String hexLiteral(int value) {
+        return String.format("0x%08x", value);
+    }
+
     /**
      * Value class that holds a {@link Property} and a {@link FieldSpec} for that property.
      */
index 455f5b0..01d9430 100644 (file)
@@ -32,6 +32,7 @@ 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.Modifier;
 import javax.lang.model.element.TypeElement;
 
 
@@ -118,6 +119,12 @@ public final class PlatformInspectableProcessor extends AbstractProcessor {
                 break;
             }
 
+            final Set<Modifier> classModifiers = classElement.get().getModifiers();
+
+            if (classModifiers.contains(Modifier.PRIVATE)) {
+                fail("Enclosing class cannot be private", element);
+            }
+
             final InspectableClassModel model = modelMap.computeIfAbsent(
                     classElement.get().getQualifiedName().toString(),
                     k -> new InspectableClassModel(ClassName.get(classElement.get())));
index b0775dc..f6d8bb0 100644 (file)
 
 package android.processor.view.inspector;
 
-import android.processor.view.inspector.InspectableClassModel.Property;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
 
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertNotNull;
-import static junit.framework.Assert.fail;
+import android.processor.view.inspector.InspectableClassModel.IntEnumEntry;
+import android.processor.view.inspector.InspectableClassModel.IntFlagEntry;
+import android.processor.view.inspector.InspectableClassModel.Property;
 
 import com.google.common.base.Charsets;
 import com.google.common.io.Resources;
@@ -31,6 +33,7 @@ import org.junit.Test;
 
 import java.io.IOException;
 import java.net.URL;
+import java.util.Arrays;
 import java.util.Optional;
 
 /**
@@ -40,7 +43,7 @@ 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");
+            ClassName.get("com.android.node", "TestNode");
     private InspectableClassModel mModel;
     private InspectionCompanionGenerator mGenerator;
 
@@ -59,7 +62,7 @@ public class InspectionCompanionGeneratorTest {
     @Test
     public void testNestedClass() {
         mModel = new InspectableClassModel(
-                ClassName.get("com.android.inspectable", "Outer", "Inner"));
+                ClassName.get("com.android.node", "Outer", "Inner"));
         assertGeneratedFileEquals("NestedClass");
     }
 
@@ -105,6 +108,42 @@ public class InspectionCompanionGeneratorTest {
         assertGeneratedFileEquals("SuppliedAttributeId");
     }
 
+    @Test
+    public void testIntEnum() {
+        final Property property = new Property(
+                "intEnumProperty",
+                "getIntEnumProperty",
+                Property.Type.INT_ENUM);
+
+        property.setIntEnumEntries(Arrays.asList(
+                new IntEnumEntry("THREE", 3),
+                new IntEnumEntry("TWO", 2),
+                new IntEnumEntry("ONE", 1)));
+
+        mModel.putProperty(property);
+
+        assertGeneratedFileEquals("IntEnum");
+    }
+
+    @Test
+    public void testIntFlag() {
+        final Property property = new Property(
+                "intFlag",
+                "getIntFlag",
+                Property.Type.INT_FLAG);
+
+        property.setAttributeIdInferrableFromR(false);
+        property.setIntFlagEntries(Arrays.asList(
+                new IntFlagEntry("TURBO", 0x1, 0x3),
+                new IntFlagEntry("OVERDRIVE", 0x2, 0x3),
+                new IntFlagEntry("WARP", 0x4)
+        ));
+
+        mModel.putProperty(property);
+
+        assertGeneratedFileEquals("IntFlag");
+    }
+
     private Property addProperty(String name, String getter, Property.Type type) {
         final Property property = new Property(name, getter, type);
         mModel.putProperty(property);
diff --git a/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/IntEnum.java.txt b/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/IntEnum.java.txt
new file mode 100644 (file)
index 0000000..764aa8b
--- /dev/null
@@ -0,0 +1,45 @@
+package com.android.node;
+
+import android.R;
+import android.view.inspector.InspectionCompanion;
+import android.view.inspector.IntEnumMapping;
+import android.view.inspector.PropertyMapper;
+import android.view.inspector.PropertyReader;
+import java.lang.Override;
+
+/**
+ * Inspection companion for {@link TestNode}.
+ *
+ * Generated by {@link android.processor.view.inspector.InspectionCompanionGenerator}
+ * on behalf of {@link android.processor.view.inspector.InspectionCompanionGeneratorTest}.
+ */
+public final class TestNode$$InspectionCompanion implements InspectionCompanion<TestNode> {
+    /**
+     * Set by {@link #mapProperties(PropertyMapper)} once properties have been mapped.
+     */
+    private boolean mPropertiesMapped = false;
+
+    /**
+     * Property ID of {@code intEnumProperty}.
+     */
+    private int mIntEnumPropertyId;
+
+    @Override
+    public void mapProperties(PropertyMapper propertyMapper) {
+        mIntEnumPropertyId = propertyMapper.mapIntEnum("intEnumProperty", R.attr.intEnumProperty,
+                new IntEnumMapping.Builder()
+                    .addValue("ONE", 1)
+                    .addValue("TWO", 2)
+                    .addValue("THREE", 3)
+                    .build());
+        mPropertiesMapped = true;
+    }
+
+    @Override
+    public void readProperties(TestNode node, PropertyReader propertyReader) {
+        if (!mPropertiesMapped) {
+            throw new InspectionCompanion.UninitializedPropertyMapException();
+        }
+        propertyReader.readIntEnum(mIntEnumPropertyId, node.getIntEnumProperty());
+    }
+}
diff --git a/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/IntFlag.java.txt b/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/IntFlag.java.txt
new file mode 100644 (file)
index 0000000..75f2813
--- /dev/null
@@ -0,0 +1,43 @@
+package com.android.node;
+
+import android.view.inspector.InspectionCompanion;
+import android.view.inspector.IntFlagMapping;
+import android.view.inspector.PropertyMapper;
+import android.view.inspector.PropertyReader;
+import java.lang.Override;
+
+/**
+ * Inspection companion for {@link TestNode}.
+ *
+ * Generated by {@link android.processor.view.inspector.InspectionCompanionGenerator}
+ * on behalf of {@link android.processor.view.inspector.InspectionCompanionGeneratorTest}.
+ */
+public final class TestNode$$InspectionCompanion implements InspectionCompanion<TestNode> {
+    /**
+     * Set by {@link #mapProperties(PropertyMapper)} once properties have been mapped.
+     */
+    private boolean mPropertiesMapped = false;
+
+    /**
+     * Property ID of {@code intFlag}.
+     */
+    private int mIntFlagId;
+
+    @Override
+    public void mapProperties(PropertyMapper propertyMapper) {
+        mIntFlagId = propertyMapper.mapIntFlag("intFlag", 0, new IntFlagMapping.Builder()
+                    .addFlag("OVERDRIVE", 0x00000002, 0x00000003)
+                    .addFlag("TURBO", 0x00000001, 0x00000003)
+                    .addFlag("WARP", 0x00000004)
+                    .build());
+        mPropertiesMapped = true;
+    }
+
+    @Override
+    public void readProperties(TestNode node, PropertyReader propertyReader) {
+        if (!mPropertiesMapped) {
+            throw new InspectionCompanion.UninitializedPropertyMapException();
+        }
+        propertyReader.readIntFlag(mIntFlagId, node.getIntFlag());
+    }
+}
index 2fc242c..0cac462 100644 (file)
@@ -1,4 +1,4 @@
-package com.android.inspectable;
+package com.android.node;
 
 import android.view.inspector.InspectionCompanion;
 import android.view.inspector.PropertyMapper;
@@ -23,7 +23,7 @@ public final class Outer$Inner$$InspectionCompanion implements InspectionCompani
     }
 
     @Override
-    public void readProperties(Outer.Inner inspectable, PropertyReader propertyReader) {
+    public void readProperties(Outer.Inner node, PropertyReader propertyReader) {
         if (!mPropertiesMapped) {
             throw new InspectionCompanion.UninitializedPropertyMapException();
         }
index 23d0f78..ce0f867 100644 (file)
@@ -1,4 +1,4 @@
-package com.android.inspectable;
+package com.android.node;
 
 import android.view.inspector.InspectionCompanion;
 import android.view.inspector.PropertyMapper;
@@ -6,12 +6,12 @@ import android.view.inspector.PropertyReader;
 import java.lang.Override;
 
 /**
- * Inspection companion for {@link TestInspectable}.
+ * Inspection companion for {@link TestNode}.
  *
  * 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> {
+public final class TestNode$$InspectionCompanion implements InspectionCompanion<TestNode> {
     /**
      * Set by {@link #mapProperties(PropertyMapper)} once properties have been mapped.
      */
@@ -29,10 +29,10 @@ public final class TestInspectable$$InspectionCompanion implements InspectionCom
     }
 
     @Override
-    public void readProperties(TestInspectable inspectable, PropertyReader propertyReader) {
+    public void readProperties(TestNode node, PropertyReader propertyReader) {
         if (!mPropertiesMapped) {
             throw new InspectionCompanion.UninitializedPropertyMapException();
         }
-        propertyReader.readInt(mNoAttributePropertyId, inspectable.getNoAttributeProperty());
+        propertyReader.readInt(mNoAttributePropertyId, node.getNoAttributeProperty());
     }
 }
index 1142548..f7357fe 100644 (file)
@@ -1,4 +1,4 @@
-package com.android.inspectable;
+package com.android.node;
 
 import android.view.inspector.InspectionCompanion;
 import android.view.inspector.PropertyMapper;
@@ -7,12 +7,12 @@ import java.lang.Override;
 import java.lang.String;
 
 /**
- * Inspection companion for {@link TestInspectable}.
+ * Inspection companion for {@link TestNode}.
  *
  * 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> {
+public final class TestNode$$InspectionCompanion implements InspectionCompanion<TestNode> {
     /**
      * Set by {@link #mapProperties(PropertyMapper)} once properties have been mapped.
      */
@@ -24,7 +24,7 @@ public final class TestInspectable$$InspectionCompanion implements InspectionCom
     }
 
     @Override
-    public void readProperties(TestInspectable inspectable, PropertyReader propertyReader) {
+    public void readProperties(TestNode node, PropertyReader propertyReader) {
         if (!mPropertiesMapped) {
             throw new InspectionCompanion.UninitializedPropertyMapException();
         }
index 57eb080..dfc1bce 100644 (file)
@@ -1,4 +1,4 @@
-package com.android.inspectable;
+package com.android.node;
 
 import android.R;
 import android.view.inspector.InspectionCompanion;
@@ -7,12 +7,12 @@ import android.view.inspector.PropertyReader;
 import java.lang.Override;
 
 /**
- * Inspection companion for {@link TestInspectable}.
+ * Inspection companion for {@link TestNode}.
  *
  * 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> {
+public final class TestNode$$InspectionCompanion implements InspectionCompanion<TestNode> {
     /**
      * Set by {@link #mapProperties(PropertyMapper)} once properties have been mapped.
      */
@@ -90,20 +90,20 @@ public final class TestInspectable$$InspectionCompanion implements InspectionCom
     }
 
     @Override
-    public void readProperties(TestInspectable inspectable, PropertyReader propertyReader) {
+    public void readProperties(TestNode node, PropertyReader propertyReader) {
         if (!mPropertiesMapped) {
             throw new InspectionCompanion.UninitializedPropertyMapException();
         }
-        propertyReader.readBoolean(mBooleanId, inspectable.getBoolean());
-        propertyReader.readByte(mByteId, inspectable.getByte());
-        propertyReader.readChar(mCharId, inspectable.getChar());
-        propertyReader.readColor(mColorId, inspectable.getColor());
-        propertyReader.readDouble(mDoubleId, inspectable.getDouble());
-        propertyReader.readFloat(mFloatId, inspectable.getFloat());
-        propertyReader.readGravity(mGravityId, inspectable.getGravity());
-        propertyReader.readInt(mIntId, inspectable.getInt());
-        propertyReader.readLong(mLongId, inspectable.getLong());
-        propertyReader.readObject(mObjectId, inspectable.getObject());
-        propertyReader.readShort(mShortId, inspectable.getShort());
+        propertyReader.readBoolean(mBooleanId, node.getBoolean());
+        propertyReader.readByte(mByteId, node.getByte());
+        propertyReader.readChar(mCharId, node.getChar());
+        propertyReader.readColor(mColorId, node.getColor());
+        propertyReader.readDouble(mDoubleId, node.getDouble());
+        propertyReader.readFloat(mFloatId, node.getFloat());
+        propertyReader.readGravity(mGravityId, node.getGravity());
+        propertyReader.readInt(mIntId, node.getInt());
+        propertyReader.readLong(mLongId, node.getLong());
+        propertyReader.readObject(mObjectId, node.getObject());
+        propertyReader.readShort(mShortId, node.getShort());
     }
 }
index 6b6ce21..d72cdd5 100644 (file)
@@ -1,4 +1,4 @@
-package com.android.inspectable;
+package com.android.node;
 
 import android.view.inspector.InspectionCompanion;
 import android.view.inspector.PropertyMapper;
@@ -6,12 +6,12 @@ import android.view.inspector.PropertyReader;
 import java.lang.Override;
 
 /**
- * Inspection companion for {@link TestInspectable}.
+ * Inspection companion for {@link TestNode}.
  *
  * 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> {
+public final class TestNode$$InspectionCompanion implements InspectionCompanion<TestNode> {
     /**
      * Set by {@link #mapProperties(PropertyMapper)} once properties have been mapped.
      */
@@ -30,10 +30,10 @@ public final class TestInspectable$$InspectionCompanion implements InspectionCom
     }
 
     @Override
-    public void readProperties(TestInspectable inspectable, PropertyReader propertyReader) {
+    public void readProperties(TestNode node, PropertyReader propertyReader) {
         if (!mPropertiesMapped) {
             throw new InspectionCompanion.UninitializedPropertyMapException();
         }
-        propertyReader.readInt(mSuppliedAttributePropertyId, inspectable.getSuppliedAttributeProperty());
+        propertyReader.readInt(mSuppliedAttributePropertyId, node.getSuppliedAttributeProperty());
     }
 }