Merge branch 'features/r2_11_2/JAL-3821_reinstate_patch' into develop
[jalview.git] / unused / javajs / util / JSJSONParser.java
1 package javajs.util;
2
3 import java.util.HashMap;
4 import java.util.Hashtable;
5 import java.util.Map;
6
7
8 /**
9  * a very simple JSON parser for JSON objects that are compatible with JavaScript
10  * A gross simplification of https://github.com/douglascrockford/JSON-java
11  * 
12  * A SUBSET of JSON with similarly to window.JSON.parse():
13  * 
14  * In JavaScript returns "null" for a null value, not null
15  * 
16  *  -- requires quoted strings for keys and values
17  *  
18  *  -- does not allow /xxx/ objects
19  *  
20  *  @author Bob Hanson
21  *  
22  */
23 public class JSJSONParser {
24
25   private String str;
26   private int index;
27   private int len;
28   private boolean asHashTable;
29
30   public JSJSONParser () {
31     // for reflection
32   }
33   
34   /**
35    * requires { "key":"value", "key":"value",....}
36    * 
37    * @param str
38    * @param asHashTable TODO
39    * 
40    * @return Map or null
41    */
42   @SuppressWarnings("unchecked")
43   public Map<String, Object> parseMap(String str, boolean asHashTable) {
44     index = 0;
45     this.asHashTable = asHashTable;
46     this.str = str;
47     len = str.length();
48     if (getChar() != '{')
49       return null;
50     returnChar();
51     return (Map<String, Object>) getValue(false);
52   }
53   
54   /**
55    * Could return Integer, Float, Boolean, String, Map<String, Object>, Lst<Object>, or null
56    * 
57    * @param str
58    * @param asHashTable 
59    * @return a object equivalent to the JSON string str
60    * 
61    */
62   public Object parse(String str, boolean asHashTable) {
63     index = 0;
64     this.asHashTable = asHashTable;
65     this.str = str;
66     len = str.length();
67     return getValue(false);
68   }
69
70   private char next() {
71     return (index < len ? str.charAt(index++) : '\0');
72   }
73
74   private void returnChar() {
75     index--;
76   }
77
78   /**
79    * Get the next char in the string, skipping whitespace.
80    * 
81    * @throws JSONException
82    * @return one character, or 0 if there are no more characters.
83    */
84   private char getChar() throws JSONException {
85     for (;;) {
86       char c = next();
87       if (c == 0 || c > ' ') {
88         return c;
89       }
90     }
91   }
92
93   /**
94    * only allowing the following values:
95    * 
96    * {...} object
97    * 
98    * [...] array
99    * 
100    * Integer
101    * 
102    * Float
103    * 
104    * "quoted string"
105    * 
106    * 
107    * @param isKey if we should allow {...} and [...]
108    * @return a subclass of Object
109    * @throws JSONException
110    */
111   private Object getValue(boolean isKey) throws JSONException {
112     int i = index;
113     char c = getChar();
114     switch (c) {
115     case '\0':
116       return null;
117     case '"':
118     case '\'':
119       return getString(c);
120     case '{':
121       if (!isKey)
122         return getObject();
123       c = 0;
124       break;
125     case '[':
126       if (!isKey)
127         return getArray();
128       c = 0;
129       break;
130     default:
131       // standard syntax is assumed; not checking all possible invalid keys
132       // for example, "-" is not allowed in JavaScript, which is what this is for
133       returnChar();
134       while (c >= ' ' && "[,]{:}'\"".indexOf(c) < 0)
135         c = next();
136       returnChar();
137       if (isKey && c != ':')
138         c = 0;
139       break;
140     }
141     if (isKey && c == 0)
142       throw new JSONException("invalid key");
143
144     String string = str.substring(i, index).trim();
145
146     // check for the only valid simple words: true, false, null (lower case)
147     // and in this case, only for 
148
149     if (!isKey) {
150       if (string.equals("true")) {
151         return Boolean.TRUE;
152       }
153       if (string.equals("false")) {
154         return Boolean.FALSE;
155       }
156       if (string.equals("null")) {
157         return (asHashTable ? string : null);
158       }
159     }
160     //  only numbers from here on:
161     c = string.charAt(0);
162     if (c >= '0' && c <= '9' || c == '-')
163       try {
164         if (string.indexOf('.') < 0 && string.indexOf('e') < 0
165             && string.indexOf('E') < 0)
166           return new Integer(string);
167         // not allowing infinity or NaN
168         // using float here because Jmol does not use Double
169         Float d = Float.valueOf(string);
170         if (!d.isInfinite() && !d.isNaN())
171           return d;
172       } catch (Exception e) {
173       }
174     // not a valid number
175     System.out.println("JSON parser cannot parse " + string);
176     throw new JSONException("invalid value");
177   }
178
179   private String getString(char quote) throws JSONException {
180     char c;
181     SB sb = null;
182     int i0 = index;
183     for (;;) {
184       int i1 = index;
185       switch (c = next()) {
186       case '\0':
187       case '\n':
188       case '\r':
189         throw syntaxError("Unterminated string");
190       case '\\':
191         switch (c = next()) {
192         case '"':
193         case '\'':
194         case '\\':
195         case '/':
196           break;
197         case 'b':
198           c = '\b';
199           break;
200         case 't':
201           c = '\t';
202           break;
203         case 'n':
204           c = '\n';
205           break;
206         case 'f':
207           c = '\f';
208           break;
209         case 'r':
210           c = '\r';
211           break;
212         case 'u':
213           int i = index;
214           index += 4;
215           try {
216             c = (char) Integer.parseInt(str.substring(i, index), 16);
217           } catch (Exception e) {
218             throw syntaxError("Substring bounds error");
219           }
220           break;
221         default:
222           throw syntaxError("Illegal escape.");
223         }
224         break;
225       default:
226         if (c == quote)
227           return (sb == null ? str.substring(i0, i1) : sb.toString());
228         break;
229       }
230       if (index > i1 + 1) {
231         if (sb == null) {
232           sb = new SB();
233           sb.append(str.substring(i0, i1));
234         }
235       }
236       if (sb != null)
237         sb.appendC(c);
238     }
239   }
240
241   private Object getObject() {
242     Map<String, Object> map = (asHashTable ? new Hashtable<String, Object>() : new HashMap<String, Object>());
243     String key = null;
244     switch (getChar()) {
245     case '}':
246       return map;
247     case 0:
248       throw new JSONException("invalid object");
249     }
250     returnChar();
251     boolean isKey = false;
252     for (;;) {
253       if ((isKey = !isKey) == true)
254         key = getValue(true).toString();
255       else
256         map.put(key, getValue(false));
257       switch (getChar()) {
258       case '}':
259         return map;
260       case ':':
261         if (isKey)
262           continue;
263         isKey = true;
264         //$FALL-THROUGH$
265       case ',':
266         if (!isKey)
267           continue;
268         //$FALL-THROUGH$
269       default:
270         throw syntaxError("Expected ',' or ':' or '}'");
271       }
272     }
273   }
274
275   private Object getArray() {
276     Lst<Object> l = new Lst<Object>();
277     switch (getChar()) {
278     case ']':
279       return l;
280     case '\0':
281       throw new JSONException("invalid array");
282     }
283     returnChar();
284     boolean isNull = false;
285     for (;;) {
286       if (isNull) {
287         l.addLast(null);
288         isNull = false;
289       } else {
290         l.addLast(getValue(false));
291       }
292       switch (getChar()) {
293       case ',':
294         switch (getChar()) {
295         case ']':
296           // terminal ,
297           return l;
298         case ',':
299           // empty value
300           isNull = true;
301           //$FALL-THROUGH$
302         default:
303           returnChar();
304         }
305         continue;
306       case ']':
307         return l;
308       default:
309         throw syntaxError("Expected ',' or ']'");
310       }
311     }
312   }
313
314   /**
315    * Make a JSONException to signal a syntax error.
316    * 
317    * @param message
318    *        The error message.
319    * @return A JSONException object, suitable for throwing
320    */
321   public JSONException syntaxError(String message) {
322     return new JSONException(message + " for " + str.substring(0, Math.min(index,  len)));
323   }
324
325 }