com.facebook.litho.ComponentsStethoManagerImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.facebook.litho.ComponentsStethoManagerImpl.java

Source

/**
 * Copyright (c) 2017-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 */

package com.facebook.litho;

import java.lang.reflect.Field;

import android.graphics.Color;
import android.support.annotation.Nullable;
import android.support.v4.util.SimpleArrayMap;

import com.facebook.stetho.inspector.elements.StyleAccumulator;
import com.facebook.yoga.YogaAlign;
import com.facebook.yoga.YogaConstants;
import com.facebook.yoga.YogaDirection;
import com.facebook.yoga.YogaEdge;
import com.facebook.yoga.YogaFlexDirection;
import com.facebook.yoga.YogaJustify;
import com.facebook.yoga.YogaNode;
import com.facebook.yoga.YogaPositionType;
import com.facebook.yoga.YogaUnit;
import com.facebook.yoga.YogaValue;

import static com.facebook.yoga.YogaUnit.PERCENT;
import static com.facebook.yoga.YogaUnit.POINT;

class ComponentsStethoManagerImpl implements ComponentsStethoManager {
    private static final YogaValue YOGA_VALUE_UNDEFINED = new YogaValue(YogaConstants.UNDEFINED,
            YogaUnit.UNDEFINED);
    private static final YogaValue YOGA_VALUE_AUTO = new YogaValue(YogaConstants.UNDEFINED, YogaUnit.AUTO);
    private final static YogaEdge[] edges = YogaEdge.values();
    private final SimpleArrayMap<String, SimpleArrayMap<String, String>> mStyleOverrides = new SimpleArrayMap<>();
    private final SimpleArrayMap<String, SimpleArrayMap<String, String>> mPropOverrides = new SimpleArrayMap<>();
    private final SimpleArrayMap<String, SimpleArrayMap<String, String>> mStateOverrides = new SimpleArrayMap<>();
    private final SimpleArrayMap<String, ComponentStethoNode> mComponentsStethoNodes = new SimpleArrayMap<>();

    private static String toCSSString(String str) {
        final StringBuilder builder = new StringBuilder(str.length());
        builder.append(str);
        for (int i = 0, length = builder.length(); i < length; ++i) {
            final char oldChar = builder.charAt(i);
            final char lowerChar = Character.toLowerCase(oldChar);
            final char newChar = lowerChar == '_' ? '-' : lowerChar;
            builder.setCharAt(i, newChar);
        }
        return builder.toString();
    }

    private static String toCSSString(Object obj) {
        return toCSSString(obj.toString());
    }

    private static String toEnumString(String str) {
        final StringBuilder builder = new StringBuilder(str.length());
        builder.append(str);
        for (int i = 0, length = builder.length(); i < length; ++i) {
            final char oldChar = builder.charAt(i);
            final char upperChar = Character.toUpperCase(oldChar);
            final char newChar = upperChar == '-' ? '_' : upperChar;
            builder.setCharAt(i, newChar);
        }
        return builder.toString();
    }

    static float parseFloat(@Nullable String s) {
        if (s == null) {
            return 0;
        }

        try {
            return Float.parseFloat(s);
        } catch (NumberFormatException e) {
            return 0;
        }
    }

    private static void storeEnum(StyleAccumulator accumulator, SimpleArrayMap<String, String> overrides,
            String key, Object value) {
        if (overrides.containsKey(key)) {
            accumulator.store(key, overrides.get(key), false);
        } else {
            accumulator.store(key, toCSSString(value), false);
        }
    }

    private static void storeFloat(StyleAccumulator accumulator, SimpleArrayMap<String, String> overrides,
            String key, float value) {
        if (overrides.containsKey(key)) {
            accumulator.store(key, overrides.get(key), false);
        } else {
            accumulator.store(key, Float.toString(value), false);
        }
    }

