1 /*
2  * Copyright (C) 2021 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 package androidx.constraintlayout.core.parser;
17 
18 public class CLParser {
19 
20     static boolean sDebug = false;
21 
22     private String mContent;
23     private boolean mHasComment = false;
24     private int mLineNumber;
25 
26     enum TYPE {UNKNOWN, OBJECT, ARRAY, NUMBER, STRING, KEY, TOKEN}
27 
28     // @TODO: add description
parse(String string)29     public static CLObject parse(String string) throws CLParsingException {
30         return new CLParser(string).parse();
31     }
32 
CLParser(String content)33     public CLParser(String content) {
34         mContent = content;
35     }
36 
37     // @TODO: add description
parse()38     public CLObject parse() throws CLParsingException {
39         @SuppressWarnings("unused") CLObject root = null;
40 
41         char[] content = mContent.toCharArray();
42         @SuppressWarnings("unused") CLElement currentElement = null;
43 
44         final int length = content.length;
45 
46         // First, let's find the root element start
47         mLineNumber = 1;
48 
49         int startIndex = -1;
50         for (int i = 0; i < length; i++) {
51             char c = content[i];
52             if (c == '{') {
53                 startIndex = i;
54                 break;
55             }
56             if (c == '\n') {
57                 mLineNumber++;
58             }
59         }
60         if (startIndex == -1) {
61             throw new CLParsingException("invalid json content", null);
62         }
63 
64         // We have a root object, let's start
65         root = CLObject.allocate(content);
66         root.setLine(mLineNumber);
67         root.setStart(startIndex);
68         currentElement = root;
69 
70         for (int i = startIndex + 1; i < length; i++) {
71             char c = content[i];
72             if (c == '\n') {
73                 mLineNumber++;
74             }
75             if (mHasComment) {
76                 if (c == '\n') {
77                     mHasComment = false;
78                 } else {
79                     continue;
80                 }
81             }
82             if (false) {
83                 System.out.println("Looking at " + i + " : <" + c + ">");
84             }
85             if (currentElement == null) {
86                 break;
87             }
88             if (currentElement.isDone()) {
89                 currentElement = getNextJsonElement(i, c, currentElement, content);
90             } else if (currentElement instanceof CLObject) {
91                 if (c == '}') {
92                     currentElement.setEnd(i - 1);
93                 } else {
94                     currentElement = getNextJsonElement(i, c, currentElement, content);
95                 }
96             } else if (currentElement instanceof CLArray) {
97                 if (c == ']') {
98                     currentElement.setEnd(i - 1);
99                 } else {
100                     currentElement = getNextJsonElement(i, c, currentElement, content);
101                 }
102             } else if (currentElement instanceof CLString) {
103                 char ck = content[(int) currentElement.mStart];
104                 if (ck == c) {
105                     currentElement.setStart(currentElement.mStart + 1);
106                     currentElement.setEnd(i - 1);
107                 }
108             } else {
109                 if (currentElement instanceof CLToken) {
110                     CLToken token = (CLToken) currentElement;
111                     if (!token.validate(c, i)) {
112                         throw new CLParsingException("parsing incorrect token " + token.content()
113                                 + " at line " + mLineNumber, token);
114                     }
115                 }
116                 if (currentElement instanceof CLKey || currentElement instanceof CLString) {
117                     char ck = content[(int) currentElement.mStart];
118                     if ((ck == '\'' || ck == '"') && ck == c) {
119                         currentElement.setStart(currentElement.mStart + 1);
120                         currentElement.setEnd(i - 1);
121                     }
122                 }
123                 if (!currentElement.isDone()) {
124                     if (c == '}' || c == ']' || c == ',' || c == ' '
125                             || c == '\t' || c == '\r' || c == '\n' || c == ':') {
126                         currentElement.setEnd(i - 1);
127                         if (c == '}' || c == ']') {
128                             currentElement = currentElement.getContainer();
129                             currentElement.setEnd(i - 1);
130                             if (currentElement instanceof CLKey) {
131                                 currentElement = currentElement.getContainer();
132                                 currentElement.setEnd(i - 1);
133                             }
134                         }
135                     }
136                 }
137             }
138 
139             if (currentElement.isDone() && (!(currentElement instanceof CLKey)
140                     || ((CLKey) currentElement).mElements.size() > 0)) {
141                 currentElement = currentElement.getContainer();
142             }
143         }
144 
145         // Close all open elements --
146         // allow us to be more resistant to invalid json, useful during editing.
147         while (currentElement != null && !currentElement.isDone()) {
148             if (currentElement instanceof CLString) {
149                 currentElement.setStart((int) currentElement.mStart + 1);
150             }
151             currentElement.setEnd(length - 1);
152             currentElement = currentElement.getContainer();
153         }
154 
155         if (sDebug) {
156             System.out.println("Root: " + root.toJSON());
157         }
158 
159         return root;
160     }
161 
getNextJsonElement(int position, char c, CLElement currentElement, char[] content)162     private CLElement getNextJsonElement(int position, char c, CLElement currentElement,
163             char[] content) throws CLParsingException {
164         switch (c) {
165             case ' ':
166             case ':':
167             case ',':
168             case '\t':
169             case '\r':
170             case '\n': {
171                 // skip space
172             }
173             break;
174             case '{': {
175                 currentElement = createElement(currentElement,
176                         position, TYPE.OBJECT, true, content);
177             }
178             break;
179             case '[': {
180                 currentElement = createElement(currentElement,
181                         position, TYPE.ARRAY, true, content);
182             }
183             break;
184             case ']':
185             case '}': {
186                 currentElement.setEnd(position - 1);
187                 currentElement = currentElement.getContainer();
188                 currentElement.setEnd(position);
189             }
190             break;
191             case '"':
192             case '\'': {
193                 if (currentElement instanceof CLObject) {
194                     currentElement = createElement(currentElement,
195                             position, TYPE.KEY, true, content);
196                 } else {
197                     currentElement = createElement(currentElement,
198                             position, TYPE.STRING, true, content);
199                 }
200             }
201             break;
202             case '/': {
203                 if (position + 1 < content.length && content[position + 1] == '/') {
204                     mHasComment = true;
205                 }
206             }
207             break;
208             case '-':
209             case '+':
210             case '.':
211             case '0':
212             case '1':
213             case '2':
214             case '3':
215             case '4':
216             case '5':
217             case '6':
218             case '7':
219             case '8':
220             case '9': {
221                 currentElement = createElement(currentElement,
222                         position, TYPE.NUMBER, true, content);
223             }
224             break;
225             default: {
226                 if (currentElement instanceof CLContainer
227                         && !(currentElement instanceof CLObject)) {
228                     currentElement = createElement(currentElement,
229                             position, TYPE.TOKEN, true, content);
230                     CLToken token = (CLToken) currentElement;
231                     if (!token.validate(c, position)) {
232                         throw new CLParsingException("incorrect token <"
233                                 + c + "> at line " + mLineNumber, token);
234                     }
235                 } else {
236                     currentElement = createElement(currentElement,
237                             position, TYPE.KEY, true, content);
238                 }
239             }
240         }
241         return currentElement;
242     }
243 
createElement(CLElement currentElement, int position, TYPE type, boolean applyStart, char[] content)244     private CLElement createElement(CLElement currentElement, int position,
245             TYPE type, boolean applyStart, char[] content) {
246         CLElement newElement = null;
247         if (sDebug) {
248             System.out.println("CREATE " + type + " at " + content[position]);
249         }
250         switch (type) {
251             case OBJECT: {
252                 newElement = CLObject.allocate(content);
253                 position++;
254             }
255             break;
256             case ARRAY: {
257                 newElement = CLArray.allocate(content);
258                 position++;
259             }
260             break;
261             case STRING: {
262                 newElement = CLString.allocate(content);
263             }
264             break;
265             case NUMBER: {
266                 newElement = CLNumber.allocate(content);
267             }
268             break;
269             case KEY: {
270                 newElement = CLKey.allocate(content);
271             }
272             break;
273             case TOKEN: {
274                 newElement = CLToken.allocate(content);
275             }
276             break;
277             default:
278                 break;
279         }
280         if (newElement == null) {
281             return null;
282         }
283         newElement.setLine(mLineNumber);
284         if (applyStart) {
285             newElement.setStart(position);
286         }
287         if (currentElement instanceof CLContainer) {
288             CLContainer container = (CLContainer) currentElement;
289             newElement.setContainer(container);
290         }
291         return newElement;
292     }
293 
294 }
295