Initial import
[jpf-core.git] / src / main / gov / nasa / jpf / util / json / JSONParser.java
1 /*
2  * Copyright (C) 2014, United States Government, as represented by the
3  * Administrator of the National Aeronautics and Space Administration.
4  * All rights reserved.
5  *
6  * The Java Pathfinder core (jpf-core) platform is licensed under the
7  * Apache License, Version 2.0 (the "License"); you may not use this file except
8  * in compliance with the License. You may obtain a copy of the License at
9  * 
10  *        http://www.apache.org/licenses/LICENSE-2.0. 
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and 
16  * limitations under the License.
17  */
18 package gov.nasa.jpf.util.json;
19
20 import gov.nasa.jpf.JPFException;
21
22 /**
23  * JSON parser. Read tokenized stream from JSONTokenizer and returns root JSON
24  * node.
25  * Parser read extended JSON grammar (http://json.org).
26  * Standard grammar was extended by ability to set Choice Generator call as a
27  * value in JSON object.
28  * @author Ivan Mushketik
29  */
30 public class JSONParser {
31
32   JSONLexer lexer;
33   // Last token returned by lexer
34   Token lastReadToken;
35   
36   Token prevReadToken;
37   // true if parser bactracked to previous token
38   int backtrack;
39
40   public JSONParser(JSONLexer lexer) {
41     this.lexer = lexer;
42   }
43
44   /**
45    * Parse JSON document
46    * @return root node of JSON tree.
47    */
48   public JSONObject parse() {
49     return parseObject();
50   }
51
52   /**
53    * Read next token from lexer output stream. If parser backtraced return previously
54    * read token
55    * @return
56    */
57   private Token next() {
58     if (lastReadToken != null && lastReadToken.getType() == Token.Type.DocumentEnd) {
59       return lastReadToken;
60     }
61
62     if (backtrack == 1) {
63       backtrack--;
64       return lastReadToken;
65     }
66
67     if (backtrack == 2) {
68       backtrack--;
69       return prevReadToken;
70     }
71
72     prevReadToken = lastReadToken;
73     lastReadToken = lexer.getNextToken();
74
75     return lastReadToken;
76   }
77
78   /**
79    * Backtrack to previous token
80    */
81   private void back() {
82     if (backtrack == 2) {
83       throw new JPFException("Attempt to bactrack three times. Posibly an error. Please report");
84     }
85
86     if (lastReadToken == null) {
87       throw new JPFException("Attempt to backtrack before starting to read token stream. Please report");
88     }
89
90     if (backtrack == 1 && prevReadToken == null) {
91       throw new JPFException("Attempt to backtrack twice when less then two tokens read. Please report");
92     }
93
94     backtrack++;
95   }
96
97   /**
98    * Read next token and check it's type. If type is wrong method throws exception
99    * else it returns read token
100    * @param type - type of the following token.
101    * @return read token if it has correct type
102    */
103   private Token consume(Token.Type type) {
104     Token t = next();
105
106     if (t.getType() != type) {
107       error("Unexpected token '" + t.getValue() + "' expected " + type);
108     }
109
110     return t;
111   }
112
113   /**
114    * Parse JSON object
115    * @return
116    */
117   private JSONObject parseObject() {
118     JSONObject pn = new JSONObject();
119     consume(Token.Type.ObjectStart);  
120     Token t = next();
121
122     // Check if object is empty
123     if (t.getType() != Token.Type.ObjectEnd) {
124       back();
125       while (true) {
126         Token key = consume(Token.Type.String);
127         consume(Token.Type.KeyValueSeparator);
128         
129
130         Token posibleId = next();
131         t = next();
132
133         if (posibleId.getType() == Token.Type.Identificator &&
134             t.getType() == Token.Type.CGCallParamsStart) {
135             CGCall cg = parseCGCall(posibleId.getValue());
136             pn.addCGCall(key.getValue(), cg);
137         } else {
138           back();
139           back();
140           Value v = parseValue();
141           pn.addValue(key.getValue(), v);
142         }
143
144         t = next();
145         // If next token is comma there is one more key-value pair to read
146         if (t.getType() != Token.Type.Comma) {
147           back();
148           break;
149         }
150       }
151       consume(Token.Type.ObjectEnd);
152     }
153     return pn;
154   }
155
156   /**
157    * Parse array of JSON objects
158    * @return parsed array of JSON objects
159    */
160   private ArrayValue parseArray() {
161     consume(Token.Type.ArrayStart);
162     ArrayValue arrayValue = new ArrayValue();
163     Token t = next();
164     if (t.getType() != Token.Type.ArrayEnd) {
165       back();
166       while (true) {
167         Value val = parseValue();
168         arrayValue.addValue(val);
169
170         t = next();
171         // If next token is comma there is one more object to parse
172         if (t.getType() != Token.Type.Comma) {
173           back();
174           break;
175         }
176       }
177     } else {
178       back();
179     }
180     consume(Token.Type.ArrayEnd);
181     
182     return arrayValue;
183   }
184
185   /**
186    * Parse identifier. Identifier can be "null", "true" or "false"
187    * @return appropriate value object
188    */
189   private Value parseIdentificator() {
190     Token id = consume(Token.Type.Identificator);
191
192     String val = id.getValue();
193     if (val.equals("true")) {
194       return new BooleanValue(true, "true");
195
196     } else if (val.equals("false")) {
197       return new BooleanValue(false, "false");
198       
199     } else if (val.equals("null")) {
200       return new NullValue();
201     }
202
203     error("Unknown identifier");
204     return null;
205   }
206
207   private void error(String string) {
208     throw new JPFException(string + "(" + lexer.getLineNumber() + ":" + lexer.getCurrentPos() + ")");
209   }
210
211   private Value parseValue() {
212     Token t = next();
213     switch (t.getType()) {
214       case Number:
215         return new DoubleValue(t.getValue());
216         
217       case String:
218         return new StringValue(t.getValue());
219         
220       case ArrayStart:
221         back();
222         return parseArray();
223         
224       case ObjectStart:
225         back();
226         return new JSONObjectValue(parseObject());
227         
228       case Identificator:
229         back();
230         return parseIdentificator();
231         
232       default:
233         error("Unexpected token '" + t.getValue() + "' during parsing JSON value");
234         return null;
235     }
236     
237   }
238
239   /**
240    * Parse Choice Generator call
241    * @param cgName - name of called Choice Generator.
242    * @return parsed object with info about Choice Generator call
243    */
244   private CGCall parseCGCall(String cgName) {
245     
246     CGCall parsedCG = new CGCall(cgName);
247     Token t = next();
248
249     if (t.getType() != Token.Type.CGCallParamsEnd) {
250       back();
251       while (true) {
252         Value v = parseValue();
253         parsedCG.addParam(v);
254
255         t = next();
256         if (t.getType() == Token.Type.CGCallParamsEnd) {
257           back();
258           break;
259         }
260         back();
261         consume(Token.Type.Comma);
262       }
263     } else {
264       back();
265     }
266
267     consume(Token.Type.CGCallParamsEnd);
268
269     return parsedCG;
270   }
271 }