    private static void storeYogaValue(StyleAccumulator accumulator, SimpleArrayMap<String, String> overrides,
            String key, YogaValue value) {
        if (overrides.containsKey(key)) {
            accumulator.store(key, overrides.get(key), false);
        } else {
            final String valueString;
            switch (value.unit) {
            case UNDEFINED:
                valueString = "undefined";
                break;
            case POINT:
                valueString = Float.toString(value.value);
                break;
            case PERCENT:
                valueString = value.value + "%";
                break;
            case AUTO:
                valueString = "auto";
                break;
            default:
                throw new IllegalStateException();
            }
            accumulator.store(key, valueString, false);
        }
    }

    private static void storeDrawable(StyleAccumulator accumulator, SimpleArrayMap<String, String> overrides,
            String key) {
        if (overrides.containsKey(key)) {
            accumulator.store(key, overrides.get(key), false);
        } else {
            accumulator.store(key, "<drawable>", false);
        }
    }

    void getStyles(ComponentStethoNode stethoNode, StyleAccumulator accumulator) {
        final YogaNode yogaNode = stethoNode.node.mYogaNode;
        final YogaNode defaults = ComponentsPools.acquireYogaNode();

        SimpleArrayMap<String, String> overrides = mStyleOverrides.get(stethoNode.key);
        if (overrides == null) {
            overrides = new SimpleArrayMap<>();
            mStyleOverrides.put(stethoNode.key, overrides);
        }

        storeDrawable(accumulator, overrides, "background");
        storeDrawable(accumulator, overrides, "foreground");

        storeEnum(accumulator, overrides, "direction", yogaNode.getStyleDirection());
        storeEnum(accumulator, overrides, "flex-direction", yogaNode.getFlexDirection());
        storeEnum(accumulator, overrides, "justify-content", yogaNode.getJustifyContent());
        storeEnum(accumulator, overrides, "align-items", yogaNode.getAlignItems());
        storeEnum(accumulator, overrides, "align-self", yogaNode.getAlignSelf());
        storeEnum(accumulator, overrides, "align-content", yogaNode.getAlignContent());
        storeEnum(accumulator, overrides, "position", yogaNode.getPositionType());
        storeFloat(accumulator, overrides, "flex-grow", yogaNode.getFlexGrow());
        storeFloat(accumulator, overrides, "flex-shrink", yogaNode.getFlexShrink());
        storeYogaValue(accumulator, overrides, "flex-basis", yogaNode.getFlexBasis());

        storeYogaValue(accumulator, overrides, "width", yogaNode.getWidth());
        storeYogaValue(accumulator, overrides, "min-width", yogaNode.getMinWidth());
        storeYogaValue(accumulator, overrides, "max-width", yogaNode.getMaxWidth());
        storeYogaValue(accumulator, overrides, "height", yogaNode.getHeight());
        storeYogaValue(accumulator, overrides, "min-height", yogaNode.getMinHeight());
        storeYogaValue(accumulator, overrides, "max-height", yogaNode.getMaxHeight());

        for (YogaEdge edge : edges) {
            final String key = "margin-" + toCSSString(edge);
            storeYogaValue(accumulator, overrides, key, yogaNode.getMargin(edge));
        }

        for (YogaEdge edge : edges) {
            final String key = "padding-" + toCSSString(edge);
            storeYogaValue(accumulator, overrides, key, yogaNode.getPadding(edge));
        }

        for (YogaEdge edge : edges) {
            final String key = "position-" + toCSSString(edge);
            storeYogaValue(accumulator, overrides, key, yogaNode.getPosition(edge));
        }

        for (YogaEdge edge : edges) {
            final String key = "border-" + toCSSString(edge);
            storeFloat(accumulator, overrides, key, yogaNode.getBorder(edge));
        }

        ComponentsPools.release(defaults);
    }

