Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit fe878c45 authored by Ember Rose's avatar Ember Rose Committed by Android (Google) Code Review
Browse files

Merge "Add InspectionHelper and related interfaces"

parents 53feef27 0f25a25f
Loading
Loading
Loading
Loading
+46 −0
Original line number Diff line number Diff line
/*
 * 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 −0
Original line number Diff line number Diff line
/*
 * 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 −0
Original line number Diff line number Diff line
alanv@google.com
ashleyrose@google.com
aurimas@google.com
 No newline at end of file
+130 −0
Original line number Diff line number Diff line
/*
 * 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 −0
Original line number Diff line number Diff line
/*
 * 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
                );
            }
        }
    }
}