diff options
author | 2018-11-07 20:54:53 +0000 | |
---|---|---|
committer | 2018-11-07 20:54:53 +0000 | |
commit | fe878c454ad92f98db770eb51a55ac14ca7fcd08 (patch) | |
tree | 60e1e322272b95771f8f43444c3170f2634a61f8 | |
parent | 53feef27c41b1c630c1dccdf6af94534296c283b (diff) | |
parent | 0f25a25f4066038e9c812a801659368ccbdc98de (diff) |
Merge "Add InspectionHelper and related interfaces"
-rw-r--r-- | core/java/android/view/inspector/ChildTraverser.java | 46 | ||||
-rw-r--r-- | core/java/android/view/inspector/InspectionHelper.java | 145 | ||||
-rw-r--r-- | core/java/android/view/inspector/OWNERS | 3 | ||||
-rw-r--r-- | core/java/android/view/inspector/PropertyMapper.java | 130 | ||||
-rw-r--r-- | core/java/android/view/inspector/PropertyReader.java | 162 |
5 files changed, 486 insertions, 0 deletions
diff --git a/core/java/android/view/inspector/ChildTraverser.java b/core/java/android/view/inspector/ChildTraverser.java new file mode 100644 index 000000000000..b775de503d98 --- /dev/null +++ b/core/java/android/view/inspector/ChildTraverser.java @@ -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); +} diff --git a/core/java/android/view/inspector/InspectionHelper.java b/core/java/android/view/inspector/InspectionHelper.java new file mode 100644 index 000000000000..27a97040926c --- /dev/null +++ b/core/java/android/view/inspector/InspectionHelper.java @@ -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() + )); + } + } +} diff --git a/core/java/android/view/inspector/OWNERS b/core/java/android/view/inspector/OWNERS new file mode 100644 index 000000000000..0473f54e57ca --- /dev/null +++ b/core/java/android/view/inspector/OWNERS @@ -0,0 +1,3 @@ +alanv@google.com +ashleyrose@google.com +aurimas@google.com
\ No newline at end of file diff --git a/core/java/android/view/inspector/PropertyMapper.java b/core/java/android/view/inspector/PropertyMapper.java new file mode 100644 index 000000000000..35550bd45b30 --- /dev/null +++ b/core/java/android/view/inspector/PropertyMapper.java @@ -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 + )); + } + } +} diff --git a/core/java/android/view/inspector/PropertyReader.java b/core/java/android/view/inspector/PropertyReader.java new file mode 100644 index 000000000000..df81c102dbad --- /dev/null +++ b/core/java/android/view/inspector/PropertyReader.java @@ -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 + ); + } + } + } +} |