Merge "Add InspectionHelper and related interfaces"
This commit is contained in:
46
core/java/android/view/inspector/ChildTraverser.java
Normal file
46
core/java/android/view/inspector/ChildTraverser.java
Normal file
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.view.inspector;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
|
||||
/**
|
||||
* Interface for visiting all the child nodes of an inspectable object.
|
||||
*
|
||||
* Inspectable objects may return a collection of children as an array, an {@link Iterable} or an
|
||||
* {@link java.util.Iterator}. This provides a unified API for traversing across all the children
|
||||
* of an inspectable node.
|
||||
*
|
||||
* This interface is consumed by {@link InspectionHelper#traverseChildren(Object, ChildTraverser)}
|
||||
* and may be implemented as a lambda.
|
||||
*
|
||||
* @see InspectionHelper#traverseChildren(Object, ChildTraverser)
|
||||
* @hide
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface ChildTraverser {
|
||||
/**
|
||||
* Visit one child object of a parent inspectable object.
|
||||
*
|
||||
* The iteration interface will filter null values out before passing them to this method, but
|
||||
* some child objects may not be inspectable. It is up to the implementor to determine their
|
||||
* inspectablity and what to do with them.
|
||||
*
|
||||
* @param child A child object, guaranteed not to be null.
|
||||
*/
|
||||
void traverseChild(@NonNull Object child);
|
||||
}
|
||||
145
core/java/android/view/inspector/InspectionHelper.java
Normal file
145
core/java/android/view/inspector/InspectionHelper.java
Normal file
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* Copyright 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.view.inspector;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* An interface for companion objects used to inspect views.
|
||||
*
|
||||
* Inspection helpers only need to handle the properties, name and traversal of the specific class
|
||||
* they are defined for, not anything from a parent class. At runtime, the inspector instantiates
|
||||
* one instance of each inspection helper, and handles visiting them in the correct inheritance
|
||||
* order for each type it inspects.
|
||||
*
|
||||
* Properties are read from the top of the type tree to the bottom, so that classes that override
|
||||
* a property in their parent class can overwrite it in the reader. In general, properties will
|
||||
* cleanly inherit through their getters, and the inspector runtime will read the properties of a
|
||||
* parent class via the parent's inspection helper, and the child helper will only read properties
|
||||
* added or changed since the parent was defined.
|
||||
*
|
||||
* Only one child traversal is considered for each class. If a descendant class defines a
|
||||
* different child traversal than its parent, only the bottom traversal is used. If a class does
|
||||
* not define its own child traversal, but one of its ancestors does, the bottom-most ancestor's
|
||||
* traversal will be used.
|
||||
*
|
||||
* @param <T> The type of inspectable this helper operates on
|
||||
* @hide
|
||||
*/
|
||||
public interface InspectionHelper<T> {
|
||||
/**
|
||||
* Map the string names of the properties this helper knows about to integer IDs.
|
||||
*
|
||||
* Each helper is responsible for storing the integer IDs of all its properties. This is the
|
||||
* only method that is allowed to modify the stored IDs.
|
||||
*
|
||||
* Calling {@link #readProperties(T, PropertyReader)} before calling this results in
|
||||
* undefined behavior.
|
||||
*
|
||||
* @param propertyMapper A {@link PropertyMapper} or lambda which maps string names to IDs.
|
||||
*/
|
||||
void mapProperties(@NonNull PropertyMapper propertyMapper);
|
||||
|
||||
/**
|
||||
* Read the values of an instance of this helper's type into a {@link PropertyReader}.
|
||||
*
|
||||
* This method needs to return the property IDs stored by
|
||||
* {@link #mapProperties(PropertyMapper)}. Implementations should track if their properties
|
||||
* have been mapped and throw a {@link UninitializedPropertyMapException} if this method is
|
||||
* called before {mapProperties}.
|
||||
*
|
||||
* @param inspectable A object of type {@link T} to read the properties of.
|
||||
* @param propertyReader An object which receives the property IDs and values.
|
||||
*/
|
||||
void readProperties(@NonNull T inspectable, @NonNull PropertyReader propertyReader);
|
||||
|
||||
/**
|
||||
* Query if this inspectable type can potentially have child nodes.
|
||||
*
|
||||
* E.g.: any descendant of {@link android.view.ViewGroup} can have child nodes, but a leaf
|
||||
* view like {@link android.widget.ImageView} may not.
|
||||
*
|
||||
* The default implementation always returns false. If an implementing class overrides this, it
|
||||
* should also define {@link #traverseChildren(T, ChildTraverser)}.
|
||||
*
|
||||
* @return True if this inspectable type can potentially have child nodes, false otherwise.
|
||||
*/
|
||||
default boolean hasChildTraversal() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Traverse the child nodes of an instance of this helper's type into a {@link ChildTraverser}.
|
||||
*
|
||||
* This provides the ability to traverse over a variety of collection APIs (e.g.: arrays,
|
||||
* {@link Iterable}, or {@link java.util.Iterator}) in a uniform fashion. The traversal must be
|
||||
* in the order defined by this helper's type. If the getter returns null, the helper must
|
||||
* treat it as an empty collection.
|
||||
*
|
||||
* The default implementation throws a {@link NoChildTraversalException}. If
|
||||
* {@link #hasChildTraversal()} returns is overriden to return true, it is expected that the
|
||||
* implementing class will also override this method and provide a traversal.
|
||||
*
|
||||
* @param inspectable An object of type {@link T} to traverse the child nodes of.
|
||||
* @param childTraverser A {@link ChildTraverser} or lamba to receive the children in order.
|
||||
* @throws NoChildTraversalException If there is no defined child traversal
|
||||
*/
|
||||
default void traverseChildren(
|
||||
@NonNull T inspectable,
|
||||
@SuppressWarnings("unused") @NonNull ChildTraverser childTraverser) {
|
||||
throw new NoChildTraversalException(inspectable.getClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an optional name to display to developers for inspection nodes of this helper's type.
|
||||
*
|
||||
* The default implementation returns null, which will cause the runtime to use the class's
|
||||
* simple name as defined by {@link Class#getSimpleName()} as the node name.
|
||||
*
|
||||
* If the type of this helper is inflated from XML, this method should be overridden to return
|
||||
* the string used as the tag name for this type in XML.
|
||||
*
|
||||
* @return A string to use as the node name, or null to use the simple class name fallback.
|
||||
*/
|
||||
@Nullable
|
||||
default String getNodeName() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Thrown by {@link #readProperties(Object, PropertyReader)} if called before
|
||||
* {@link #mapProperties(PropertyMapper)}.
|
||||
*/
|
||||
class UninitializedPropertyMapException extends RuntimeException {
|
||||
public UninitializedPropertyMapException() {
|
||||
super("Unable to read properties of an inspectable before mapping their IDs.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Thrown by {@link #traverseChildren(Object, ChildTraverser)} if no child traversal exists.
|
||||
*/
|
||||
class NoChildTraversalException extends RuntimeException {
|
||||
public NoChildTraversalException(Class cls) {
|
||||
super(String.format(
|
||||
"Class %s does not have a defined child traversal. Cannot traverse children.",
|
||||
cls.getCanonicalName()
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
3
core/java/android/view/inspector/OWNERS
Normal file
3
core/java/android/view/inspector/OWNERS
Normal file
@@ -0,0 +1,3 @@
|
||||
alanv@google.com
|
||||
ashleyrose@google.com
|
||||
aurimas@google.com
|
||||
130
core/java/android/view/inspector/PropertyMapper.java
Normal file
130
core/java/android/view/inspector/PropertyMapper.java
Normal file
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.view.inspector;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
|
||||
/**
|
||||
* An interface for mapping the string names of inspectable properties to integer identifiers.
|
||||
*
|
||||
* This interface is consumed by {@link InspectionHelper#mapProperties(PropertyMapper)}.
|
||||
*
|
||||
* Mapping properties to IDs enables quick comparisons against shadow copies of inspectable
|
||||
* objects without performing a large number of string comparisons.
|
||||
*
|
||||
* @see InspectionHelper#mapProperties(PropertyMapper)
|
||||
* @hide
|
||||
*/
|
||||
public interface PropertyMapper {
|
||||
/**
|
||||
* Map a string name to an integer ID for a primitive boolean property.
|
||||
*
|
||||
* @param name The name of the property
|
||||
* @return An integer ID for the property
|
||||
* @throws PropertyConflictException If the property name is already mapped as another type.
|
||||
*/
|
||||
int mapBoolean(@NonNull String name);
|
||||
|
||||
/**
|
||||
* Map a string name to an integer ID for a primitive byte property.
|
||||
*
|
||||
* @param name The name of the property
|
||||
* @return An integer ID for the property
|
||||
* @throws PropertyConflictException If the property name is already mapped as another type.
|
||||
*/
|
||||
int mapByte(@NonNull String name);
|
||||
|
||||
/**
|
||||
* Map a string name to an integer ID for a primitive char property.
|
||||
*
|
||||
* @param name The name of the property
|
||||
* @return An integer ID for the property
|
||||
* @throws PropertyConflictException If the property name is already mapped as another type.
|
||||
*/
|
||||
int mapChar(@NonNull String name);
|
||||
|
||||
/**
|
||||
* Map a string name to an integer ID for a primitive double property.
|
||||
*
|
||||
* @param name The name of the property
|
||||
* @return An integer ID for the property
|
||||
* @throws PropertyConflictException If the property name is already mapped as another type.
|
||||
*/
|
||||
int mapDouble(@NonNull String name);
|
||||
|
||||
/**
|
||||
* Map a string name to an integer ID for a primitive float property.
|
||||
*
|
||||
* @param name The name of the property
|
||||
* @return An integer ID for the property
|
||||
* @throws PropertyConflictException If the property name is already mapped as another type.
|
||||
*/
|
||||
int mapFloat(@NonNull String name);
|
||||
|
||||
/**
|
||||
* Map a string name to an integer ID for a primitive int property.
|
||||
*
|
||||
* @param name The name of the property
|
||||
* @return An integer ID for the property
|
||||
* @throws PropertyConflictException If the property name is already mapped as another type.
|
||||
*/
|
||||
int mapInt(@NonNull String name);
|
||||
|
||||
/**
|
||||
* Map a string name to an integer ID for a primitive long property.
|
||||
*
|
||||
* @param name The name of the property
|
||||
* @return An integer ID for the property
|
||||
* @throws PropertyConflictException If the property name is already mapped as another type.
|
||||
*/
|
||||
int mapLong(@NonNull String name);
|
||||
|
||||
/**
|
||||
* Map a string name to an integer ID for a primitive short property.
|
||||
*
|
||||
* @param name The name of the property
|
||||
* @return An integer ID for the property
|
||||
* @throws PropertyConflictException If the property name is already mapped as another type.
|
||||
*/
|
||||
int mapShort(@NonNull String name);
|
||||
|
||||
/**
|
||||
* Map a string name to an integer ID for an object property.
|
||||
*
|
||||
* @param name The name of the property
|
||||
* @return An integer ID for the property
|
||||
* @throws PropertyConflictException If the property name is already mapped as another type.
|
||||
*/
|
||||
int mapObject(@NonNull String name);
|
||||
|
||||
/**
|
||||
* Thrown from a map method if a property name is already mapped as different type.
|
||||
*/
|
||||
class PropertyConflictException extends RuntimeException {
|
||||
public PropertyConflictException(
|
||||
@NonNull String name,
|
||||
@NonNull String newPropertyType,
|
||||
@NonNull String existingPropertyType) {
|
||||
super(String.format(
|
||||
"Attempted to map property \"%s\" as type %s, but it is already mapped as %s.",
|
||||
name,
|
||||
newPropertyType,
|
||||
existingPropertyType
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
162
core/java/android/view/inspector/PropertyReader.java
Normal file
162
core/java/android/view/inspector/PropertyReader.java
Normal file
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* Copyright 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.view.inspector;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* An interface for reading the properties of an inspectable object.
|
||||
*
|
||||
* Used as the parameter for {@link InspectionHelper#readProperties(Object, PropertyReader)}.
|
||||
* It has separate methods for all primitive types to avoid autoboxing overhead if a concrete
|
||||
* implementation is able to work with primitives. Implementations should be prepared to accept
|
||||
* {null} as the value of {@link PropertyReader#readObject(int, Object)}.
|
||||
*
|
||||
* @see InspectionHelper#readProperties(Object, PropertyReader)
|
||||
* @hide
|
||||
*/
|
||||
public interface PropertyReader {
|
||||
/**
|
||||
* Read a primitive boolean property.
|
||||
*
|
||||
* @param id Identifier of the property from a {@link PropertyMapper}
|
||||
* @param value Value of the property
|
||||
* @throws PropertyTypeMismatchException If the property ID is not mapped as a {boolean}
|
||||
*/
|
||||
void readBoolean(int id, boolean value);
|
||||
|
||||
/**
|
||||
* Read a primitive byte property.
|
||||
*
|
||||
* @param id Identifier of the property from a {@link PropertyMapper}
|
||||
* @param value Value of the property
|
||||
* @throws PropertyTypeMismatchException If the property ID is not mapped as a {byte}
|
||||
*/
|
||||
void readByte(int id, byte value);
|
||||
|
||||
/**
|
||||
* Read a primitive character property.
|
||||
*
|
||||
* @param id Identifier of the property from a {@link PropertyMapper}
|
||||
* @param value Value of the property
|
||||
* @throws PropertyTypeMismatchException If the property ID is not mapped as a {char}
|
||||
*/
|
||||
void readChar(int id, char value);
|
||||
|
||||
/**
|
||||
* Read a read a primitive double property.
|
||||
*
|
||||
* @param id Identifier of the property from a {@link PropertyMapper}
|
||||
* @param value Value of the property
|
||||
* @throws PropertyTypeMismatchException If the property ID is not mapped as a {double}
|
||||
*/
|
||||
void readDouble(int id, double value);
|
||||
|
||||
/**
|
||||
* Read a primitive float property.
|
||||
*
|
||||
* @param id Identifier of the property from a {@link PropertyMapper}
|
||||
* @param value Value of the property
|
||||
* @throws PropertyTypeMismatchException If the property ID is not mapped as a {float}
|
||||
*/
|
||||
void readFloat(int id, float value);
|
||||
|
||||
/**
|
||||
* Read a primitive integer property.
|
||||
*
|
||||
* @param id Identifier of the property from a {@link PropertyMapper}
|
||||
* @param value Value of the property
|
||||
* @throws PropertyTypeMismatchException If the property ID is not mapped as an {int}
|
||||
*/
|
||||
void readInt(int id, int value);
|
||||
|
||||
/**
|
||||
* Read a primitive long property.
|
||||
*
|
||||
* @param id Identifier of the property from a {@link PropertyMapper}
|
||||
* @param value Value of the property
|
||||
* @throws PropertyTypeMismatchException If the property ID is not mapped as a {long}
|
||||
*/
|
||||
void readLong(int id, long value);
|
||||
|
||||
/**
|
||||
* Read a primitive short property.
|
||||
*
|
||||
* @param id Identifier of the property from a {@link PropertyMapper}
|
||||
* @param value Value of the property
|
||||
* @throws PropertyTypeMismatchException If the property ID is not mapped as a {short}
|
||||
*/
|
||||
void readShort(int id, short value);
|
||||
|
||||
/**
|
||||
* Read any object as a property.
|
||||
*
|
||||
* If value is null, the property is marked as empty.
|
||||
*
|
||||
* @param id Identifier of the property from a {@link PropertyMapper}
|
||||
* @param value Value of the property
|
||||
* @throws PropertyTypeMismatchException If the property ID is not mapped as an object
|
||||
*/
|
||||
void readObject(int id, @Nullable Object value);
|
||||
|
||||
/**
|
||||
* Thrown if a client calls a typed read method for a property of a different type.
|
||||
*/
|
||||
class PropertyTypeMismatchException extends RuntimeException {
|
||||
public PropertyTypeMismatchException(
|
||||
int id,
|
||||
@NonNull String expectedPropertyType,
|
||||
@NonNull String actualPropertyType,
|
||||
@Nullable String propertyName) {
|
||||
super(formatMessage(id, expectedPropertyType, actualPropertyType, propertyName));
|
||||
}
|
||||
|
||||
public PropertyTypeMismatchException(
|
||||
int id,
|
||||
@NonNull String expectedPropertyType,
|
||||
@NonNull String actualPropertyType) {
|
||||
super(formatMessage(id, expectedPropertyType, actualPropertyType, null));
|
||||
}
|
||||
|
||||
private static @NonNull String formatMessage(
|
||||
int id,
|
||||
@NonNull String expectedPropertyType,
|
||||
@NonNull String actualPropertyType,
|
||||
@Nullable String propertyName) {
|
||||
|
||||
if (propertyName == null) {
|
||||
return String.format(
|
||||
"Attempted to read property with ID 0x%08X as type %s, "
|
||||
+ "but the ID is of type %s.",
|
||||
id,
|
||||
expectedPropertyType,
|
||||
actualPropertyType
|
||||
);
|
||||
} else {
|
||||
return String.format(
|
||||
"Attempted to read property \"%s\" with ID 0x%08X as type %s, "
|
||||
+ "but the ID is of type %s.",
|
||||
propertyName,
|
||||
id,
|
||||
expectedPropertyType,
|
||||
actualPropertyType
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user