Annotation processing for int enum and flag mapping
Bug: 117616612 Test: atest --host view-inspector-annotation-processor-test Change-Id: I791ffd8ce6bf6ec3ba408bb2a781fd91871b0ed6
This commit is contained in:
@@ -16,8 +16,12 @@
|
||||
|
||||
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;
|
||||
@@ -101,6 +105,83 @@ final class AnnotationUtils {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,13 +16,19 @@
|
||||
|
||||
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();
|
||||
@@ -294,6 +359,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.
|
||||
*
|
||||
@@ -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())) {
|
||||
@@ -352,6 +441,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.
|
||||
*
|
||||
|
||||
@@ -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;
|
||||
@@ -68,6 +70,18 @@ public final class InspectionCompanionGenerator {
|
||||
private static final ClassName PROPERTY_READER = ClassName.get(
|
||||
"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.
|
||||
*/
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -326,6 +341,56 @@ public final class InspectionCompanionGenerator {
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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())));
|
||||
|
||||
@@ -16,11 +16,13 @@
|
||||
|
||||
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);
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user