    private static int parseColor(String color) {
        if (color == null || color.length() == 0) {
            return Color.TRANSPARENT;
        }

        // Color.parse does not handle hax code with 3 ints e.g. #123
        if (color.length() == 4) {
            final char r = color.charAt(1);
            final char g = color.charAt(2);
            final char b = color.charAt(3);
            color = "#" + r + r + g + g + b + b;
        }

        return Color.parseColor(color);
    }

    public void applyOverrides(InternalNode node) {
        final String nodeKey = getGlobalKey(node, 0); // We only override the root

        if (mStyleOverrides.containsKey(nodeKey)) {
            final SimpleArrayMap<String, String> styles = mStyleOverrides.get(nodeKey);
            for (int i = 0, size = styles.size(); i < size; i++) {
                final String key = styles.keyAt(i);
                final String value = styles.get(key);

                try {
                    if (key.equals("background")) {
                        node.backgroundColor(parseColor(value));
                    }

                    if (key.equals("foreground")) {
                        node.foregroundColor(parseColor(value));
                    }

                    if (key.equals("direction")) {
                        node.layoutDirection(YogaDirection.valueOf(toEnumString(value)));
                    }

                    if (key.equals("flex-direction")) {
                        node.flexDirection(YogaFlexDirection.valueOf(toEnumString(value)));
                    }

                    if (key.equals("justify-content")) {
                        node.justifyContent(YogaJustify.valueOf(toEnumString(value)));
                    }

                    if (key.equals("align-items")) {
                        node.alignItems(YogaAlign.valueOf(toEnumString(value)));
                    }

                    if (key.equals("align-self")) {
                        node.alignSelf(YogaAlign.valueOf(toEnumString(value)));
                    }

                    if (key.equals("align-content")) {
                        node.alignContent(YogaAlign.valueOf(toEnumString(value)));
                    }

                    if (key.equals("position")) {
                        node.positionType(YogaPositionType.valueOf(toEnumString(value)));
                    }

                    if (key.equals("flex-grow")) {
                        node.flexGrow(parseFloat(value));
                    }

                    if (key.equals("flex-shrink")) {
                        node.flexShrink(parseFloat(value));
                    }
                } catch (IllegalArgumentException ignored) {
                    // ignore errors when the user suplied an invalid enum value
                }

                if (key.equals("flex-basis")) {
                    final YogaValue flexBasis = yogaValueFromString(value);
                    if (flexBasis == null) {
                        continue;
                    }
                    switch (flexBasis.unit) {
                    case AUTO:
                        node.flexBasisAuto();
                        break;
                    case UNDEFINED:
                    case POINT:
                        node.flexBasisPx(FastMath.round(flexBasis.value));
                        break;
                    case PERCENT:
                        node.flexBasisPercent(FastMath.round(flexBasis.value));
                        break;
                    }
                }

                if (key.equals("width")) {
                    final YogaValue width = yogaValueFromString(value);
                    if (width == null) {
                        continue;
                    }
                    switch (width.unit) {
                    case AUTO:
                        node.widthAuto();
                        break;
                    case UNDEFINED:
                    case POINT:
                        node.widthPx(FastMath.round(width.value));
                        break;
                    case PERCENT:
                        node.widthPercent(FastMath.round(width.value));
                        break;
                    }
                }

                if (key.equals("min-width")) {
                    final YogaValue minWidth = yogaValueFromString(value);
                    if (minWidth == null) {
                        continue;
                    }
                    switch (minWidth.unit) {
                    case UNDEFINED:
                    case POINT:
                        node.minWidthPx(FastMath.round(minWidth.value));
                        break;
                    case PERCENT:
                        node.minWidthPercent(FastMath.round(minWidth.value));
                        break;
                    }
                }

                if (key.equals("max-width")) {
                    final YogaValue maxWidth = yogaValueFromString(value);
                    if (maxWidth == null) {
                        continue;
                    }
                    switch (maxWidth.unit) {
                    case UNDEFINED:
                    case POINT:
                        node.maxWidthPx(FastMath.round(maxWidth.value));
                        break;
                    case PERCENT:
                        node.maxWidthPercent(FastMath.round(maxWidth.value));
                        break;
                    }
                }

                if (key.equals("height")) {
                    final YogaValue height = yogaValueFromString(value);
                    if (height == null) {
                        continue;
                    }
                    switch (height.unit) {
                    case AUTO:
                        node.heightAuto();
                        break;
                    case UNDEFINED:
                    case POINT:
                        node.heightPx(FastMath.round(height.value));
                        break;
                    case PERCENT:
                        node.heightPercent(FastMath.round(height.value));
                        break;
                    }
                }

                if (key.equals("min-height")) {
                    final YogaValue minHeight = yogaValueFromString(value);
                    if (minHeight == null) {
                        continue;
                    }
                    switch (minHeight.unit) {
                    case UNDEFINED:
                    case POINT:
                        node.minHeightPx(FastMath.round(minHeight.value));
                        break;
                    case PERCENT:
                        node.minHeightPercent(FastMath.round(minHeight.value));
                        break;
                    }
                }

                if (key.equals("max-height")) {
                    final YogaValue maxHeight = yogaValueFromString(value);
                    if (maxHeight == null) {
                        continue;
                    }
                    switch (maxHeight.unit) {
                    case UNDEFINED:
                    case POINT:
                        node.maxHeightPx(FastMath.round(maxHeight.value));
                        break;
                    case PERCENT:
                        node.maxHeightPercent(FastMath.round(maxHeight.value));
                        break;
                    }
                }

                for (YogaEdge edge : edges) {
                    if (key.equals("margin-" + toCSSString(edge))) {
                        final YogaValue margin = yogaValueFromString(value);
                        if (margin == null) {
                            continue;
                        }
                        switch (margin.unit) {
                        case UNDEFINED:
                        case POINT:
                            node.marginPx(edge, FastMath.round(margin.value));
                            break;
                        case AUTO:
                            node.marginAuto(edge);
                            break;
                        case PERCENT:
                            node.marginPercent(edge, FastMath.round(margin.value));
                            break;
                        }
                    }
                }

                for (YogaEdge edge : edges) {
                    if (key.equals("padding-" + toCSSString(edge))) {
                        final YogaValue padding = yogaValueFromString(value);
                        if (padding == null) {
                            continue;
                        }
                        switch (padding.unit) {
                        case UNDEFINED:
                        case POINT:
                            node.paddingPx(edge, FastMath.round(padding.value));
                            break;
                        case PERCENT:
                            node.paddingPercent(edge, FastMath.round(padding.value));
                            break;
                        }
                    }
                }

                for (YogaEdge edge : edges) {
                    if (key.equals("position-" + toCSSString(edge))) {
                        final YogaValue position = yogaValueFromString(value);
                        if (position == null) {
                            continue;
                        }
                        switch (position.unit) {
                        case UNDEFINED:
                        case POINT:
                            node.positionPx(edge, FastMath.round(position.value));
                            break;
                        case PERCENT:
                            node.positionPercent(edge, FastMath.round(position.value));
                            break;
                        }
                    }
                }

                for (YogaEdge edge : edges) {
                    if (key.equals("border-" + toCSSString(edge))) {
                        final float border = parseFloat(value);
                        node.borderWidthPx(edge, FastMath.round(border));
                    }
                }
            }
        }

        if (mPropOverrides.containsKey(nodeKey)) {
            final Component component = node.getRootComponent();
            if (component != null) {
                final SimpleArrayMap<String, String> props = mPropOverrides.get(nodeKey);
                for (int i = 0, size = props.size(); i < size; i++) {
                    final String key = props.keyAt(i);
                    applyReflectiveOverride(component, key, props.get(key));
                }
            }
        }

        if (mStateOverrides.containsKey(nodeKey)) {
            final Component component = node.getRootComponent();
            final ComponentLifecycle.StateContainer stateContainer = component == null ? null
                    : component.getStateContainer();
            if (stateContainer != null) {
                final SimpleArrayMap<String, String> state = mStateOverrides.get(nodeKey);
                for (int i = 0, size = state.size(); i < size; i++) {
                    final String key = state.keyAt(i);
                    applyReflectiveOverride(stateContainer, key, state.get(key));
                }
            }
        }
    }

