1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.constraintlayout.core.state;
18 
19 import static androidx.constraintlayout.core.motion.utils.TypedValues.MotionType.TYPE_QUANTIZE_INTERPOLATOR_TYPE;
20 import static androidx.constraintlayout.core.motion.utils.TypedValues.MotionType.TYPE_QUANTIZE_MOTIONSTEPS;
21 import static androidx.constraintlayout.core.motion.utils.TypedValues.MotionType.TYPE_QUANTIZE_MOTION_PHASE;
22 import static androidx.constraintlayout.core.widgets.ConstraintWidget.HORIZONTAL;
23 import static androidx.constraintlayout.core.widgets.ConstraintWidget.VERTICAL;
24 
25 import androidx.annotation.RestrictTo;
26 import androidx.constraintlayout.core.motion.utils.TypedBundle;
27 import androidx.constraintlayout.core.motion.utils.TypedValues;
28 import androidx.constraintlayout.core.parser.CLArray;
29 import androidx.constraintlayout.core.parser.CLElement;
30 import androidx.constraintlayout.core.parser.CLKey;
31 import androidx.constraintlayout.core.parser.CLNumber;
32 import androidx.constraintlayout.core.parser.CLObject;
33 import androidx.constraintlayout.core.parser.CLParser;
34 import androidx.constraintlayout.core.parser.CLParsingException;
35 import androidx.constraintlayout.core.parser.CLString;
36 import androidx.constraintlayout.core.state.helpers.BarrierReference;
37 import androidx.constraintlayout.core.state.helpers.ChainReference;
38 import androidx.constraintlayout.core.state.helpers.FlowReference;
39 import androidx.constraintlayout.core.state.helpers.GridReference;
40 import androidx.constraintlayout.core.state.helpers.GuidelineReference;
41 import androidx.constraintlayout.core.widgets.ConstraintWidget;
42 import androidx.constraintlayout.core.widgets.Flow;
43 
44 import org.jspecify.annotations.NonNull;
45 
46 import java.util.ArrayList;
47 import java.util.HashMap;
48 
49 public class ConstraintSetParser {
50 
51     private static final boolean PARSER_DEBUG = false;
52 
53     public static class DesignElement {
54         String mId;
55         String mType;
56         HashMap<String, String> mParams;
57 
getId()58         public String getId() {
59             return mId;
60         }
61 
getType()62         public String getType() {
63             return mType;
64         }
65 
getParams()66         public HashMap<String, String> getParams() {
67             return mParams;
68         }
69 
DesignElement(String id, String type, HashMap<String, String> params)70         DesignElement(String id,
71                       String type,
72                       HashMap<String, String> params) {
73             mId = id;
74             mType = type;
75             mParams = params;
76         }
77     }
78 
79     /**
80      * Provide the storage for managing Variables in the system.
81      * When the json has a variable:{   } section this is used.
82      */
83     public static class LayoutVariables {
84         HashMap<String, Integer> mMargins = new HashMap<>();
85         HashMap<String, GeneratedValue> mGenerators = new HashMap<>();
86         HashMap<String, ArrayList<String>> mArrayIds = new HashMap<>();
87 
put(String elementName, int element)88         void put(String elementName, int element) {
89             mMargins.put(elementName, element);
90         }
91 
put(String elementName, float start, float incrementBy)92         void put(String elementName, float start, float incrementBy) {
93             if (mGenerators.containsKey(elementName)) {
94                 if (mGenerators.get(elementName) instanceof OverrideValue) {
95                     return;
96                 }
97             }
98             mGenerators.put(elementName, new Generator(start, incrementBy));
99         }
100 
put(String elementName, float from, float to, float step, String prefix, String postfix)101         void put(String elementName,
102                  float from,
103                  float to,
104                  float step,
105                  String prefix,
106                  String postfix) {
107             if (mGenerators.containsKey(elementName)) {
108                 if (mGenerators.get(elementName) instanceof OverrideValue) {
109                     return;
110                 }
111             }
112             FiniteGenerator generator =
113                     new FiniteGenerator(from, to, step, prefix, postfix);
114             mGenerators.put(elementName, generator);
115             mArrayIds.put(elementName, generator.array());
116 
117         }
118 
119         /**
120          * insert an override variable
121          *
122          * @param elementName the name
123          * @param value       the value a float
124          */
putOverride(String elementName, float value)125         public void putOverride(String elementName, float value) {
126             GeneratedValue generator = new OverrideValue(value);
127             mGenerators.put(elementName, generator);
128         }
129 
get(Object elementName)130         float get(Object elementName) {
131             if (elementName instanceof CLString) {
132                 String stringValue = ((CLString) elementName).content();
133                 if (mGenerators.containsKey(stringValue)) {
134                     return mGenerators.get(stringValue).value();
135                 }
136                 if (mMargins.containsKey(stringValue)) {
137                     return mMargins.get(stringValue).floatValue();
138                 }
139             } else if (elementName instanceof CLNumber) {
140                 return ((CLNumber) elementName).getFloat();
141             }
142             return 0f;
143         }
144 
getList(String elementName)145         ArrayList<String> getList(String elementName) {
146             if (mArrayIds.containsKey(elementName)) {
147                 return mArrayIds.get(elementName);
148             }
149             return null;
150         }
151 
put(String elementName, ArrayList<String> elements)152         void put(String elementName, ArrayList<String> elements) {
153             mArrayIds.put(elementName, elements);
154         }
155 
156     }
157 
158     interface GeneratedValue {
value()159         float value();
160     }
161 
162     /**
163      * Generate a floating point value
164      */
165     static class Generator implements GeneratedValue {
166         float mStart = 0;
167         float mIncrementBy = 0;
168         float mCurrent = 0;
169         boolean mStop = false;
170 
Generator(float start, float incrementBy)171         Generator(float start, float incrementBy) {
172             mStart = start;
173             mIncrementBy = incrementBy;
174             mCurrent = start;
175         }
176 
177         @Override
value()178         public float value() {
179             if (!mStop) {
180                 mCurrent += mIncrementBy;
181             }
182             return mCurrent;
183         }
184     }
185 
186     /**
187      * Generate values like button1, button2 etc.
188      */
189     static class FiniteGenerator implements GeneratedValue {
190         float mFrom = 0;
191         float mTo = 0;
192         float mStep = 0;
193         boolean mStop = false;
194         String mPrefix;
195         String mPostfix;
196         float mCurrent = 0;
197         float mInitial;
198         float mMax;
199 
FiniteGenerator(float from, float to, float step, String prefix, String postfix)200         FiniteGenerator(float from,
201                         float to,
202                         float step,
203                         String prefix,
204                         String postfix) {
205             mFrom = from;
206             mTo = to;
207             mStep = step;
208             mPrefix = (prefix == null) ? "" : prefix;
209             mPostfix = (postfix == null) ? "" : postfix;
210             mMax = to;
211             mInitial = from;
212         }
213 
214         @Override
value()215         public float value() {
216             if (mCurrent >= mMax) {
217                 mStop = true;
218             }
219             if (!mStop) {
220                 mCurrent += mStep;
221             }
222             return mCurrent;
223         }
224 
array()225         public ArrayList<String> array() {
226             ArrayList<String> array = new ArrayList<>();
227             int value = (int) mInitial;
228             int maxInt = (int) mMax;
229             for (int i = value; i <= maxInt; i++) {
230                 array.add(mPrefix + value + mPostfix);
231                 value += (int) mStep;
232             }
233             return array;
234 
235         }
236 
237     }
238 
239     static class OverrideValue implements GeneratedValue {
240         float mValue;
241 
OverrideValue(float value)242         OverrideValue(float value) {
243             mValue = value;
244         }
245 
246         @Override
value()247         public float value() {
248             return mValue;
249         }
250     }
251 //==================== end store variables =========================
252 //==================== MotionScene =========================
253 
254     public enum MotionLayoutDebugFlags {
255         NONE,
256         SHOW_ALL,
257         UNKNOWN
258     }
259 
260     //==================== end Motion Scene =========================
261 
262     /**
263      * Parse and populate a transition
264      *
265      * @param content    JSON string to parse
266      * @param transition The Transition to be populated
267      * @param state
268      */
parseJSON(String content, Transition transition, int state)269     public static void parseJSON(String content, Transition transition, int state) {
270         try {
271             CLObject json = CLParser.parse(content);
272             ArrayList<String> elements = json.names();
273             if (elements == null) {
274                 return;
275             }
276             for (String elementName : elements) {
277                 CLElement base_element = json.get(elementName);
278                 if (base_element instanceof CLObject) {
279                     CLObject element = (CLObject) base_element;
280                     CLObject customProperties = element.getObjectOrNull("custom");
281                     if (customProperties != null) {
282                         ArrayList<String> properties = customProperties.names();
283                         for (String property : properties) {
284                             CLElement value = customProperties.get(property);
285                             if (value instanceof CLNumber) {
286                                 transition.addCustomFloat(
287                                         state,
288                                         elementName,
289                                         property,
290                                         value.getFloat()
291                                 );
292                             } else if (value instanceof CLString) {
293                                 long color = parseColorString(value.content());
294                                 if (color != -1) {
295                                     transition.addCustomColor(state,
296                                             elementName, property, (int) color);
297                                 }
298                             }
299                         }
300                     }
301                 }
302 
303             }
304         } catch (CLParsingException e) {
305             System.err.println("Error parsing JSON " + e);
306         }
307     }
308 
309     /**
310      * Parse and build a motionScene
311      *
312      * this should be in a MotionScene / MotionSceneParser
313      */
parseMotionSceneJSON(CoreMotionScene scene, String content)314     public static void parseMotionSceneJSON(CoreMotionScene scene, String content) {
315         try {
316             CLObject json = CLParser.parse(content);
317             ArrayList<String> elements = json.names();
318             if (elements == null) {
319                 return;
320             }
321             for (String elementName : elements) {
322                 CLElement element = json.get(elementName);
323                 if (element instanceof CLObject) {
324                     CLObject clObject = (CLObject) element;
325                     switch (elementName) {
326                         case "ConstraintSets":
327                             parseConstraintSets(scene, clObject);
328                             break;
329                         case "Transitions":
330                             parseTransitions(scene, clObject);
331                             break;
332                         case "Header":
333                             parseHeader(scene, clObject);
334                             break;
335                     }
336                 }
337             }
338         } catch (CLParsingException e) {
339             System.err.println("Error parsing JSON " + e);
340         }
341     }
342 
343     /**
344      * Parse ConstraintSets and populate MotionScene
345      */
parseConstraintSets(CoreMotionScene scene, CLObject json)346     static void parseConstraintSets(CoreMotionScene scene,
347                                     CLObject json) throws CLParsingException {
348         ArrayList<String> constraintSetNames = json.names();
349         if (constraintSetNames == null) {
350             return;
351         }
352 
353         for (String csName : constraintSetNames) {
354             CLObject constraintSet = json.getObject(csName);
355             boolean added = false;
356             String ext = constraintSet.getStringOrNull("Extends");
357             if (ext != null && !ext.isEmpty()) {
358                 String base = scene.getConstraintSet(ext);
359                 if (base == null) {
360                     continue;
361                 }
362 
363                 CLObject baseJson = CLParser.parse(base);
364                 ArrayList<String> widgetsOverride = constraintSet.names();
365                 if (widgetsOverride == null) {
366                     continue;
367                 }
368 
369                 for (String widgetOverrideName : widgetsOverride) {
370                     CLElement value = constraintSet.get(widgetOverrideName);
371                     if (value instanceof CLObject) {
372                         override(baseJson, widgetOverrideName, (CLObject) value);
373                     }
374                 }
375 
376                 scene.setConstraintSetContent(csName, baseJson.toJSON());
377                 added = true;
378             }
379             if (!added) {
380                 scene.setConstraintSetContent(csName, constraintSet.toJSON());
381             }
382         }
383 
384     }
385 
override(CLObject baseJson, String name, CLObject overrideValue)386     static void override(CLObject baseJson,
387                          String name, CLObject overrideValue) throws CLParsingException {
388         if (!baseJson.has(name)) {
389             baseJson.put(name, overrideValue);
390         } else {
391             CLObject base = baseJson.getObject(name);
392             ArrayList<String> keys = overrideValue.names();
393             for (String key : keys) {
394                 if (!key.equals("clear")) {
395                     base.put(key, overrideValue.get(key));
396                     continue;
397                 }
398                 CLArray toClear = overrideValue.getArray("clear");
399                 for (int i = 0; i < toClear.size(); i++) {
400                     String clearedKey = toClear.getStringOrNull(i);
401                     if (clearedKey == null) {
402                         continue;
403                     }
404                     switch (clearedKey) {
405                         case "dimensions":
406                             base.remove("width");
407                             base.remove("height");
408                             break;
409                         case "constraints":
410                             base.remove("start");
411                             base.remove("end");
412                             base.remove("top");
413                             base.remove("bottom");
414                             base.remove("baseline");
415                             base.remove("center");
416                             base.remove("centerHorizontally");
417                             base.remove("centerVertically");
418                             break;
419                         case "transforms":
420                             base.remove("visibility");
421                             base.remove("alpha");
422                             base.remove("pivotX");
423                             base.remove("pivotY");
424                             base.remove("rotationX");
425                             base.remove("rotationY");
426                             base.remove("rotationZ");
427                             base.remove("scaleX");
428                             base.remove("scaleY");
429                             base.remove("translationX");
430                             base.remove("translationY");
431                             break;
432                         default:
433                             base.remove(clearedKey);
434 
435                     }
436                 }
437             }
438         }
439     }
440 
441     /**
442      * Parse the Transition
443      */
parseTransitions(CoreMotionScene scene, CLObject json)444     static void parseTransitions(CoreMotionScene scene, CLObject json) throws CLParsingException {
445         ArrayList<String> elements = json.names();
446         if (elements == null) {
447             return;
448         }
449         for (String elementName : elements) {
450             scene.setTransitionContent(elementName, json.getObject(elementName).toJSON());
451         }
452     }
453 
454     /**
455      * Used to parse for "export"
456      */
parseHeader(CoreMotionScene scene, CLObject json)457     static void parseHeader(CoreMotionScene scene, CLObject json) {
458         String name = json.getStringOrNull("export");
459         if (name != null) {
460             scene.setDebugName(name);
461         }
462     }
463 
464     /**
465      * Top leve parsing of the json ConstraintSet supporting
466      * "Variables", "Helpers", "Generate", guidelines, and barriers
467      *
468      * @param content         the JSON string
469      * @param state           the state to populate
470      * @param layoutVariables the variables to override
471      */
parseJSON(String content, State state, LayoutVariables layoutVariables)472     public static void parseJSON(String content, State state,
473                                  LayoutVariables layoutVariables) throws CLParsingException {
474         try {
475             CLObject json = CLParser.parse(content);
476             populateState(json, state, layoutVariables);
477         } catch (CLParsingException e) {
478             System.err.println("Error parsing JSON " + e);
479         }
480     }
481 
482     /**
483      * Populates the given {@link State} with the parameters from {@link CLObject}. Where the
484      * object represents a parsed JSONObject of a ConstraintSet.
485      *
486      * @param parsedJson CLObject of the parsed ConstraintSet
487      * @param state the state to populate
488      * @param layoutVariables the variables to override
489      * @throws CLParsingException when parsing fails
490      */
491     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
populateState( @onNull CLObject parsedJson, @NonNull State state, @NonNull LayoutVariables layoutVariables )492     public static void populateState(
493             @NonNull CLObject parsedJson,
494             @NonNull State state,
495             @NonNull LayoutVariables layoutVariables
496     ) throws CLParsingException {
497         ArrayList<String> elements = parsedJson.names();
498         if (elements == null) {
499             return;
500         }
501         for (String elementName : elements) {
502             CLElement element = parsedJson.get(elementName);
503             if (PARSER_DEBUG) {
504                 System.out.println("[" + elementName + "] = " + element
505                         + " > " + element.getContainer());
506             }
507             switch (elementName) {
508                 case "Variables":
509                     if (element instanceof CLObject) {
510                         parseVariables(state, layoutVariables, (CLObject) element);
511                     }
512                     break;
513                 case "Helpers":
514                     if (element instanceof CLArray) {
515                         parseHelpers(state, layoutVariables, (CLArray) element);
516                     }
517                     break;
518                 case "Generate":
519                     if (element instanceof CLObject) {
520                         parseGenerate(state, layoutVariables, (CLObject) element);
521                     }
522                     break;
523                 default:
524                     if (element instanceof CLObject) {
525                         String type = lookForType((CLObject) element);
526                         if (type != null) {
527                             switch (type) {
528                                 case "hGuideline":
529                                     parseGuidelineParams(
530                                             ConstraintWidget.HORIZONTAL,
531                                             state,
532                                             elementName,
533                                             (CLObject) element
534                                     );
535                                     break;
536                                 case "vGuideline":
537                                     parseGuidelineParams(
538                                             ConstraintWidget.VERTICAL,
539                                             state,
540                                             elementName,
541                                             (CLObject) element
542                                     );
543                                     break;
544                                 case "barrier":
545                                     parseBarrier(state, elementName, (CLObject) element);
546                                     break;
547                                 case "vChain":
548                                 case "hChain":
549                                     parseChainType(
550                                             type,
551                                             state,
552                                             elementName,
553                                             layoutVariables,
554                                             (CLObject) element
555                                     );
556                                     break;
557                                 case "vFlow":
558                                 case "hFlow":
559                                     parseFlowType(
560                                             type,
561                                             state,
562                                             elementName,
563                                             layoutVariables,
564                                             (CLObject) element
565                                     );
566                                     break;
567                                 case "grid":
568                                 case "row":
569                                 case "column":
570                                     parseGridType(
571                                             type,
572                                             state,
573                                             elementName,
574                                             layoutVariables,
575                                             (CLObject) element
576                                     );
577                                     break;
578                             }
579                         } else {
580                             parseWidget(state,
581                                     layoutVariables,
582                                     elementName,
583                                     (CLObject) element);
584                         }
585                     } else if (element instanceof CLNumber) {
586                         layoutVariables.put(elementName, element.getInt());
587                     }
588             }
589         }
590     }
591 
parseVariables(State state, LayoutVariables layoutVariables, CLObject json)592     private static void parseVariables(State state,
593                                        LayoutVariables layoutVariables,
594                                        CLObject json) throws CLParsingException {
595         ArrayList<String> elements = json.names();
596         if (elements == null) {
597             return;
598         }
599         for (String elementName : elements) {
600             CLElement element = json.get(elementName);
601             if (element instanceof CLNumber) {
602                 layoutVariables.put(elementName, element.getInt());
603             } else if (element instanceof CLObject) {
604                 CLObject obj = (CLObject) element;
605                 ArrayList<String> arrayIds;
606                 if (obj.has("from") && obj.has("to")) {
607                     float from = layoutVariables.get(obj.get("from"));
608                     float to = layoutVariables.get(obj.get("to"));
609                     String prefix = obj.getStringOrNull("prefix");
610                     String postfix = obj.getStringOrNull("postfix");
611                     layoutVariables.put(elementName, from, to, 1f, prefix, postfix);
612                 } else if (obj.has("from") && obj.has("step")) {
613                     float start = layoutVariables.get(obj.get("from"));
614                     float increment = layoutVariables.get(obj.get("step"));
615                     layoutVariables.put(elementName, start, increment);
616 
617                 } else if (obj.has("ids")) {
618                     CLArray ids = obj.getArray("ids");
619                     arrayIds = new ArrayList<>();
620                     for (int i = 0; i < ids.size(); i++) {
621                         arrayIds.add(ids.getString(i));
622                     }
623                     layoutVariables.put(elementName, arrayIds);
624                 } else if (obj.has("tag")) {
625                     arrayIds = state.getIdsForTag(obj.getString("tag"));
626                     layoutVariables.put(elementName, arrayIds);
627                 }
628             }
629         }
630     }
631 
632     /**
633      * parse the Design time elements.
634      *
635      * @param content the json
636      * @param list    output the list of design elements
637      */
parseDesignElementsJSON( String content, ArrayList<DesignElement> list)638     public static void parseDesignElementsJSON(
639             String content, ArrayList<DesignElement> list) throws CLParsingException {
640         CLObject json = CLParser.parse(content);
641         ArrayList<String> elements = json.names();
642         if (elements == null) {
643             return;
644         }
645         for (int i = 0; i < elements.size(); i++) {
646             String elementName = elements.get(i);
647             CLElement element = json.get(elementName);
648             if (PARSER_DEBUG) {
649                 System.out.println("[" + element + "] " + element.getClass());
650             }
651             switch (elementName) {
652                 case "Design":
653                     if (!(element instanceof CLObject)) {
654                         return;
655                     }
656                     CLObject obj = (CLObject) element;
657                     elements = obj.names();
658                     for (int j = 0; j < elements.size(); j++) {
659                         String designElementName = elements.get(j);
660                         CLObject designElement =
661                                 (CLObject) ((CLObject) element).get(designElementName);
662                         System.out.printf("element found " + designElementName + "");
663                         String type = designElement.getStringOrNull("type");
664                         if (type != null) {
665                             HashMap<String, String> parameters = new HashMap<String, String>();
666                             int size = designElement.size();
667                             for (int k = 0; k < size; k++) {
668 
669                                 CLKey key = (CLKey) designElement.get(j);
670                                 String paramName = key.content();
671                                 String paramValue = key.getValue().content();
672                                 if (paramValue != null) {
673                                     parameters.put(paramName, paramValue);
674                                 }
675                             }
676                             list.add(new DesignElement(elementName, type, parameters));
677                         }
678                     }
679             }
680             break;
681         }
682     }
683 
parseHelpers(State state, LayoutVariables layoutVariables, CLArray element)684     static void parseHelpers(State state,
685                              LayoutVariables layoutVariables,
686                              CLArray element) throws CLParsingException {
687         for (int i = 0; i < element.size(); i++) {
688             CLElement helper = element.get(i);
689             if (helper instanceof CLArray) {
690                 CLArray array = (CLArray) helper;
691                 if (array.size() > 1) {
692                     switch (array.getString(0)) {
693                         case "hChain":
694                             parseChain(ConstraintWidget.HORIZONTAL, state, layoutVariables, array);
695                             break;
696                         case "vChain":
697                             parseChain(ConstraintWidget.VERTICAL, state, layoutVariables, array);
698                             break;
699                         case "hGuideline":
700                             parseGuideline(ConstraintWidget.HORIZONTAL, state, array);
701                             break;
702                         case "vGuideline":
703                             parseGuideline(ConstraintWidget.VERTICAL, state, array);
704                             break;
705                     }
706                 }
707             }
708         }
709     }
710 
parseGenerate(State state, LayoutVariables layoutVariables, CLObject json)711     static void parseGenerate(State state,
712                               LayoutVariables layoutVariables,
713                               CLObject json) throws CLParsingException {
714         ArrayList<String> elements = json.names();
715         if (elements == null) {
716             return;
717         }
718         for (String elementName : elements) {
719             CLElement element = json.get(elementName);
720             ArrayList<String> arrayIds = layoutVariables.getList(elementName);
721             if (arrayIds != null && element instanceof CLObject) {
722                 for (String id : arrayIds) {
723                     parseWidget(state, layoutVariables, id, (CLObject) element);
724                 }
725             }
726         }
727     }
728 
parseChain(int orientation, State state, LayoutVariables margins, CLArray helper)729     static void parseChain(int orientation, State state,
730                            LayoutVariables margins, CLArray helper) throws CLParsingException {
731         ChainReference chain = (orientation == ConstraintWidget.HORIZONTAL)
732                 ? state.horizontalChain() : state.verticalChain();
733         CLElement refs = helper.get(1);
734         if (!(refs instanceof CLArray) || ((CLArray) refs).size() < 1) {
735             return;
736         }
737         for (int i = 0; i < ((CLArray) refs).size(); i++) {
738             chain.add(((CLArray) refs).getString(i));
739         }
740 
741         if (helper.size() > 2) { // we have additional parameters
742             CLElement params = helper.get(2);
743             if (!(params instanceof CLObject)) {
744                 return;
745             }
746             CLObject obj = (CLObject) params;
747             ArrayList<String> constraints = obj.names();
748             for (String constraintName : constraints) {
749                 switch (constraintName) {
750                     case "style":
751                         CLElement styleObject = ((CLObject) params).get(constraintName);
752                         String styleValue;
753                         if (styleObject instanceof CLArray && ((CLArray) styleObject).size() > 1) {
754                             styleValue = ((CLArray) styleObject).getString(0);
755                             float biasValue = ((CLArray) styleObject).getFloat(1);
756                             chain.bias(biasValue);
757                         } else {
758                             styleValue = styleObject.content();
759                         }
760                         switch (styleValue) {
761                             case "packed":
762                                 chain.style(State.Chain.PACKED);
763                                 break;
764                             case "spread_inside":
765                                 chain.style(State.Chain.SPREAD_INSIDE);
766                                 break;
767                             default:
768                                 chain.style(State.Chain.SPREAD);
769                                 break;
770                         }
771 
772                         break;
773                     default:
774                         parseConstraint(
775                                 state,
776                                 margins,
777                                 (CLObject) params,
778                                 (ConstraintReference) chain,
779                                 constraintName
780                         );
781                         break;
782                 }
783             }
784         }
785     }
786 
toPix(State state, float dp)787     private static float toPix(State state, float dp) {
788         return state.getDpToPixel().toPixels(dp);
789     }
790 
791     /**
792      * Support parsing Chain in the following manner
793      * chainId : {
794      *      type:'hChain'  // or vChain
795      *      contains: ['id1', 'id2', 'id3' ]
796      *      contains: [['id', weight, marginL ,marginR], 'id2', 'id3' ]
797      *      start: ['parent', 'start',0],
798      *      end: ['parent', 'end',0],
799      *      top: ['parent', 'top',0],
800      *      bottom: ['parent', 'bottom',0],
801      *      style: 'spread'
802      * }
803 
804      * @throws CLParsingException
805      */
parseChainType(String orientation, State state, String chainName, LayoutVariables margins, CLObject object)806     private static void parseChainType(String orientation,
807                                        State state,
808                                        String chainName,
809                                        LayoutVariables margins,
810                                        CLObject object) throws CLParsingException {
811 
812         ChainReference chain = (orientation.charAt(0) == 'h')
813                 ? state.horizontalChain() : state.verticalChain();
814         chain.setKey(chainName);
815 
816         for (String params : object.names()) {
817             switch (params) {
818                 case "contains":
819                     CLElement refs = object.get(params);
820                     if (!(refs instanceof CLArray) || ((CLArray) refs).size() < 1) {
821                         System.err.println(
822                                 chainName + " contains should be an array \"" + refs.content()
823                                         + "\"");
824                         return;
825                     }
826                     for (int i = 0; i < ((CLArray) refs).size(); i++) {
827                         CLElement chainElement = ((CLArray) refs).get(i);
828                         if (chainElement instanceof CLArray) {
829                             CLArray array = (CLArray) chainElement;
830                             if (array.size() > 0) {
831                                 String id = array.get(0).content();
832                                 float weight = Float.NaN;
833                                 float preMargin = Float.NaN;
834                                 float postMargin = Float.NaN;
835                                 float preGoneMargin = Float.NaN;
836                                 float postGoneMargin = Float.NaN;
837                                 switch (array.size()) {
838                                     case 2: // sets only the weight
839                                         weight = array.getFloat(1);
840                                         break;
841                                     case 3: // sets the pre and post margin to the 2 arg
842                                         weight = array.getFloat(1);
843                                         postMargin = preMargin = toPix(state, array.getFloat(2));
844                                         break;
845                                     case 4: // sets the pre and post margin
846                                         weight = array.getFloat(1);
847                                         preMargin = toPix(state, array.getFloat(2));
848                                         postMargin = toPix(state, array.getFloat(3));
849                                         break;
850                                     case 6: // weight, preMargin, postMargin, preGoneMargin,
851                                         // postGoneMargin
852                                         weight = array.getFloat(1);
853                                         preMargin = toPix(state, array.getFloat(2));
854                                         postMargin = toPix(state, array.getFloat(3));
855                                         preGoneMargin = toPix(state, array.getFloat(4));
856                                         postGoneMargin = toPix(state, array.getFloat(5));
857                                         break;
858                                 }
859                                 chain.addChainElement(id,
860                                         weight,
861                                         preMargin,
862                                         postMargin,
863                                         preGoneMargin,
864                                         postGoneMargin);
865                             }
866                         } else {
867                             chain.add(chainElement.content());
868                         }
869                     }
870                     break;
871                 case "start":
872                 case "end":
873                 case "top":
874                 case "bottom":
875                 case "left":
876                 case "right":
877                     parseConstraint(state, margins, object, chain, params);
878                     break;
879                 case "style":
880 
881                     CLElement styleObject = object.get(params);
882                     String styleValue;
883                     if (styleObject instanceof CLArray && ((CLArray) styleObject).size() > 1) {
884                         styleValue = ((CLArray) styleObject).getString(0);
885                         float biasValue = ((CLArray) styleObject).getFloat(1);
886                         chain.bias(biasValue);
887                     } else {
888                         styleValue = styleObject.content();
889                     }
890                     switch (styleValue) {
891                         case "packed":
892                             chain.style(State.Chain.PACKED);
893                             break;
894                         case "spread_inside":
895                             chain.style(State.Chain.SPREAD_INSIDE);
896                             break;
897                         default:
898                             chain.style(State.Chain.SPREAD);
899                             break;
900                     }
901 
902                     break;
903             }
904         }
905     }
906 
907     /**
908      * Support parsing Grid in the following manner
909      * chainId : {
910      *      height: "parent",
911      *      width: "parent",
912      *      type: "Grid",
913      *      vGap: 10,
914      *      hGap: 10,
915      *      orientation: 0,
916      *      rows: 0,
917      *      columns: 1,
918      *      columnWeights: "",
919      *      rowWeights: "",
920      *      contains: ["btn1", "btn2", "btn3", "btn4"],
921      *      top: ["parent", "top", 10],
922      *      bottom: ["parent", "bottom", 20],
923      *      right: ["parent", "right", 30],
924      *      left: ["parent", "left", 40],
925      * }
926      *
927      * @param gridType type of the Grid helper could be "Grid"|"Row"|"Column"
928      * @param state ConstraintLayout State
929      * @param name the name of the Grid Helper
930      * @param layoutVariables layout margins
931      * @param element the element to be parsed
932      * @throws CLParsingException
933      */
parseGridType(String gridType, State state, String name, LayoutVariables layoutVariables, CLObject element)934     private static void parseGridType(String gridType,
935                                       State state,
936                                       String name,
937                                       LayoutVariables layoutVariables,
938                                       CLObject element) throws CLParsingException {
939         GridReference grid = state.getGrid(name, gridType);
940 
941         for (String param : element.names()) {
942             switch (param) {
943                 case "contains":
944                     CLArray list = element.getArrayOrNull(param);
945                     if (list != null) {
946                         for (int j = 0; j < list.size(); j++) {
947 
948                             String elementNameReference = list.get(j).content();
949                             ConstraintReference elementReference =
950                                     state.constraints(elementNameReference);
951                             grid.add(elementReference);
952                         }
953                     }
954                     break;
955                 case "orientation":
956                     int orientation = element.get(param).getInt();
957                     grid.setOrientation(orientation);
958                     break;
959                 case "rows":
960                     int rows = element.get(param).getInt();
961                     if (rows > 0) {
962                         grid.setRowsSet(rows);
963                     }
964                     break;
965                 case "columns":
966                     int columns = element.get(param).getInt();
967                     if (columns > 0) {
968                         grid.setColumnsSet(columns);
969                     }
970                     break;
971                 case "hGap":
972                     float hGap = element.get(param).getFloat();
973                     grid.setHorizontalGaps(toPix(state, hGap));
974                     break;
975                 case "vGap":
976                     float vGap = element.get(param).getFloat();
977                     grid.setVerticalGaps(toPix(state, vGap));
978                     break;
979                 case "spans":
980                     String spans = element.get(param).content();
981                     if (spans != null && spans.contains(":")) {
982                         grid.setSpans(spans);
983                     }
984                     break;
985                 case "skips":
986                     String skips = element.get(param).content();
987                     if (skips != null && skips.contains(":")) {
988                         grid.setSkips(skips);
989                     }
990                     break;
991                 case "rowWeights":
992                     String rowWeights = element.get(param).content();
993                     if (rowWeights != null && rowWeights.contains(",")) {
994                         grid.setRowWeights(rowWeights);
995                     }
996                     break;
997                 case "columnWeights":
998                     String columnWeights = element.get(param).content();
999                     if (columnWeights != null && columnWeights.contains(",")) {
1000                         grid.setColumnWeights(columnWeights);
1001                     }
1002                     break;
1003                 case "padding":
1004                     // Note that padding is currently not properly handled in GridCore
1005                     CLElement paddingObject = element.get(param);
1006                     float paddingStart = 0;
1007                     float paddingTop = 0;
1008                     float paddingEnd = 0;
1009                     float paddingBottom = 0;
1010                     if (paddingObject instanceof CLArray && ((CLArray) paddingObject).size() > 1) {
1011                         paddingStart = ((CLArray) paddingObject).getInt(0);
1012                         paddingEnd = paddingStart;
1013                         paddingTop = ((CLArray) paddingObject).getInt(1);
1014                         paddingBottom = paddingTop;
1015                         if (((CLArray) paddingObject).size() > 2) {
1016                             paddingEnd = ((CLArray) paddingObject).getInt(2);
1017                             try {
1018                                 paddingBottom = ((CLArray) paddingObject).getInt(3);
1019                             } catch (ArrayIndexOutOfBoundsException e) {
1020                                 paddingBottom = 0;
1021                             }
1022 
1023                         }
1024                     } else {
1025                         paddingStart = paddingObject.getInt();
1026                         paddingTop = paddingStart;
1027                         paddingEnd = paddingStart;
1028                         paddingBottom = paddingStart;
1029                     }
1030                     grid.setPaddingStart(Math.round(toPix(state, paddingStart)));
1031                     grid.setPaddingTop(Math.round(toPix(state, paddingTop)));
1032                     grid.setPaddingEnd(Math.round(toPix(state, paddingEnd)));
1033                     grid.setPaddingBottom(Math.round(toPix(state, paddingBottom)));
1034                     break;
1035                 case "flags":
1036                     int flagValue = 0;
1037                     String flags = "";
1038                     try {
1039                         CLElement obj  = element.get(param);
1040                         if (obj instanceof CLNumber) {
1041                             flagValue = obj.getInt();
1042                         } else {
1043                             flags = obj.content();
1044                         }
1045                     } catch (Exception ex) {
1046                         System.err.println("Error parsing grid flags " + ex);
1047                     }
1048 
1049                     if (flags != null && !flags.isEmpty()) {
1050                         // In older APIs, the flags may still be defined as a String
1051                         grid.setFlags(flags);
1052                     } else {
1053                         grid.setFlags(flagValue);
1054                     }
1055                     break;
1056                 default:
1057                     ConstraintReference reference = state.constraints(name);
1058                     applyAttribute(state, layoutVariables, reference, element, param);
1059             }
1060         }
1061     }
1062 
1063     /**
1064      * It's used to parse the Flow type of Helper with the following format:
1065      * flowID: {
1066      *    type: 'hFlow'|'vFlow’
1067      *    wrap: 'chain'|'none'|'aligned',
1068      *    contains: ['id1', 'id2', 'id3' ] |
1069      *              [['id1', weight, preMargin , postMargin], 'id2', 'id3'],
1070      *    vStyle: 'spread'|'spread_inside'|'packed' | ['first', 'middle', 'last'],
1071      *    hStyle: 'spread'|'spread_inside'|'packed' | ['first', 'middle', 'last'],
1072      *    vAlign: 'top'|'bottom'|'baseline'|'center',
1073      *    hAlign: 'start'|'end'|'center',
1074      *    vGap: 32,
1075      *    hGap: 23,
1076      *    padding: 32,
1077      *    maxElement: 5,
1078      *    vBias: 0.3 | [0.0, 0.5, 0.5],
1079      *    hBias: 0.4 | [0.0, 0.5, 0.5],
1080      *    start: ['parent', 'start', 0],
1081      *    end: ['parent', 'end', 0],
1082      *    top: ['parent', 'top', 0],
1083      *    bottom: ['parent', 'bottom', 0],
1084      * }
1085      *
1086      * @param flowType orientation of the Flow Helper
1087      * @param state ConstraintLayout State
1088      * @param flowName the name of the Flow Helper
1089      * @param layoutVariables layout margins
1090      * @param element the element to be parsed
1091      * @throws CLParsingException
1092      */
parseFlowType(String flowType, State state, String flowName, LayoutVariables layoutVariables, CLObject element)1093     private static void parseFlowType(String flowType,
1094                                       State state,
1095                                       String flowName,
1096                                       LayoutVariables layoutVariables,
1097                                       CLObject element) throws CLParsingException {
1098         boolean isVertical = flowType.charAt(0) == 'v';
1099         FlowReference flow = state.getFlow(flowName, isVertical);
1100 
1101         for (String param : element.names()) {
1102             switch (param) {
1103                 case "contains":
1104                     CLElement refs = element.get(param);
1105                     if (!(refs instanceof CLArray) || ((CLArray) refs).size() < 1) {
1106                         System.err.println(
1107                                 flowName + " contains should be an array \"" + refs.content()
1108                                         + "\"");
1109                         return;
1110                     }
1111                     for (int i = 0; i < ((CLArray) refs).size(); i++) {
1112                         CLElement chainElement = ((CLArray) refs).get(i);
1113                         if (chainElement instanceof CLArray) {
1114                             CLArray array = (CLArray) chainElement;
1115                             if (array.size() > 0) {
1116                                 String id = array.get(0).content();
1117                                 float weight = Float.NaN;
1118                                 float preMargin = Float.NaN;
1119                                 float postMargin = Float.NaN;
1120                                 switch (array.size()) {
1121                                     case 2: // sets only the weight
1122                                         weight = array.getFloat(1);
1123                                         break;
1124                                     case 3: // sets the pre and post margin to the 2 arg
1125                                         weight = array.getFloat(1);
1126                                         postMargin = preMargin = toPix(state, array.getFloat(2));
1127                                         break;
1128                                     case 4: // sets the pre and post margin
1129                                         weight = array.getFloat(1);
1130                                         preMargin = toPix(state, array.getFloat(2));
1131                                         postMargin = toPix(state, array.getFloat(3));
1132                                         break;
1133                                 }
1134                                 flow.addFlowElement(id, weight, preMargin, postMargin);
1135                             }
1136                         } else {
1137                             flow.add(chainElement.content());
1138                         }
1139                     }
1140                     break;
1141                 case "type":
1142                     if (element.get(param).content().equals("hFlow")) {
1143                         flow.setOrientation(HORIZONTAL);
1144                     } else {
1145                         flow.setOrientation(VERTICAL);
1146                     }
1147                     break;
1148                 case "wrap":
1149                     String wrapValue = element.get(param).content();
1150                     flow.setWrapMode(State.Wrap.getValueByString(wrapValue));
1151                     break;
1152                 case "vGap":
1153                     int vGapValue = element.get(param).getInt();
1154                     flow.setVerticalGap(vGapValue);
1155                     break;
1156                 case "hGap":
1157                     int hGapValue = element.get(param).getInt();
1158                     flow.setHorizontalGap(hGapValue);
1159                     break;
1160                 case "maxElement":
1161                     int maxElementValue = element.get(param).getInt();
1162                     flow.setMaxElementsWrap(maxElementValue);
1163                     break;
1164                 case "padding":
1165                     CLElement paddingObject = element.get(param);
1166                     float paddingLeft = 0;
1167                     float paddingTop = 0;
1168                     float paddingRight = 0;
1169                     float paddingBottom = 0;
1170                     if (paddingObject instanceof CLArray && ((CLArray) paddingObject).size() > 1) {
1171                         paddingLeft = ((CLArray) paddingObject).getInt(0);
1172                         paddingRight = paddingLeft;
1173                         paddingTop = ((CLArray) paddingObject).getInt(1);
1174                         paddingBottom = paddingTop;
1175                         if (((CLArray) paddingObject).size() > 2) {
1176                             paddingRight = ((CLArray) paddingObject).getInt(2);
1177                             try {
1178                                 paddingBottom = ((CLArray) paddingObject).getInt(3);
1179                             } catch (ArrayIndexOutOfBoundsException e) {
1180                                 paddingBottom = 0;
1181                             }
1182 
1183                         }
1184                     } else {
1185                         paddingLeft = paddingObject.getInt();
1186                         paddingTop = paddingLeft;
1187                         paddingRight = paddingLeft;
1188                         paddingBottom = paddingLeft;
1189                     }
1190                     flow.setPaddingLeft(Math.round(toPix(state, paddingLeft)));
1191                     flow.setPaddingTop(Math.round(toPix(state, paddingTop)));
1192                     flow.setPaddingRight(Math.round(toPix(state, paddingRight)));
1193                     flow.setPaddingBottom(Math.round(toPix(state, paddingBottom)));
1194                     break;
1195                 case "vAlign":
1196                     String vAlignValue = element.get(param).content();
1197                     switch (vAlignValue) {
1198                         case "top":
1199                             flow.setVerticalAlign(Flow.VERTICAL_ALIGN_TOP);
1200                             break;
1201                         case "bottom":
1202                             flow.setVerticalAlign(Flow.VERTICAL_ALIGN_BOTTOM);
1203                             break;
1204                         case "baseline":
1205                             flow.setVerticalAlign(Flow.VERTICAL_ALIGN_BASELINE);
1206                             break;
1207                         default:
1208                             flow.setVerticalAlign(Flow.VERTICAL_ALIGN_CENTER);
1209                             break;
1210                     }
1211                     break;
1212                 case "hAlign":
1213                     String hAlignValue = element.get(param).content();
1214                     switch (hAlignValue) {
1215                         case "start":
1216                             flow.setHorizontalAlign(Flow.HORIZONTAL_ALIGN_START);
1217                             break;
1218                         case "end":
1219                             flow.setHorizontalAlign(Flow.HORIZONTAL_ALIGN_END);
1220                             break;
1221                         default:
1222                             flow.setHorizontalAlign(Flow.HORIZONTAL_ALIGN_CENTER);
1223                             break;
1224                     }
1225                     break;
1226                 case "vFlowBias":
1227                     CLElement vBiasObject = element.get(param);
1228                     Float vBiasValue = 0.5f;
1229                     Float vFirstBiasValue = 0.5f;
1230                     Float vLastBiasValue = 0.5f;
1231                     if (vBiasObject instanceof CLArray && ((CLArray) vBiasObject).size() > 1) {
1232                         vFirstBiasValue = ((CLArray) vBiasObject).getFloat(0);
1233                         vBiasValue = ((CLArray) vBiasObject).getFloat(1);
1234                         if (((CLArray) vBiasObject).size() > 2) {
1235                             vLastBiasValue = ((CLArray) vBiasObject).getFloat(2);
1236                         }
1237                     } else {
1238                         vBiasValue = vBiasObject.getFloat();
1239                     }
1240                     try {
1241                         flow.verticalBias(vBiasValue);
1242                         if (vFirstBiasValue != 0.5f) {
1243                             flow.setFirstVerticalBias(vFirstBiasValue);
1244                         }
1245                         if (vLastBiasValue != 0.5f) {
1246                             flow.setLastVerticalBias(vLastBiasValue);
1247                         }
1248                     } catch(NumberFormatException e) {
1249 
1250                     }
1251                     break;
1252                 case "hFlowBias":
1253                     CLElement hBiasObject = element.get(param);
1254                     Float hBiasValue = 0.5f;
1255                     Float hFirstBiasValue = 0.5f;
1256                     Float hLastBiasValue = 0.5f;
1257                     if (hBiasObject instanceof CLArray && ((CLArray) hBiasObject).size() > 1) {
1258                         hFirstBiasValue = ((CLArray) hBiasObject).getFloat(0);
1259                         hBiasValue = ((CLArray) hBiasObject).getFloat(1);
1260                         if (((CLArray) hBiasObject).size() > 2) {
1261                             hLastBiasValue = ((CLArray) hBiasObject).getFloat(2);
1262                         }
1263                     } else {
1264                         hBiasValue = hBiasObject.getFloat();
1265                     }
1266                     try {
1267                         flow.horizontalBias(hBiasValue);
1268                         if (hFirstBiasValue != 0.5f) {
1269                             flow.setFirstHorizontalBias(hFirstBiasValue);
1270                         }
1271                         if (hLastBiasValue != 0.5f) {
1272                             flow.setLastHorizontalBias(hLastBiasValue);
1273                         }
1274                     } catch(NumberFormatException e) {
1275 
1276                     }
1277                     break;
1278                 case "vStyle":
1279                     CLElement vStyleObject = element.get(param);
1280                     String vStyleValueStr = "";
1281                     String vFirstStyleValueStr = "";
1282                     String vLastStyleValueStr = "";
1283                     if (vStyleObject instanceof CLArray && ((CLArray) vStyleObject).size() > 1) {
1284                         vFirstStyleValueStr = ((CLArray) vStyleObject).getString(0);
1285                         vStyleValueStr = ((CLArray) vStyleObject).getString(1);
1286                         if (((CLArray) vStyleObject).size() > 2) {
1287                             vLastStyleValueStr = ((CLArray) vStyleObject).getString(2);
1288                         }
1289                     } else {
1290                         vStyleValueStr = vStyleObject.content();
1291                     }
1292 
1293                     if (!vStyleValueStr.equals("")) {
1294                         flow.setVerticalStyle(State.Chain.getValueByString(vStyleValueStr));
1295                     }
1296                     if (!vFirstStyleValueStr.equals("")) {
1297                         flow.setFirstVerticalStyle(
1298                                 State.Chain.getValueByString(vFirstStyleValueStr));
1299                     }
1300                     if (!vLastStyleValueStr.equals("")) {
1301                         flow.setLastVerticalStyle(State.Chain.getValueByString(vLastStyleValueStr));
1302                     }
1303                     break;
1304                 case "hStyle":
1305                     CLElement hStyleObject = element.get(param);
1306                     String hStyleValueStr = "";
1307                     String hFirstStyleValueStr = "";
1308                     String hLastStyleValueStr = "";
1309                     if (hStyleObject instanceof CLArray && ((CLArray) hStyleObject).size() > 1) {
1310                         hFirstStyleValueStr = ((CLArray) hStyleObject).getString(0);
1311                         hStyleValueStr = ((CLArray) hStyleObject).getString(1);
1312                         if (((CLArray) hStyleObject).size() > 2) {
1313                             hLastStyleValueStr = ((CLArray) hStyleObject).getString(2);
1314                         }
1315                     } else {
1316                         hStyleValueStr = hStyleObject.content();
1317                     }
1318 
1319                     if (!hStyleValueStr.equals("")) {
1320                         flow.setHorizontalStyle(State.Chain.getValueByString(hStyleValueStr));
1321                     }
1322                     if (!hFirstStyleValueStr.equals("")) {
1323                         flow.setFirstHorizontalStyle(
1324                                 State.Chain.getValueByString(hFirstStyleValueStr));
1325                     }
1326                     if (!hLastStyleValueStr.equals("")) {
1327                         flow.setLastHorizontalStyle(
1328                                 State.Chain.getValueByString(hLastStyleValueStr));
1329                     }
1330                     break;
1331                 default:
1332                     // Get the underlying reference for the flow, apply the constraints
1333                     // attributes to it
1334                     ConstraintReference reference = state.constraints(flowName);
1335                     applyAttribute(state, layoutVariables, reference, element, param);
1336             }
1337         }
1338     }
1339 
parseGuideline(int orientation, State state, CLArray helper)1340     static void parseGuideline(int orientation,
1341                                State state, CLArray helper) throws CLParsingException {
1342         CLElement params = helper.get(1);
1343         if (!(params instanceof CLObject)) {
1344             return;
1345         }
1346         String guidelineId = ((CLObject) params).getStringOrNull("id");
1347         if (guidelineId == null) return;
1348         parseGuidelineParams(orientation, state, guidelineId, (CLObject) params);
1349     }
1350 
parseGuidelineParams( int orientation, State state, String guidelineId, CLObject params )1351     static void parseGuidelineParams(
1352             int orientation,
1353             State state,
1354             String guidelineId,
1355             CLObject params
1356     ) throws CLParsingException {
1357         ArrayList<String> constraints = params.names();
1358         if (constraints == null) return;
1359         ConstraintReference reference = state.constraints(guidelineId);
1360 
1361         if (orientation == ConstraintWidget.HORIZONTAL) {
1362             state.horizontalGuideline(guidelineId);
1363         } else {
1364             state.verticalGuideline(guidelineId);
1365         }
1366 
1367         // Layout direction may be ignored for Horizontal guidelines (placed along the Y axis),
1368         // since `start` & `end` represent the `top` and `bottom` distances respectively.
1369         boolean isLtr = !state.isRtl() || orientation == ConstraintWidget.HORIZONTAL;
1370 
1371         GuidelineReference guidelineReference = (GuidelineReference) reference.getFacade();
1372 
1373         // Whether the guideline is based on percentage or distance
1374         boolean isPercent = false;
1375 
1376         // Percent or distance value of the guideline
1377         float value = 0f;
1378 
1379         // Indicates if the value is considered from the "start" position,
1380         // meaning "left" anchor for vertical guidelines and "top" anchor for
1381         // horizontal guidelines
1382         boolean fromStart = true;
1383         for (String constraintName : constraints) {
1384             switch (constraintName) {
1385                 // left and right are here just to support LTR independent vertical guidelines
1386                 case "left":
1387                     value = toPix(state, params.getFloat(constraintName));
1388                     fromStart = true;
1389                     break;
1390                 case "right":
1391                     value = toPix(state, params.getFloat(constraintName));
1392                     fromStart = false;
1393                     break;
1394                 case "start":
1395                     value = toPix(state, params.getFloat(constraintName));
1396                     fromStart = isLtr;
1397                     break;
1398                 case "end":
1399                     value = toPix(state, params.getFloat(constraintName));
1400                     fromStart = !isLtr;
1401                     break;
1402                 case "percent":
1403                     isPercent = true;
1404                     CLArray percentParams = params.getArrayOrNull(constraintName);
1405                     if (percentParams == null) {
1406                         fromStart = true;
1407                         value = params.getFloat(constraintName);
1408                     } else if (percentParams.size() > 1) {
1409                         String origin = percentParams.getString(0);
1410                         value = percentParams.getFloat(1);
1411                         switch (origin) {
1412                             case "left":
1413                                 fromStart = true;
1414                                 break;
1415                             case "right":
1416                                 fromStart = false;
1417                                 break;
1418                             case "start":
1419                                 fromStart = isLtr;
1420                                 break;
1421                             case "end":
1422                                 fromStart = !isLtr;
1423                                 break;
1424                         }
1425                     }
1426                     break;
1427             }
1428         }
1429 
1430         // Populate the guideline based on the resolved properties
1431         if (isPercent) {
1432             if (fromStart) {
1433                 guidelineReference.percent(value);
1434             } else {
1435                 guidelineReference.percent(1f - value);
1436             }
1437         } else {
1438             if (fromStart) {
1439                 guidelineReference.start(value);
1440             } else {
1441                 guidelineReference.end(value);
1442             }
1443         }
1444     }
1445 
parseBarrier( State state, String elementName, CLObject element )1446     static void parseBarrier(
1447             State state,
1448             String elementName, CLObject element
1449     ) throws CLParsingException {
1450         boolean isLtr = !state.isRtl();
1451         BarrierReference reference = state.barrier(elementName, State.Direction.END);
1452         ArrayList<String> constraints = element.names();
1453         if (constraints == null) {
1454             return;
1455         }
1456         for (String constraintName : constraints) {
1457             switch (constraintName) {
1458                 case "direction": {
1459                     switch (element.getString(constraintName)) {
1460                         case "start":
1461                             if (isLtr) {
1462                                 reference.setBarrierDirection(State.Direction.LEFT);
1463                             } else {
1464                                 reference.setBarrierDirection(State.Direction.RIGHT);
1465                             }
1466                             break;
1467                         case "end":
1468                             if (isLtr) {
1469                                 reference.setBarrierDirection(State.Direction.RIGHT);
1470                             } else {
1471                                 reference.setBarrierDirection(State.Direction.LEFT);
1472                             }
1473                             break;
1474                         case "left":
1475                             reference.setBarrierDirection(State.Direction.LEFT);
1476                             break;
1477                         case "right":
1478                             reference.setBarrierDirection(State.Direction.RIGHT);
1479                             break;
1480                         case "top":
1481                             reference.setBarrierDirection(State.Direction.TOP);
1482                             break;
1483                         case "bottom":
1484                             reference.setBarrierDirection(State.Direction.BOTTOM);
1485                             break;
1486                     }
1487                 }
1488                 break;
1489                 case "margin":
1490                     float margin = element.getFloatOrNaN(constraintName);
1491                     if (!Float.isNaN(margin)) {
1492                         reference.margin(toPix(state, margin));
1493                     }
1494                     break;
1495                 case "contains":
1496                     CLArray list = element.getArrayOrNull(constraintName);
1497                     if (list != null) {
1498                         for (int j = 0; j < list.size(); j++) {
1499 
1500                             String elementNameReference = list.get(j).content();
1501                             ConstraintReference elementReference =
1502                                     state.constraints(elementNameReference);
1503                             if (PARSER_DEBUG) {
1504                                 System.out.println(
1505                                         "Add REFERENCE "
1506                                                 + "($elementNameReference = $elementReference) "
1507                                                 + "TO BARRIER "
1508                                 );
1509                             }
1510                             reference.add(elementReference);
1511                         }
1512                     }
1513                     break;
1514             }
1515         }
1516     }
1517 
parseWidget( State state, LayoutVariables layoutVariables, String elementName, CLObject element )1518     static void parseWidget(
1519             State state,
1520             LayoutVariables layoutVariables,
1521             String elementName,
1522             CLObject element
1523     ) throws CLParsingException {
1524         ConstraintReference reference = state.constraints(elementName);
1525         parseWidget(state, layoutVariables, reference, element);
1526     }
1527 
1528     /**
1529      * Set/apply attribute to a widget/helper reference
1530      *
1531      * @param state Constraint State
1532      * @param layoutVariables layout variables
1533      * @param reference widget/helper reference
1534      * @param element the parsed CLObject
1535      * @param attributeName Name of the attribute to be set/applied
1536      * @throws CLParsingException
1537      */
applyAttribute( State state, LayoutVariables layoutVariables, ConstraintReference reference, CLObject element, String attributeName)1538     static void applyAttribute(
1539             State state,
1540             LayoutVariables layoutVariables,
1541             ConstraintReference reference,
1542             CLObject element,
1543             String attributeName) throws CLParsingException {
1544 
1545         float value;
1546         switch (attributeName) {
1547             case "width":
1548                 reference.setWidth(parseDimension(element,
1549                         attributeName, state, state.getDpToPixel()));
1550                 break;
1551             case "height":
1552                 reference.setHeight(parseDimension(element,
1553                         attributeName, state, state.getDpToPixel()));
1554                 break;
1555             case "center":
1556                 String target = element.getString(attributeName);
1557 
1558                 ConstraintReference targetReference;
1559                 if (target.equals("parent")) {
1560                     targetReference = state.constraints(State.PARENT);
1561                 } else {
1562                     targetReference = state.constraints(target);
1563                 }
1564                 reference.startToStart(targetReference);
1565                 reference.endToEnd(targetReference);
1566                 reference.topToTop(targetReference);
1567                 reference.bottomToBottom(targetReference);
1568                 break;
1569             case "centerHorizontally":
1570                 target = element.getString(attributeName);
1571                 targetReference = target.equals("parent")
1572                         ? state.constraints(State.PARENT) : state.constraints(target);
1573 
1574                 reference.startToStart(targetReference);
1575                 reference.endToEnd(targetReference);
1576                 break;
1577             case "centerVertically":
1578                 target = element.getString(attributeName);
1579                 targetReference = target.equals("parent")
1580                         ? state.constraints(State.PARENT) : state.constraints(target);
1581 
1582                 reference.topToTop(targetReference);
1583                 reference.bottomToBottom(targetReference);
1584                 break;
1585             case "alpha":
1586                 value = layoutVariables.get(element.get(attributeName));
1587                 reference.alpha(value);
1588                 break;
1589             case "scaleX":
1590                 value = layoutVariables.get(element.get(attributeName));
1591                 reference.scaleX(value);
1592                 break;
1593             case "scaleY":
1594                 value = layoutVariables.get(element.get(attributeName));
1595                 reference.scaleY(value);
1596                 break;
1597             case "translationX":
1598                 value = layoutVariables.get(element.get(attributeName));
1599                 reference.translationX(toPix(state, value));
1600                 break;
1601             case "translationY":
1602                 value = layoutVariables.get(element.get(attributeName));
1603                 reference.translationY(toPix(state, value));
1604                 break;
1605             case "translationZ":
1606                 value = layoutVariables.get(element.get(attributeName));
1607                 reference.translationZ(toPix(state, value));
1608                 break;
1609             case "pivotX":
1610                 value = layoutVariables.get(element.get(attributeName));
1611                 reference.pivotX(value);
1612                 break;
1613             case "pivotY":
1614                 value = layoutVariables.get(element.get(attributeName));
1615                 reference.pivotY(value);
1616                 break;
1617             case "rotationX":
1618                 value = layoutVariables.get(element.get(attributeName));
1619                 reference.rotationX(value);
1620                 break;
1621             case "rotationY":
1622                 value = layoutVariables.get(element.get(attributeName));
1623                 reference.rotationY(value);
1624                 break;
1625             case "rotationZ":
1626                 value = layoutVariables.get(element.get(attributeName));
1627                 reference.rotationZ(value);
1628                 break;
1629             case "visibility":
1630                 switch (element.getString(attributeName)) {
1631                     case "visible":
1632                         reference.visibility(ConstraintWidget.VISIBLE);
1633                         break;
1634                     case "invisible":
1635                         reference.visibility(ConstraintWidget.INVISIBLE);
1636                         reference.alpha(0f);
1637                         break;
1638                     case "gone":
1639                         reference.visibility(ConstraintWidget.GONE);
1640                         break;
1641                 }
1642                 break;
1643             case "vBias":
1644                 value = layoutVariables.get(element.get(attributeName));
1645                 reference.verticalBias(value);
1646                 break;
1647             case "hRtlBias":
1648                 // TODO: This is a temporary solution to support bias with start/end constraints,
1649                 //  where the bias needs to be reversed in RTL, we probably want a better or more
1650                 //  intuitive way to do this
1651                 value = layoutVariables.get(element.get(attributeName));
1652                 if (state.isRtl()) {
1653                     value = 1f - value;
1654                 }
1655                 reference.horizontalBias(value);
1656                 break;
1657             case "hBias":
1658                 value = layoutVariables.get(element.get(attributeName));
1659                 reference.horizontalBias(value);
1660                 break;
1661             case "vWeight":
1662                 value = layoutVariables.get(element.get(attributeName));
1663                 reference.setVerticalChainWeight(value);
1664                 break;
1665             case "hWeight":
1666                 value = layoutVariables.get(element.get(attributeName));
1667                 reference.setHorizontalChainWeight(value);
1668                 break;
1669             case "custom":
1670                 parseCustomProperties(element, reference, attributeName);
1671                 break;
1672             case "motion":
1673                 parseMotionProperties(element.get(attributeName), reference);
1674                 break;
1675             default:
1676                 parseConstraint(state, layoutVariables, element, reference, attributeName);
1677         }
1678     }
1679 
parseWidget( State state, LayoutVariables layoutVariables, ConstraintReference reference, CLObject element )1680     static void parseWidget(
1681             State state,
1682             LayoutVariables layoutVariables,
1683             ConstraintReference reference,
1684             CLObject element
1685     ) throws CLParsingException {
1686         if (reference.getWidth() == null) {
1687             // Default to Wrap when the Dimension has not been assigned
1688             reference.setWidth(Dimension.createWrap());
1689         }
1690         if (reference.getHeight() == null) {
1691             // Default to Wrap when the Dimension has not been assigned
1692             reference.setHeight(Dimension.createWrap());
1693         }
1694         ArrayList<String> constraints = element.names();
1695         if (constraints == null) {
1696             return;
1697         }
1698         for (String constraintName : constraints) {
1699             applyAttribute(state, layoutVariables, reference, element, constraintName);
1700         }
1701     }
1702 
parseCustomProperties( CLObject element, ConstraintReference reference, String constraintName )1703     static void parseCustomProperties(
1704             CLObject element,
1705             ConstraintReference reference,
1706             String constraintName
1707     ) throws CLParsingException {
1708         CLObject json = element.getObjectOrNull(constraintName);
1709         if (json == null) {
1710             return;
1711         }
1712         ArrayList<String> properties = json.names();
1713         if (properties == null) {
1714             return;
1715         }
1716         for (String property : properties) {
1717             CLElement value = json.get(property);
1718             if (value instanceof CLNumber) {
1719                 reference.addCustomFloat(property, value.getFloat());
1720             } else if (value instanceof CLString) {
1721                 long it = parseColorString(value.content());
1722                 if (it != -1) {
1723                     reference.addCustomColor(property, (int) it);
1724                 }
1725             }
1726         }
1727     }
1728 
indexOf(String val, String... types)1729     private static int indexOf(String val, String... types) {
1730         for (int i = 0; i < types.length; i++) {
1731             if (types[i].equals(val)) {
1732                 return i;
1733             }
1734         }
1735         return -1;
1736     }
1737 
1738     /**
1739      * parse the motion section of a constraint
1740      * <pre>
1741      * csetName: {
1742      *   idToConstrain : {
1743      *       motion: {
1744      *          pathArc : 'startVertical'
1745      *          relativeTo: 'id'
1746      *          easing: 'curve'
1747      *          stagger: '2'
1748      *          quantize: steps or [steps, 'interpolator' phase ]
1749      *       }
1750      *   }
1751      * }
1752      * </pre>
1753      */
parseMotionProperties( CLElement element, ConstraintReference reference )1754     private static void parseMotionProperties(
1755             CLElement element,
1756             ConstraintReference reference
1757     ) throws CLParsingException {
1758         if (!(element instanceof CLObject)) {
1759             return;
1760         }
1761         CLObject obj = (CLObject) element;
1762         TypedBundle bundle = new TypedBundle();
1763         ArrayList<String> constraints = obj.names();
1764         if (constraints == null) {
1765             return;
1766         }
1767         for (String constraintName : constraints) {
1768 
1769             switch (constraintName) {
1770                 case "pathArc":
1771                     String val = obj.getString(constraintName);
1772                     int ord = indexOf(val, "none", "startVertical", "startHorizontal", "flip",
1773                             "below", "above");
1774                     if (ord == -1) {
1775                         System.err.println(obj.getLine() + " pathArc = '" + val + "'");
1776                         break;
1777                     }
1778                     bundle.add(TypedValues.MotionType.TYPE_PATHMOTION_ARC, ord);
1779                     break;
1780                 case "relativeTo":
1781                     bundle.add(TypedValues.MotionType.TYPE_ANIMATE_RELATIVE_TO,
1782                             obj.getString(constraintName));
1783                     break;
1784                 case "easing":
1785                     bundle.add(TypedValues.MotionType.TYPE_EASING, obj.getString(constraintName));
1786                     break;
1787                 case "stagger":
1788                     bundle.add(TypedValues.MotionType.TYPE_STAGGER, obj.getFloat(constraintName));
1789                     break;
1790                 case "quantize":
1791                     CLElement quant = obj.get(constraintName);
1792                     if (quant instanceof CLArray) {
1793                         CLArray array = (CLArray) quant;
1794                         int len = array.size();
1795                         if (len > 0) {
1796                             bundle.add(TYPE_QUANTIZE_MOTIONSTEPS, array.getInt(0));
1797                             if (len > 1) {
1798                                 bundle.add(TYPE_QUANTIZE_INTERPOLATOR_TYPE, array.getString(1));
1799                                 if (len > 2) {
1800                                     bundle.add(TYPE_QUANTIZE_MOTION_PHASE, array.getFloat(2));
1801                                 }
1802                             }
1803                         }
1804                     } else {
1805                         bundle.add(TYPE_QUANTIZE_MOTIONSTEPS, obj.getInt(constraintName));
1806                     }
1807                     break;
1808             }
1809         }
1810         reference.mMotionProperties = bundle;
1811     }
1812 
parseConstraint( State state, LayoutVariables layoutVariables, CLObject element, ConstraintReference reference, String constraintName )1813     static void parseConstraint(
1814             State state,
1815             LayoutVariables layoutVariables,
1816             CLObject element,
1817             ConstraintReference reference,
1818             String constraintName
1819     ) throws CLParsingException {
1820         boolean isLtr = !state.isRtl();
1821         CLArray constraint = element.getArrayOrNull(constraintName);
1822         if (constraint != null && constraint.size() > 1) {
1823             // params: target, anchor
1824             String target = constraint.getString(0);
1825             String anchor = constraint.getStringOrNull(1);
1826             float margin = 0f;
1827             float marginGone = 0f;
1828             if (constraint.size() > 2) {
1829                 // params: target, anchor, margin
1830                 CLElement arg2 = constraint.getOrNull(2);
1831                 margin = layoutVariables.get(arg2);
1832                 margin = toPix(state, margin);
1833             }
1834             if (constraint.size() > 3) {
1835                 // params: target, anchor, margin, marginGone
1836                 CLElement arg2 = constraint.getOrNull(3);
1837                 marginGone = layoutVariables.get(arg2);
1838                 marginGone = toPix(state, marginGone);
1839             }
1840 
1841             ConstraintReference targetReference = target.equals("parent")
1842                     ? state.constraints(State.PARENT) :
1843                     state.constraints(target);
1844 
1845             // For simplicity, we'll apply horizontal constraints separately
1846             boolean isHorizontalConstraint = false;
1847             boolean isHorOriginLeft = true;
1848             boolean isHorTargetLeft = true;
1849 
1850             switch (constraintName) {
1851                 case "circular":
1852                     float angle = layoutVariables.get(constraint.get(1));
1853                     float distance = 0f;
1854                     if (constraint.size() > 2) {
1855                         CLElement distanceArg = constraint.getOrNull(2);
1856                         distance = layoutVariables.get(distanceArg);
1857                         distance = toPix(state, distance);
1858                     }
1859                     reference.circularConstraint(targetReference, angle, distance);
1860                     break;
1861                 case "top":
1862                     switch (anchor) {
1863                         case "top":
1864                             reference.topToTop(targetReference);
1865                             break;
1866                         case "bottom":
1867                             reference.topToBottom(targetReference);
1868                             break;
1869                         case "baseline":
1870                             state.baselineNeededFor(targetReference.getKey());
1871                             reference.topToBaseline(targetReference);
1872                             break;
1873                     }
1874                     break;
1875                 case "bottom":
1876                     switch (anchor) {
1877                         case "top":
1878                             reference.bottomToTop(targetReference);
1879                             break;
1880                         case "bottom":
1881                             reference.bottomToBottom(targetReference);
1882                             break;
1883                         case "baseline":
1884                             state.baselineNeededFor(targetReference.getKey());
1885                             reference.bottomToBaseline(targetReference);
1886                     }
1887                     break;
1888                 case "baseline":
1889                     switch (anchor) {
1890                         case "baseline":
1891                             state.baselineNeededFor(reference.getKey());
1892                             state.baselineNeededFor(targetReference.getKey());
1893                             reference.baselineToBaseline(targetReference);
1894                             break;
1895                         case "top":
1896                             state.baselineNeededFor(reference.getKey());
1897                             reference.baselineToTop(targetReference);
1898                             break;
1899                         case "bottom":
1900                             state.baselineNeededFor(reference.getKey());
1901                             reference.baselineToBottom(targetReference);
1902                             break;
1903                     }
1904                     break;
1905                 case "left":
1906                     isHorizontalConstraint = true;
1907                     isHorOriginLeft = true;
1908                     break;
1909                 case "right":
1910                     isHorizontalConstraint = true;
1911                     isHorOriginLeft = false;
1912                     break;
1913                 case "start":
1914                     isHorizontalConstraint = true;
1915                     isHorOriginLeft = isLtr;
1916                     break;
1917                 case "end":
1918                     isHorizontalConstraint = true;
1919                     isHorOriginLeft = !isLtr;
1920                     break;
1921             }
1922 
1923             if (isHorizontalConstraint) {
1924                 // Resolve horizontal target anchor
1925                 switch (anchor) {
1926                     case "left":
1927                         isHorTargetLeft = true;
1928                         break;
1929                     case "right":
1930                         isHorTargetLeft = false;
1931                         break;
1932                     case "start":
1933                         isHorTargetLeft = isLtr;
1934                         break;
1935                     case "end":
1936                         isHorTargetLeft = !isLtr;
1937                         break;
1938                 }
1939 
1940                 // Resolved anchors, apply corresponding constraint
1941                 if (isHorOriginLeft) {
1942                     if (isHorTargetLeft) {
1943                         reference.leftToLeft(targetReference);
1944                     } else {
1945                         reference.leftToRight(targetReference);
1946                     }
1947                 } else {
1948                     if (isHorTargetLeft) {
1949                         reference.rightToLeft(targetReference);
1950                     } else {
1951                         reference.rightToRight(targetReference);
1952                     }
1953                 }
1954             }
1955 
1956             reference.margin(margin).marginGone(marginGone);
1957         } else {
1958             String target = element.getStringOrNull(constraintName);
1959             if (target != null) {
1960                 ConstraintReference targetReference = target.equals("parent")
1961                         ? state.constraints(State.PARENT) :
1962                         state.constraints(target);
1963 
1964                 switch (constraintName) {
1965                     case "start":
1966                         if (isLtr) {
1967                             reference.leftToLeft(targetReference);
1968                         } else {
1969                             reference.rightToRight(targetReference);
1970                         }
1971                         break;
1972                     case "end":
1973                         if (isLtr) {
1974                             reference.rightToRight(targetReference);
1975                         } else {
1976                             reference.leftToLeft(targetReference);
1977                         }
1978                         break;
1979                     case "top":
1980                         reference.topToTop(targetReference);
1981                         break;
1982                     case "bottom":
1983                         reference.bottomToBottom(targetReference);
1984                         break;
1985                     case "baseline":
1986                         state.baselineNeededFor(reference.getKey());
1987                         state.baselineNeededFor(targetReference.getKey());
1988                         reference.baselineToBaseline(targetReference);
1989                         break;
1990                 }
1991             }
1992         }
1993     }
1994 
parseDimensionMode(String dimensionString)1995     static Dimension parseDimensionMode(String dimensionString) {
1996         Dimension dimension = Dimension.createFixed(0);
1997         switch (dimensionString) {
1998             case "wrap":
1999                 dimension = Dimension.createWrap();
2000                 break;
2001             case "preferWrap":
2002                 dimension = Dimension.createSuggested(Dimension.WRAP_DIMENSION);
2003                 break;
2004             case "spread":
2005                 dimension = Dimension.createSuggested(Dimension.SPREAD_DIMENSION);
2006                 break;
2007             case "parent":
2008                 dimension = Dimension.createParent();
2009                 break;
2010             default: {
2011                 if (dimensionString.endsWith("%")) {
2012                     // parent percent
2013                     String percentString =
2014                             dimensionString.substring(0, dimensionString.indexOf('%'));
2015                     float percentValue = Float.parseFloat(percentString) / 100f;
2016                     dimension = Dimension.createPercent(0, percentValue).suggested(0);
2017                 } else if (dimensionString.contains(":")) {
2018                     dimension = Dimension.createRatio(dimensionString)
2019                             .suggested(Dimension.SPREAD_DIMENSION);
2020                 }
2021             }
2022         }
2023         return dimension;
2024     }
2025 
parseDimension(CLObject element, String constraintName, State state, CorePixelDp dpToPixels)2026     static Dimension parseDimension(CLObject element,
2027                                     String constraintName,
2028                                     State state,
2029                                     CorePixelDp dpToPixels) throws CLParsingException {
2030         CLElement dimensionElement = element.get(constraintName);
2031         Dimension dimension = Dimension.createFixed(0);
2032         if (dimensionElement instanceof CLString) {
2033             dimension = parseDimensionMode(dimensionElement.content());
2034         } else if (dimensionElement instanceof CLNumber) {
2035             dimension = Dimension.createFixed(
2036                     state.convertDimension(dpToPixels.toPixels(element.getFloat(constraintName))));
2037 
2038         } else if (dimensionElement instanceof CLObject) {
2039             CLObject obj = (CLObject) dimensionElement;
2040             String mode = obj.getStringOrNull("value");
2041             if (mode != null) {
2042                 dimension = parseDimensionMode(mode);
2043             }
2044 
2045             CLElement minEl = obj.getOrNull("min");
2046             if (minEl != null) {
2047                 if (minEl instanceof CLNumber) {
2048                     float min = ((CLNumber) minEl).getFloat();
2049                     dimension.min(state.convertDimension(dpToPixels.toPixels(min)));
2050                 } else if (minEl instanceof CLString) {
2051                     dimension.min(Dimension.WRAP_DIMENSION);
2052                 }
2053             }
2054             CLElement maxEl = obj.getOrNull("max");
2055             if (maxEl != null) {
2056                 if (maxEl instanceof CLNumber) {
2057                     float max = ((CLNumber) maxEl).getFloat();
2058                     dimension.max(state.convertDimension(dpToPixels.toPixels(max)));
2059                 } else if (maxEl instanceof CLString) {
2060                     dimension.max(Dimension.WRAP_DIMENSION);
2061                 }
2062             }
2063         }
2064         return dimension;
2065     }
2066 
2067     /**
2068      * parse a color string
2069      *
2070      * @return -1 if it cannot parse unsigned long
2071      */
parseColorString(String value)2072     static long parseColorString(String value) {
2073         String str = value;
2074         if (str.startsWith("#")) {
2075             str = str.substring(1);
2076             if (str.length() == 6) {
2077                 str = "FF" + str;
2078             }
2079             return Long.parseLong(str, 16);
2080         } else {
2081             return -1L;
2082         }
2083     }
2084 
lookForType(CLObject element)2085     static String lookForType(CLObject element) throws CLParsingException {
2086         ArrayList<String> constraints = element.names();
2087         for (String constraintName : constraints) {
2088             if (constraintName.equals("type")) {
2089                 return element.getString("type");
2090             }
2091         }
2092         return null;
2093     }
2094 }