public class

ConstraintSetParser

extends java.lang.Object

 java.lang.Object

↳androidx.constraintlayout.core.state.ConstraintSetParser

Gradle dependencies

compile group: 'androidx.constraintlayout', name: 'constraintlayout-core', version: '1.1.0-alpha01'

  • groupId: androidx.constraintlayout
  • artifactId: constraintlayout-core
  • version: 1.1.0-alpha01

Artifact androidx.constraintlayout:constraintlayout-core:1.1.0-alpha01 it located at Google repository (https://maven.google.com/)

Summary

Constructors
publicConstraintSetParser()

Methods
public static voidparseDesignElementsJSON(java.lang.String content, java.util.ArrayList<ConstraintSetParser.DesignElement> list)

parse the Design time elements.

public static voidparseJSON(java.lang.String content, State state, ConstraintSetParser.LayoutVariables layoutVariables)

Top leve parsing of the json ConstraintSet supporting "Variables", "Helpers", "Generate", guidelines, and barriers

public static voidparseJSON(java.lang.String content, Transition transition, int state)

Parse and populate a transition

public static voidparseMotionSceneJSON(CoreMotionScene scene, java.lang.String content)

Parse and build a motionScene

from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Constructors

public ConstraintSetParser()

Methods

public static void parseJSON(java.lang.String content, Transition transition, int state)

Parse and populate a transition

Parameters:

content: JSON string to parse
transition: The Transition to be populated
state: @TODO what is this

public static void parseMotionSceneJSON(CoreMotionScene scene, java.lang.String content)

Parse and build a motionScene

public static void parseJSON(java.lang.String content, State state, ConstraintSetParser.LayoutVariables layoutVariables)

Top leve parsing of the json ConstraintSet supporting "Variables", "Helpers", "Generate", guidelines, and barriers

Parameters:

content: the JSON string
state: the state to populate
layoutVariables: the variables to override

public static void parseDesignElementsJSON(java.lang.String content, java.util.ArrayList<ConstraintSetParser.DesignElement> list)

parse the Design time elements.

Parameters:

content: the json
list: output the list of design elements

Source

/*
 * Copyright (C) 2022 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 androidx.constraintlayout.core.state;

import static androidx.constraintlayout.core.motion.utils.TypedValues.MotionType.TYPE_QUANTIZE_INTERPOLATOR_TYPE;
import static androidx.constraintlayout.core.motion.utils.TypedValues.MotionType.TYPE_QUANTIZE_MOTIONSTEPS;
import static androidx.constraintlayout.core.motion.utils.TypedValues.MotionType.TYPE_QUANTIZE_MOTION_PHASE;

import androidx.constraintlayout.core.motion.utils.TypedBundle;
import androidx.constraintlayout.core.motion.utils.TypedValues;
import androidx.constraintlayout.core.parser.CLArray;
import androidx.constraintlayout.core.parser.CLElement;
import androidx.constraintlayout.core.parser.CLKey;
import androidx.constraintlayout.core.parser.CLNumber;
import androidx.constraintlayout.core.parser.CLObject;
import androidx.constraintlayout.core.parser.CLParser;
import androidx.constraintlayout.core.parser.CLParsingException;
import androidx.constraintlayout.core.parser.CLString;
import androidx.constraintlayout.core.state.helpers.BarrierReference;
import androidx.constraintlayout.core.state.helpers.ChainReference;
import androidx.constraintlayout.core.state.helpers.GuidelineReference;
import androidx.constraintlayout.core.widgets.ConstraintWidget;

import java.util.ArrayList;
import java.util.HashMap;

public class ConstraintSetParser {

    private static final boolean PARSER_DEBUG = false;

    public static class DesignElement {
        String mId;
        String mType;
        HashMap<String, String> mParams;

        public String getId() {
            return mId;
        }

        public String getType() {
            return mType;
        }

        public HashMap<String, String> getParams() {
            return mParams;
        }

        DesignElement(String id,
                String type,
                HashMap<String, String> params) {
            mId = id;
            mType = type;
            mParams = params;
        }
    }

    /**
     * Provide the storage for managing Variables in the system.
     * When the json has a variable:{   } section this is used.
     */
    public static class LayoutVariables {
        HashMap<String, Integer> mMargins = new HashMap<>();
        HashMap<String, GeneratedValue> mGenerators = new HashMap<>();
        HashMap<String, ArrayList<String>> mArrayIds = new HashMap<>();

        void put(String elementName, int element) {
            mMargins.put(elementName, element);
        }

        void put(String elementName, float start, float incrementBy) {
            if (mGenerators.containsKey(elementName)) {
                if (mGenerators.get(elementName) instanceof OverrideValue) {
                    return;
                }
            }
            mGenerators.put(elementName, new Generator(start, incrementBy));
        }

        void put(String elementName,
                float from,
                float to,
                float step,
                String prefix,
                String postfix) {
            if (mGenerators.containsKey(elementName)) {
                if (mGenerators.get(elementName) instanceof OverrideValue) {
                    return;
                }
            }
            FiniteGenerator generator =
                    new FiniteGenerator(from, to, step, prefix, postfix);
            mGenerators.put(elementName, generator);
            mArrayIds.put(elementName, generator.array());

        }

        /**
         * insert an override variable
         *
         * @param elementName the name
         * @param value       the value a float
         */
        public void putOverride(String elementName, float value) {
            GeneratedValue generator = new OverrideValue(value);
            mGenerators.put(elementName, generator);
        }

        float get(Object elementName) {
            if (elementName instanceof CLString) {
                String stringValue = ((CLString) elementName).content();
                if (mGenerators.containsKey(stringValue)) {
                    return mGenerators.get(stringValue).value();
                }
                if (mMargins.containsKey(stringValue)) {
                    return mMargins.get(stringValue).floatValue();
                }
            } else if (elementName instanceof CLNumber) {
                return ((CLNumber) elementName).getFloat();
            }
            return 0f;
        }

        ArrayList<String> getList(String elementName) {
            if (mArrayIds.containsKey(elementName)) {
                return mArrayIds.get(elementName);
            }
            return null;
        }

        void put(String elementName, ArrayList<String> elements) {
            mArrayIds.put(elementName, elements);
        }

    }

    interface GeneratedValue {
        float value();
    }

    /**
     * Generate a floating point value
     */
    static class Generator implements GeneratedValue {
        float mStart = 0;
        float mIncrementBy = 0;
        float mCurrent = 0;
        boolean mStop = false;

        Generator(float start, float incrementBy) {
            mStart = start;
            mIncrementBy = incrementBy;
            mCurrent = start;
        }

        @Override
        public float value() {
            if (!mStop) {
                mCurrent += mIncrementBy;
            }
            return mCurrent;
        }
    }

    /**
     * Generate values like button1, button2 etc.
     */
    static class FiniteGenerator implements GeneratedValue {
        float mFrom = 0;
        float mTo = 0;
        float mStep = 0;
        boolean mStop = false;
        String mPrefix;
        String mPostfix;
        float mCurrent = 0;
        float mInitial;
        float mMax;

        FiniteGenerator(float from,
                float to,
                float step,
                String prefix,
                String postfix) {
            mFrom = from;
            mTo = to;
            mStep = step;
            mPrefix = (prefix == null) ? "" : prefix;
            mPostfix = (postfix == null) ? "" : postfix;
            mMax = to;
            mInitial = from;
        }

        @Override
        public float value() {
            if (mCurrent >= mMax) {
                mStop = true;
            }
            if (!mStop) {
                mCurrent += mStep;
            }
            return mCurrent;
        }

        public ArrayList<String> array() {
            ArrayList<String> array = new ArrayList<>();
            int value = (int) mInitial;
            int maxInt = (int) mMax;
            for (int i = value; i <= maxInt; i++) {
                array.add(mPrefix + value + mPostfix);
                value += mStep;
            }
            return array;

        }

    }

    static class OverrideValue implements GeneratedValue {
        float mValue;

        OverrideValue(float value) {
            mValue = value;
        }

        @Override
        public float value() {
            return mValue;
        }
    }
//==================== end store variables =========================
//==================== MotionScene =========================

    public enum MotionLayoutDebugFlags {
        NONE,
        SHOW_ALL,
        UNKNOWN
    }

    //==================== end Motion Scene =========================

    /**
     * Parse and populate a transition
     *
     * @param content    JSON string to parse
     * @param transition The Transition to be populated
     * @param state      @TODO what is this
     */
    public static void parseJSON(String content, Transition transition, int state) {
        try {
            CLObject json = CLParser.parse(content);
            ArrayList<String> elements = json.names();
            if (elements == null) {
                return;
            }
            for (String elementName : elements) {
                CLElement base_element = json.get(elementName);
                if (base_element instanceof CLObject) {
                    CLObject element = (CLObject) base_element;
                    CLObject customProperties = element.getObjectOrNull("custom");
                    if (customProperties != null) {
                        ArrayList<String> properties = customProperties.names();
                        for (String property : properties) {
                            CLElement value = customProperties.get(property);
                            if (value instanceof CLNumber) {
                                transition.addCustomFloat(
                                        state,
                                        elementName,
                                        property,
                                        value.getFloat()
                                );
                            } else if (value instanceof CLString) {
                                long color = parseColorString(value.content());
                                if (color != -1) {
                                    transition.addCustomColor(state,
                                            elementName, property, (int) color);
                                }
                            }
                        }
                    }
                }

            }
        } catch (CLParsingException e) {
            System.err.println("Error parsing JSON " + e);
        }
    }

    /**
     * Parse and build a motionScene
     *
     * @Todo this should be in a MotionScene / MotionSceneParser
     */
    public static void parseMotionSceneJSON(CoreMotionScene scene, String content) {
        try {
            CLObject json = CLParser.parse(content);
            ArrayList<String> elements = json.names();
            if (elements == null) {
                return;
            }
            for (String elementName : elements) {
                CLElement element = json.get(elementName);
                if (element instanceof CLObject) {
                    CLObject clObject = (CLObject) element;
                    switch (elementName) {
                        case "ConstraintSets":
                            parseConstraintSets(scene, clObject);
                            break;
                        case "Transitions":
                            parseTransitions(scene, clObject);
                            break;
                        case "Header":
                            parseHeader(scene, clObject);
                            break;
                    }
                }
            }
        } catch (CLParsingException e) {
            System.err.println("Error parsing JSON " + e);
        }
    }

    /**
     * Parse ConstraintSets and populate MotionScene
     */
    static void parseConstraintSets(CoreMotionScene scene,
            CLObject json) throws CLParsingException {
        ArrayList<String> constraintSetNames = json.names();
        if (constraintSetNames == null) {
            return;
        }

        for (String csName : constraintSetNames) {
            CLObject constraintSet = json.getObject(csName);
            boolean added = false;
            String ext = constraintSet.getStringOrNull("Extends");
            if (ext != null && !ext.isEmpty()) {
                String base = scene.getConstraintSet(ext);
                if (base == null) {
                    continue;
                }

                CLObject baseJson = CLParser.parse(base);
                ArrayList<String> widgetsOverride = constraintSet.names();
                if (widgetsOverride == null) {
                    continue;
                }

                for (String widgetOverrideName : widgetsOverride) {
                    CLElement value = constraintSet.get(widgetOverrideName);
                    if (value instanceof CLObject) {
                        override(baseJson, widgetOverrideName, (CLObject) value);
                    }
                }

                scene.setConstraintSetContent(csName, baseJson.toJSON());
                added = true;
            }
            if (!added) {
                scene.setConstraintSetContent(csName, constraintSet.toJSON());
            }
        }

    }

    static void override(CLObject baseJson,
            String name, CLObject overrideValue) throws CLParsingException {
        if (!baseJson.has(name)) {
            baseJson.put(name, overrideValue);
        } else {
            CLObject base = baseJson.getObject(name);
            ArrayList<String> keys = overrideValue.names();
            for (String key : keys) {
                if (!key.equals("clear")) {
                    base.put(key, overrideValue.get(key));
                    continue;
                }
                CLArray toClear = overrideValue.getArray("clear");
                for (int i = 0; i < toClear.size(); i++) {
                    String clearedKey = toClear.getStringOrNull(i);
                    if (clearedKey == null) {
                        continue;
                    }
                    switch (clearedKey) {
                        case "dimensions":
                            base.remove("width");
                            base.remove("height");
                            break;
                        case "constraints":
                            base.remove("start");
                            base.remove("end");
                            base.remove("top");
                            base.remove("bottom");
                            base.remove("baseline");
                            base.remove("center");
                            base.remove("centerHorizontally");
                            base.remove("centerVertically");
                            break;
                        case "transforms":
                            base.remove("visibility");
                            base.remove("alpha");
                            base.remove("pivotX");
                            base.remove("pivotY");
                            base.remove("rotationX");
                            base.remove("rotationY");
                            base.remove("rotationZ");
                            base.remove("scaleX");
                            base.remove("scaleY");
                            base.remove("translationX");
                            base.remove("translationY");
                            break;
                        default:
                            base.remove(clearedKey);

                    }
                }
            }
        }
    }

    /**
     * Parse the Transition
     */
    static void parseTransitions(CoreMotionScene scene, CLObject json) throws CLParsingException {
        ArrayList<String> elements = json.names();
        if (elements == null) {
            return;
        }
        for (String elementName : elements) {
            scene.setTransitionContent(elementName, json.getObject(elementName).toJSON());
        }
    }

    /**
     * Used to parse for "export"
     */
    static void parseHeader(CoreMotionScene scene, CLObject json) {
        String name = json.getStringOrNull("export");
        if (name != null) {
            scene.setDebugName(name);
        }
    }

    /**
     * Top leve parsing of the json ConstraintSet supporting
     * "Variables", "Helpers", "Generate", guidelines, and barriers
     *
     * @param content         the JSON string
     * @param state           the state to populate
     * @param layoutVariables the variables to override
     */
    public static void parseJSON(String content, State state,
            LayoutVariables layoutVariables) throws CLParsingException {
        try {
            CLObject json = CLParser.parse(content);
            ArrayList<String> elements = json.names();
            if (elements == null) {
                return;
            }
            for (String elementName : elements) {
                CLElement element = json.get(elementName);
                if (PARSER_DEBUG) {
                    System.out.println("[" + elementName + "] = " + element
                            + " > " + element.getContainer());
                }
                switch (elementName) {
                    case "Variables":
                        if (element instanceof CLObject) {
                            parseVariables(state, layoutVariables, (CLObject) element);
                        }
                        break;
                    case "Helpers":
                        if (element instanceof CLArray) {
                            parseHelpers(state, layoutVariables, (CLArray) element);
                        }
                        break;
                    case "Generate":
                        if (element instanceof CLObject) {
                            parseGenerate(state, layoutVariables, (CLObject) element);
                        }
                        break;
                    default:
                        if (element instanceof CLObject) {
                            String type = lookForType((CLObject) element);
                            if (type != null) {
                                switch (type) {
                                    case "hGuideline":
                                        parseGuidelineParams(
                                                ConstraintWidget.HORIZONTAL,
                                                state,
                                                elementName,
                                                (CLObject) element);
                                        break;
                                    case "vGuideline":
                                        parseGuidelineParams(
                                                ConstraintWidget.VERTICAL,
                                                state,
                                                elementName,
                                                (CLObject) element
                                        );
                                        break;
                                    case "barrier":
                                        parseBarrier(state, elementName, (CLObject) element);
                                        break;
                                }
                            } else {
                                parseWidget(state, layoutVariables,
                                        elementName, (CLObject) element);
                            }
                        } else if (element instanceof CLNumber) {
                            layoutVariables.put(elementName, element.getInt());
                        }
                }
            }

        } catch (CLParsingException e) {
            System.err.println("Error parsing JSON " + e);
        }
    }

    private static void parseVariables(State state,
            LayoutVariables layoutVariables,
            CLObject json) throws CLParsingException {
        ArrayList<String> elements = json.names();
        if (elements == null) {
            return;
        }
        for (String elementName : elements) {
            CLElement element = json.get(elementName);
            if (element instanceof CLNumber) {
                layoutVariables.put(elementName, element.getInt());
            } else if (element instanceof CLObject) {
                CLObject obj = (CLObject) element;
                ArrayList<String> arrayIds;
                if (obj.has("from") && obj.has("to")) {
                    float from = layoutVariables.get(obj.get("from"));
                    float to = layoutVariables.get(obj.get("to"));
                    String prefix = obj.getStringOrNull("prefix");
                    String postfix = obj.getStringOrNull("postfix");
                    layoutVariables.put(elementName, from, to, 1f, prefix, postfix);
                } else if (obj.has("from") && obj.has("step")) {
                    float start = layoutVariables.get(obj.get("from"));
                    float increment = layoutVariables.get(obj.get("step"));
                    layoutVariables.put(elementName, start, increment);

                } else if (obj.has("ids")) {
                    CLArray ids = obj.getArray("ids");
                    arrayIds = new ArrayList<>();
                    for (int i = 0; i < ids.size(); i++) {
                        arrayIds.add(ids.getString(i));
                    }
                    layoutVariables.put(elementName, arrayIds);
                } else if (obj.has("tag")) {
                    arrayIds = state.getIdsForTag(obj.getString("tag"));
                    layoutVariables.put(elementName, arrayIds);
                }
            }
        }
    }

    /**
     * parse the Design time elements.
     *
     * @param content the json
     * @param list    output the list of design elements
     */
    public static void parseDesignElementsJSON(
            String content, ArrayList<DesignElement> list) throws CLParsingException {
        CLObject json = CLParser.parse(content);
        ArrayList<String> elements = json.names();
        if (elements == null) {
            return;
        }
        for (int i = 0; i < elements.size(); i++) {
            String elementName = elements.get(i);
            CLElement element = json.get(elementName);
            if (PARSER_DEBUG) {
                System.out.println("[" + element + "] " + element.getClass());
            }
            switch (elementName) {
                case "Design":
                    if (!(element instanceof CLObject)) {
                        return;
                    }
                    CLObject obj = (CLObject) element;
                    elements = obj.names();
                    for (int j = 0; j < elements.size(); j++) {
                        String designElementName = elements.get(j);
                        CLObject designElement =
                                (CLObject) ((CLObject) element).get(designElementName);
                        System.out.printf("element found " + designElementName + "");
                        String type = designElement.getStringOrNull("type");
                        if (type != null) {
                            HashMap<String, String> parameters = new HashMap<String, String>();
                            int size = designElement.size();
                            for (int k = 0; k < size; k++) {

                                CLKey key = (CLKey) designElement.get(j);
                                String paramName = key.content();
                                String paramValue = key.getValue().content();
                                if (paramValue != null) {
                                    parameters.put(paramName, paramValue);
                                }
                            }
                            list.add(new DesignElement(elementName, type, parameters));
                        }
                    }
            }
            break;
        }

    }

    static void parseHelpers(State state,
            LayoutVariables layoutVariables,
            CLArray element) throws CLParsingException {
        for (int i = 0; i < element.size(); i++) {
            CLElement helper = element.get(i);
            if (helper instanceof CLArray) {
                CLArray array = (CLArray) helper;
                if (array.size() > 1) {
                    switch (array.getString(0)) {
                        case "hChain":
                            parseChain(ConstraintWidget.HORIZONTAL, state, layoutVariables, array);
                            break;
                        case "vChain":
                            parseChain(ConstraintWidget.VERTICAL, state, layoutVariables, array);
                            break;
                        case "hGuideline":
                            parseGuideline(ConstraintWidget.HORIZONTAL, state, array);
                            break;
                        case "vGuideline":
                            parseGuideline(ConstraintWidget.VERTICAL, state, array);
                            break;
                    }
                }
            }
        }
    }

    static void parseGenerate(State state,
            LayoutVariables layoutVariables,
            CLObject json) throws CLParsingException {
        ArrayList<String> elements = json.names();
        if (elements == null) {
            return;
        }
        for (String elementName : elements) {
            CLElement element = json.get(elementName);
            ArrayList<String> arrayIds = layoutVariables.getList(elementName);
            if (arrayIds != null && element instanceof CLObject) {
                for (String id : arrayIds) {
                    parseWidget(state, layoutVariables, id, (CLObject) element);
                }
            }
        }
    }

    static void parseChain(int orientation, State state,
            LayoutVariables margins, CLArray helper) throws CLParsingException {
        ChainReference chain = (orientation == ConstraintWidget.HORIZONTAL)
                ? state.horizontalChain() : state.verticalChain();
        CLElement refs = helper.get(1);
        if (!(refs instanceof CLArray) || ((CLArray) refs).size() < 1) {
            return;
        }
        for (int i = 0; i < ((CLArray) refs).size(); i++) {
            chain.add(((CLArray) refs).getString(i));
        }

        if (helper.size() > 2) { // we have additional parameters
            CLElement params = helper.get(2);
            if (!(params instanceof CLObject)) {
                return;
            }
            CLObject obj = (CLObject) params;
            ArrayList<String> constraints = obj.names();
            for (String constraintName : constraints) {
                switch (constraintName) {
                    case "style":
                        CLElement styleObject = ((CLObject) params).get(constraintName);
                        String styleValue;
                        if (styleObject instanceof CLArray && ((CLArray) styleObject).size() > 1) {
                            styleValue = ((CLArray) styleObject).getString(0);
                            float biasValue = ((CLArray) styleObject).getFloat(1);
                            chain.bias(biasValue);
                        } else {
                            styleValue = styleObject.content();
                        }
                        switch (styleValue) {
                            case "packed":
                                chain.style(State.Chain.PACKED);
                                break;
                            case "spread_inside":
                                chain.style(State.Chain.SPREAD_INSIDE);
                                break;
                            default:
                                chain.style(State.Chain.SPREAD);
                                break;
                        }

                        break;
                    default:
                        parseConstraint(
                                state,
                                margins,
                                (CLObject) params,
                                (ConstraintReference) chain,
                                constraintName
                        );
                        break;
                }
            }
        }
    }


    static void parseGuideline(int orientation,
            State state, CLArray helper) throws CLParsingException {
        CLElement params = helper.get(1);
        if (!(params instanceof CLObject)) {
            return;
        }
        String guidelineId = ((CLObject) params).getStringOrNull("id");
        if (guidelineId == null) return;
        parseGuidelineParams(orientation, state, guidelineId, (CLObject) params);
    }


    static void parseGuidelineParams(
            int orientation,
            State state,
            String guidelineId,
            CLObject params
    ) throws CLParsingException {
        ArrayList<String> constraints = params.names();
        if (constraints == null) return;
        ConstraintReference reference = state.constraints(guidelineId);

        if (orientation == ConstraintWidget.HORIZONTAL) {
            state.horizontalGuideline(guidelineId);
        } else {
            state.verticalGuideline(guidelineId);
        }

        GuidelineReference guidelineReference = (GuidelineReference) reference.getFacade();
        for (String constraintName : constraints) {
            switch (constraintName) {
                case "start":
                    int margin = state.convertDimension(params.getFloat(constraintName));
                    guidelineReference.start(state.getDpToPixel().toPixels(margin));
                    break;
                case "end":
                    margin = state.convertDimension(params.getFloat(constraintName));
                    guidelineReference.end(state.getDpToPixel().toPixels(margin));
                    break;
                case "percent":
                    guidelineReference.percent(params.getFloat(constraintName));
                    break;
            }
        }
    }

    static void parseBarrier(
            State state,
            String elementName, CLObject element
    ) throws CLParsingException {
        BarrierReference reference = state.barrier(elementName, State.Direction.END);
        ArrayList<String> constraints = element.names();
        if (constraints == null) {
            return;
        }
        for (String constraintName : constraints) {
            switch (constraintName) {
                case "direction": {
                    switch ((element.getString(constraintName))) {
                        case "start":
                            reference.setBarrierDirection(State.Direction.START);
                            break;
                        case "end":
                            reference.setBarrierDirection(State.Direction.END);
                            break;
                        case "left":
                            reference.setBarrierDirection(State.Direction.LEFT);
                            break;
                        case "right":
                            reference.setBarrierDirection(State.Direction.RIGHT);
                            break;
                        case "top":
                            reference.setBarrierDirection(State.Direction.TOP);
                            break;
                        case "bottom":
                            reference.setBarrierDirection(State.Direction.BOTTOM);
                            break;
                    }
                }
                break;
                case "margin":
                    float margin = element.getFloatOrNaN(constraintName);
                    if (!Float.isNaN(margin)) {
                        reference.margin(margin); // TODO is this a bug
                    }
                    break;
                case "contains":
                    CLArray list = element.getArrayOrNull(constraintName);
                    if (list != null) {
                        for (int j = 0; j < list.size(); j++) {

                            String elementNameReference = list.get(j).content();
                            ConstraintReference elementReference =
                                    state.constraints(elementNameReference);
                            if (PARSER_DEBUG) {
                                System.out.println(
                                        "Add REFERENCE "
                                                + "($elementNameReference = $elementReference) "
                                                + "TO BARRIER "
                                );
                            }
                            reference.add(elementReference);
                        }
                    }
                    break;
            }
        }
    }

    static void parseWidget(
            State state,
            LayoutVariables layoutVariables,
            String elementName,
            CLObject element
    ) throws CLParsingException {
        float value;
        ConstraintReference reference = state.constraints(elementName);
        if (reference.getWidth() == null) {
            // Default to Wrap when the Dimension has not been assigned
            reference.setWidth(Dimension.createWrap());
        }
        if (reference.getHeight() == null) {
            // Default to Wrap when the Dimension has not been assigned
            reference.setHeight(Dimension.createWrap());
        }
        ArrayList<String> constraints = element.names();
        if (constraints == null) {
            return;
        }
        for (String constraintName : constraints) {
            switch (constraintName) {
                case "width":
                    reference.setWidth(parseDimension(element,
                            constraintName, state, state.getDpToPixel()));
                    break;
                case "height":
                    reference.setHeight(parseDimension(element,
                            constraintName, state, state.getDpToPixel()));
                    break;
                case "center":
                    String target = element.getString(constraintName);

                    ConstraintReference targetReference;
                    if (target.equals("parent")) {
                        targetReference = state.constraints(State.PARENT);
                    } else {
                        targetReference = state.constraints(target);
                    }
                    reference.startToStart(targetReference);
                    reference.endToEnd(targetReference);
                    reference.topToTop(targetReference);
                    reference.bottomToBottom(targetReference);
                    break;
                case "centerHorizontally":
                    target = element.getString(constraintName);
                    targetReference = target.equals("parent")
                            ? state.constraints(State.PARENT) : state.constraints(target);

                    reference.startToStart(targetReference);
                    reference.endToEnd(targetReference);
                    break;
                case "centerVertically":
                    target = element.getString(constraintName);
                    targetReference = target.equals("parent")
                            ? state.constraints(State.PARENT) : state.constraints(target);

                    reference.topToTop(targetReference);
                    reference.bottomToBottom(targetReference);
                    break;
                case "alpha":
                    value = layoutVariables.get(element.get(constraintName));
                    reference.alpha(value);
                    break;
                case "scaleX":
                    value = layoutVariables.get(element.get(constraintName));
                    reference.scaleX(value);
                    break;
                case "scaleY":
                    value = layoutVariables.get(element.get(constraintName));
                    reference.scaleY(value);
                    break;
                case "translationX":
                    value = layoutVariables.get(element.get(constraintName));
                    reference.translationX(value);
                    break;
                case "translationY":
                    value = layoutVariables.get(element.get(constraintName));
                    reference.translationY(value);
                    break;
                case "translationZ":
                    value = layoutVariables.get(element.get(constraintName));
                    reference.translationZ(value);
                    break;
                case "pivotX":
                    value = layoutVariables.get(element.get(constraintName));
                    reference.pivotX(value);
                    break;
                case "pivotY":
                    value = layoutVariables.get(element.get(constraintName));
                    reference.pivotY(value);
                    break;
                case "rotationX":
                    value = layoutVariables.get(element.get(constraintName));
                    reference.rotationX(value);
                    break;
                case "rotationY":
                    value = layoutVariables.get(element.get(constraintName));
                    reference.rotationY(value);
                    break;
                case "rotationZ":
                    value = layoutVariables.get(element.get(constraintName));
                    reference.rotationZ(value);
                    break;
                case "visibility":
                    switch (element.getString(constraintName)) {
                        case "visible":
                            reference.visibility(ConstraintWidget.VISIBLE);
                            break;
                        case "invisible":
                            reference.visibility(ConstraintWidget.INVISIBLE);
                            break;
                        case "gone":
                            reference.visibility(ConstraintWidget.GONE);
                            break;
                    }
                    break;
                case "vBias":
                    value = layoutVariables.get(element.get(constraintName));
                    reference.verticalBias(value);
                    break;
                case "hBias":
                    value = layoutVariables.get(element.get(constraintName));
                    reference.horizontalBias(value);
                    break;
                case "vWeight":
                    value = layoutVariables.get(element.get(constraintName));
                    reference.setVerticalChainWeight(value);
                    break;
                case "hWeight":
                    value = layoutVariables.get(element.get(constraintName));
                    reference.setHorizontalChainWeight(value);
                    break;
                case "custom":
                    parseCustomProperties(element, reference, constraintName);
                    break;
                case "motion":
                    parseMotionProperties(element.get(constraintName), reference);
                    break;
                default:
                    parseConstraint(state, layoutVariables, element, reference, constraintName);

            }
        }

    }


    static void parseCustomProperties(
            CLObject element,
            ConstraintReference reference,
            String constraintName
    ) throws CLParsingException {
        CLObject json = element.getObjectOrNull(constraintName);
        if (json == null) {
            return;
        }
        ArrayList<String> properties = json.names();
        if (properties == null) {
            return;
        }
        for (String property : properties) {
            CLElement value = json.get(property);
            if (value instanceof CLNumber) {
                reference.addCustomFloat(property, value.getFloat());
            } else if (value instanceof CLString) {
                long it = parseColorString(value.content());
                if (it != -1) {
                    reference.addCustomColor(property, (int) it);
                }
            }
        }
    }

    private static int indexOf(String val, String... types) {
        for (int i = 0; i < types.length; i++) {
            if (types[i].equals(val)) {
                return i;
            }
        }
        return -1;
    }


    /**
     * parse the motion section of a constraint
     * <pre>
     * csetName: {
     *   idToConstrain : {
     *       motion: {
     *          pathArc : 'startVertical'
     *          relativeTo: 'id'
     *          easing: 'curve'
     *          stagger: '2'
     *          quantize: steps or [steps, 'interpolator' phase ]
     *       }
     *   }
     * }
     * </pre>
     */
    private static void parseMotionProperties(
            CLElement element,
            ConstraintReference reference
    ) throws CLParsingException {
        if (!(element instanceof CLObject)) {
            return;
        }
        CLObject obj = (CLObject) element;
        TypedBundle bundle = new TypedBundle();
        ArrayList<String> constraints = obj.names();
        if (constraints == null) {
            return;
        }
        for (String constraintName : constraints) {

            switch (constraintName) {
                case "pathArc":
                    String val = obj.getString(constraintName);
                    int ord = indexOf(val, "none", "startVertical", "startHorizontal", "flip");
                    if (ord == -1) {
                        System.err.println(obj.getLine() + " pathArc = '" + val + "'");
                        break;
                    }
                    bundle.add(TypedValues.MotionType.TYPE_PATHMOTION_ARC, ord);
                    break;
                case "relativeTo":
                    bundle.add(TypedValues.MotionType.TYPE_ANIMATE_RELATIVE_TO,
                            obj.getString(constraintName));
                    break;
                case "easing":
                    bundle.add(TypedValues.MotionType.TYPE_EASING, obj.getString(constraintName));
                    break;
                case "stagger":
                    bundle.add(TypedValues.MotionType.TYPE_STAGGER, obj.getFloat(constraintName));
                    break;
                case "quantize":
                    CLElement quant = obj.get(constraintName);
                    if (quant instanceof CLArray) {
                        CLArray array = (CLArray) quant;
                        int len = array.size();
                        if (len > 0) {
                            bundle.add(TYPE_QUANTIZE_MOTIONSTEPS, array.getInt(0));
                            if (len > 1) {
                                bundle.add(TYPE_QUANTIZE_INTERPOLATOR_TYPE, array.getString(1));
                                if (len > 2) {
                                    bundle.add(TYPE_QUANTIZE_MOTION_PHASE, array.getFloat(2));
                                }
                            }
                        }
                    } else {
                        bundle.add(TYPE_QUANTIZE_MOTIONSTEPS, obj.getInt(constraintName));
                    }
                    break;
            }
        }
        reference.mMotionProperties = bundle;
    }

    static void parseConstraint(
            State state,
            LayoutVariables layoutVariables,
            CLObject element,
            ConstraintReference reference,
            String constraintName
    ) throws CLParsingException {
        CLArray constraint = element.getArrayOrNull(constraintName);
        if (constraint != null && constraint.size() > 1) {
            String target = constraint.getString(0);
            String anchor = constraint.getStringOrNull(1);
            float margin = 0f;
            float marginGone = 0f;
            if (constraint.size() > 2) {
                CLElement arg2 = constraint.getOrNull(2);
                margin = layoutVariables.get(arg2);
                margin = state.convertDimension(state.getDpToPixel().toPixels(margin));
            }
            if (constraint.size() > 3) {
                CLElement arg2 = constraint.getOrNull(3);
                marginGone = layoutVariables.get(arg2);
                marginGone = state.convertDimension(state.getDpToPixel().toPixels(margin));
            }

            ConstraintReference targetReference = target.equals("parent")
                    ? state.constraints(State.PARENT) :
                    state.constraints(target);

            switch (constraintName) {
                case "circular":
                    float angle = layoutVariables.get(constraint.get(1));
                    reference.circularConstraint(targetReference, angle, 0f);
                    break;
                case "start":
                    switch (anchor) {
                        case "start":
                            reference.startToStart(targetReference);
                            break;
                        case "end":
                            reference.startToEnd(targetReference);
                    }
                    break;
                case "end":
                    switch (anchor) {
                        case "start":
                            reference.endToStart(targetReference);
                            break;
                        case "end":
                            reference.endToEnd(targetReference);
                    }
                    break;
                case "left":

                    switch (anchor) {
                        case "left":
                            reference.leftToLeft(targetReference);
                            break;
                        case "right":
                            reference.leftToRight(targetReference);
                    }
                    break;
                case "right":
                    switch (anchor) {
                        case "left":
                            reference.rightToLeft(targetReference);
                            break;
                        case "right":
                            reference.rightToRight(targetReference);
                    }
                    break;
                case "top":
                    switch (anchor) {
                        case "top":
                            reference.topToTop(targetReference);
                            break;
                        case "bottom":
                            reference.topToBottom(targetReference);
                    }
                    break;
                case "bottom":
                    switch (anchor) {
                        case "top":
                            reference.bottomToTop(targetReference);
                            break;
                        case "bottom":
                            reference.bottomToBottom(targetReference);
                    }
                    break;
                case "baseline":
                    switch (anchor) {
                        case "baseline":
                            state.baselineNeededFor(reference.getKey());
                            state.baselineNeededFor(targetReference.getKey());
                            reference.baselineToBaseline(targetReference);

                            break;
                        case "top":
                            state.baselineNeededFor(reference.getKey());
                            state.baselineNeededFor(targetReference.getKey());
                            reference.baselineToTop(targetReference);

                            break;
                        case "bottom":
                            state.baselineNeededFor(reference.getKey());
                            state.baselineNeededFor(targetReference.getKey());
                            reference.baselineToBottom(targetReference);

                            break;
                    }
            }

            reference.margin(margin).marginGone(marginGone);
        } else {
            String target = element.getStringOrNull(constraintName);
            if (target != null) {
                ConstraintReference targetReference = target.equals("parent")
                        ? state.constraints(State.PARENT) :
                        state.constraints(target);

                switch (constraintName) {
                    case "start":
                        reference.startToStart(targetReference);
                        break;
                    case "end":
                        reference.endToEnd(targetReference);
                        break;
                    case "top":
                        reference.topToTop(targetReference);
                        break;
                    case "bottom":
                        reference.bottomToBottom(targetReference);
                        break;
                    case "baseline":
                        state.baselineNeededFor(reference.getKey());
                        state.baselineNeededFor(targetReference.getKey());
                        reference.baselineToBaseline(targetReference);
                        break;

                }
            }
        }
    }

    static Dimension parseDimensionMode(String dimensionString) {
        Dimension dimension = Dimension.createFixed(0);
        switch (dimensionString) {
            case "wrap":
                dimension = Dimension.createWrap();
                break;
            case "preferWrap":
                dimension = Dimension.createSuggested(Dimension.WRAP_DIMENSION);
                break;
            case "spread":
                dimension = Dimension.createSuggested(Dimension.SPREAD_DIMENSION);
                break;
            case "parent":
                dimension = Dimension.createParent();
                break;
            default: {
                if (dimensionString.endsWith("%")) {
                    // parent percent
                    String percentString =
                            dimensionString.substring(0, dimensionString.indexOf('%'));
                    float percentValue = Float.parseFloat(percentString) / 100f;
                    dimension = Dimension.createPercent(0, percentValue).suggested(0);
                } else if (dimensionString.contains(":")) {
                    dimension = Dimension.createRatio(dimensionString)
                            .suggested(Dimension.SPREAD_DIMENSION);
                }
            }
        }
        return dimension;
    }

    static Dimension parseDimension(CLObject element,
            String constraintName,
            State state,
            CorePixelDp dpToPixels) throws CLParsingException {
        CLElement dimensionElement = element.get(constraintName);
        Dimension dimension = Dimension.createFixed(0);
        if (dimensionElement instanceof CLString) {
            dimension = parseDimensionMode(dimensionElement.content());
        } else if (dimensionElement instanceof CLNumber) {
            dimension = Dimension.createFixed(
                    state.convertDimension(dpToPixels.toPixels(element.getFloat(constraintName))));

        } else if (dimensionElement instanceof CLObject) {
            CLObject obj = (CLObject) dimensionElement;
            String mode = obj.getStringOrNull("value");
            if (mode != null) {
                dimension = parseDimensionMode(mode);
            }

            CLElement minEl = obj.getOrNull("min");
            if (minEl != null) {
                if (minEl instanceof CLNumber) {
                    float min = ((CLNumber) minEl).getFloat();
                    dimension.min(state.convertDimension(dpToPixels.toPixels(min)));
                } else if (minEl instanceof CLString) {
                    dimension.min(Dimension.WRAP_DIMENSION);
                }
            }
            CLElement maxEl = obj.getOrNull("max");
            if (maxEl != null) {
                if (maxEl instanceof CLNumber) {
                    float max = ((CLNumber) maxEl).getFloat();
                    dimension.max(state.convertDimension(dpToPixels.toPixels(max)));
                } else if (maxEl instanceof CLString) {
                    dimension.max(Dimension.WRAP_DIMENSION);
                }
            }
        }
        return dimension;
    }

    /**
     * parse a color string
     *
     * @return -1 if it cannot parse unsigned long
     */
    static long parseColorString(String value) {
        String str = value;
        if (str.startsWith("#")) {
            str = str.substring(1);
            if (str.length() == 6) {
                str = "FF" + str;
            }
            return Long.parseLong(str, 16);
        } else {
            return -1L;
        }
    }

    static String lookForType(CLObject element) throws CLParsingException {
        ArrayList<String> constraints = element.names();
        for (String constraintName : constraints) {
            if (constraintName.equals("type")) {
                return element.getString("type");
            }
        }

        return null;
    }
}