    private void applyReflectiveOverride(Object o, String key, String value) {
        try {
            final Field field = o.getClass().getDeclaredField(key);
            final Class type = field.getType();
            field.setAccessible(true);

            if (type.equals(short.class)) {
                field.set(o, Short.parseShort(value));
            } else if (type.equals(int.class)) {
                field.set(o, Integer.parseInt(value));
            } else if (type.equals(long.class)) {
                field.set(o, Long.parseLong(value));
            } else if (type.equals(float.class)) {
                field.set(o, Float.parseFloat(value));
            } else if (type.equals(double.class)) {
                field.set(o, Double.parseDouble(value));
            } else if (type.equals(boolean.class)) {
                field.set(o, Boolean.parseBoolean(value));
            } else if (type.equals(byte.class)) {
                field.set(o, Byte.parseByte(value));
            } else if (type.equals(char.class)) {
                field.set(o, value.charAt(0));
            } else if (CharSequence.class.isAssignableFrom(type)) {
                field.set(o, value);
            }
        } catch (Exception ignored) {
        }
    }

    public void setStyleOverride(ComponentStethoNode stethoNode, String key, String value) {
        SimpleArrayMap<String, String> styles = mStyleOverrides.get(stethoNode.key);
        if (styles == null) {
            styles = new SimpleArrayMap<>();
            mStyleOverrides.put(stethoNode.key, styles);
        }

        styles.put(key, value);
    }

    public void setPropOverride(ComponentStethoNode element, String key, String value) {
        SimpleArrayMap<String, String> props = mPropOverrides.get(element.key);
        if (props == null) {
            props = new SimpleArrayMap<>();
            mPropOverrides.put(element.key, props);
        }

        props.put(key, value);
    }

    public void setStateOverride(ComponentStethoNode element, String key, String value) {
        SimpleArrayMap<String, String> props = mStateOverrides.get(element.key);
        if (props == null) {
            props = new SimpleArrayMap<>();
            mStateOverrides.put(element.key, props);
        }

        props.put(key, value);
    }

    private static YogaValue yogaValueFromString(String s) {
        if (s == null) {
            return null;
        }

        if ("undefined".equals(s)) {
            return YOGA_VALUE_UNDEFINED;
        }

        if ("auto".equals(s)) {
            return YOGA_VALUE_AUTO;
        }

        if (s.endsWith("%")) {
            return new YogaValue(parseFloat(s.substring(0, s.length() - 1)), PERCENT);
        }

        return new YogaValue(parseFloat(s), POINT);
    }

    private static String getGlobalKey(InternalNode node, int componentIndex) {
        final InternalNode parent = node.getParent();
        final InternalNode nestedTreeHolder = node.getNestedTreeHolder();

        String key;
        if (parent != null) {
            key = getGlobalKey(parent, 0) + "." + parent.getChildIndex(node);
        } else if (nestedTreeHolder != null) {
            key = "nested";
        } else {
            key = "root";
        }

        return key + "(" + componentIndex + ")";
    }

    public ComponentStethoNode getComponentsStethoNode(InternalNode node, int componentIndex) {
        final String globalKey = getGlobalKey(node, componentIndex);
        ComponentStethoNode componentStethoNode = mComponentsStethoNodes.get(globalKey);

        if (componentStethoNode == null) {
            componentStethoNode = new ComponentStethoNode();
            mComponentsStethoNodes.put(globalKey, componentStethoNode);
        }

        componentStethoNode.key = globalKey;
        componentStethoNode.node = node;
        componentStethoNode.componentIndex = componentIndex;

        return componentStethoNode;
    }
}