KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > caucho > quercus > lib > json > JsonDecoder


1 /*
2  * Copyright (c) 1998-2006 Caucho Technology -- all rights reserved
3  *
4  * This file is part of Resin(R) Open Source
5  *
6  * Each copy or derived work must preserve the copyright notice and this
7  * notice unmodified.
8  *
9  * Resin Open Source is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * Resin Open Source is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
17  * of NON-INFRINGEMENT. See the GNU General Public License for more
18  * details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with Resin Open Source; if not, write to the
22  *
23  * Free Software Foundation, Inc.
24  * 59 Temple Place, Suite 330
25  * Boston, MA 02111-1307 USA
26  *
27  * @author Nam Nguyen
28  */

29
30 package com.caucho.quercus.lib.json;
31
32 import com.caucho.quercus.env.*;
33 import com.caucho.util.L10N;
34
35 class JsonDecoder {
36   private static final L10N L = new L10N(JsonDecoder.class);
37
38   private StringValue _string;
39   private int _len;
40   private int _offset;
41
42   private boolean _isAssociative;
43
44   public Value jsonDecode(Env env,
45                           StringValue s,
46                           boolean assoc)
47   {
48     _string = s;
49     _len = _string.length();
50     _offset = 0;
51
52     _isAssociative = assoc;
53
54     Value val = jsonDecodeImpl(env);
55
56     // Should now be at end of string or have only white spaces left.
57
if (skipWhitespace() >= 0)
58       return errorReturn(env, "expected no more input");
59
60     return val;
61   }
62
63   /**
64    * Entry point to decode a JSON value.
65    *
66    * @return decoded PHP value
67    */

68   private Value jsonDecodeImpl(Env env)
69   {
70     for (int ch = skipWhitespace(); ch >= 0; ch = read()) {
71
72       switch (ch) {
73         case '"':
74           return decodeString(env);
75
76         case 't':
77           if (read() == 'r' &&
78               read() == 'u' &&
79               read() == 'e')
80             return BooleanValue.TRUE;
81           else
82             return errorReturn(env, "expected 'true'");
83
84         case 'f':
85           if (read() == 'a' &&
86               read() == 'l' &&
87               read() == 's' &&
88               read() == 'e')
89             return BooleanValue.FALSE;
90           else
91             return errorReturn(env, "expected 'false'");
92
93         case 'n':
94           if (read() == 'u' &&
95               read() == 'l' &&
96               read() == 'l')
97             return NullValue.NULL;
98           else
99             return errorReturn(env, "expected 'null'");
100
101         case '[':
102           return decodeArray(env);
103
104         case '{':
105           return decodeObject(env);
106
107         default:
108           if (ch == '-' || ('0' <= ch && ch <= '9'))
109             return decodeNumber(env, ch);
110           else
111             return errorReturn(env);
112       }
113     }
114
115     return errorReturn(env);
116   }
117
118   /**
119    * Checks to see if there is a valid number per JSON Internet Draft.
120    */

121   private Value decodeNumber(Env env, int ch)
122   {
123     StringBuilder JavaDoc sb = new StringBuilder JavaDoc();
124
125     // (-)?
126
if (ch == '-') {
127       sb.append((char)ch);
128       ch = read();
129     }
130
131     // (0) | ([1-9] [0-9]+)
132
if (ch >= 0) {
133       if (ch == '0') {
134         sb.append((char)ch);
135         ch = read();
136       }
137       else if ('1' <= ch && ch <= '9') {
138         sb.append((char)ch);
139         ch = read();
140
141         while ('0' <= ch && ch <= '9') {
142           sb.append((char)ch);
143           ch = read();
144         }
145       }
146       else
147         return errorReturn(env, "expected 1-9");
148     }
149
150     int integerEnd = sb.length();
151
152     // ((decimalPoint) [0-9]+)?
153
if (ch == '.') {
154       sb.append((char)ch);
155       ch = read();
156
157       while ('0' <= ch && ch <= '9') {
158         sb.append((char)ch);
159         ch = read();
160       }
161     }
162
163     // ((e | E) (+ | -)? [0-9]+)
164
if (ch == 'e' || ch == 'E') {
165       sb.append((char)ch);
166       ch = read();
167
168       if (ch == '+' || ch == '-') {
169         sb.append((char)ch);
170         ch = read();
171       }
172
173       if ('0' <= ch && ch <= '9') {
174         sb.append((char)ch);
175         ch = read();
176
177         while ('0' <= ch && ch <= '9') {
178           sb.append((char)ch);
179           ch = read();
180         }
181       }
182       else
183         return errorReturn(env, "expected 0-9 exponent");
184     }
185
186     unread();
187
188     if (integerEnd != sb.length())
189       return new DoubleValue(Double.parseDouble(sb.toString()));
190     else
191       return new LongValue(Long.parseLong(sb.toString()));
192   }
193
194   /**
195    * Returns a non-associative PHP array.
196    */

197   private Value decodeArray(Env env)
198   {
199     ArrayValueImpl array = new ArrayValueImpl();
200
201     while (true) {
202       int ch = skipWhitespace();
203
204       if (ch == ']')
205         break;
206
207       unread();
208
209       array.append(jsonDecodeImpl(env));
210
211       ch = skipWhitespace();
212
213       if (ch == ',') {
214       }
215       else if (ch == ']')
216         break;
217       else
218         errorReturn(env, "expected either ',' or ']'");
219     }
220
221     return array;
222   }
223
224   private Value decodeObject(Env env)
225   {
226     if (_isAssociative)
227       return decodeObjectToArray(env);
228     else
229       return decodeObjectToObject(env);
230   }
231
232   /**
233    * Returns a PHP associative array of JSON object.
234    */

235   private Value decodeObjectToArray(Env env)
236   {
237     ArrayValue array = new ArrayValueImpl();
238
239     while (true) {
240       int ch = skipWhitespace();
241
242       if (ch == '}')
243         break;
244
245       unread();
246
247       Value name = jsonDecodeImpl(env);
248
249       ch = skipWhitespace();
250
251       if (ch != ':')
252         return errorReturn(env, "expected ':'");
253
254       array.append(name, jsonDecodeImpl(env));
255
256       ch = skipWhitespace();
257
258       if (ch == ',') {
259       }
260       else if (ch == '}')
261         break;
262       else
263         return errorReturn(env, "expected either ',' or '}'");
264     }
265
266     return array;
267   }
268
269   /**
270    * Returns a PHP stdObject of JSON object.
271    */

272   private Value decodeObjectToObject(Env env)
273   {
274     ObjectValue object = env.createObject();
275
276     while (true) {
277       int ch = skipWhitespace();
278
279       if (ch == '}')
280         break;
281
282       unread();
283
284       Value name = jsonDecodeImpl(env);
285
286       ch = skipWhitespace();
287
288       if (ch != ':')
289         return errorReturn(env, "expected ':'");
290
291       object.putFieldInit(env, name.toString(), jsonDecodeImpl(env));
292
293       ch = skipWhitespace();
294
295       if (ch == ',') {
296       }
297       else if (ch == '}')
298         break;
299       else
300         return errorReturn(env, "expected either ',' or '}'");
301     }
302
303     return object;
304   }
305
306   /**
307    * Returns a PHP string.
308    */

309   private Value decodeString(Env env)
310   {
311     StringBuilderValue sbv = new StringBuilderValue();
312
313     for (int ch = read(); ch >= 0; ch = read()) {
314
315       switch (ch) {
316         // Escaped Characters
317
case '\\':
318           ch = read();
319           if (ch < 0)
320             return errorReturn(env, "invalid escape character");
321
322           switch (ch) {
323             case '"':
324               sbv.append('"');
325               break;
326             case '\\':
327               sbv.append('\\');
328               break;
329             case '/':
330               sbv.append('/');
331               break;
332             case 'b':
333               sbv.append('\b');
334               break;
335             case 'f':
336               sbv.append('\f');
337               break;
338             case 'n':
339               sbv.append('\n');
340               break;
341             case 'r':
342               sbv.append('\r');
343               break;
344             case 't':
345               sbv.append('\t');
346               break;
347             case 'u':
348             case 'U':
349               int hex = 0;
350
351               for (int i = 0; i < 4; i++) {
352                 hex = hex << 4;
353                 ch = read();
354
355                 if ('0' <= ch && ch <= '9')
356                   hex += ch - '0';
357                 else if (ch >= 'a' && ch <= 'f')
358                   hex += ch - 'a' + 10;
359                 else if (ch >= 'A' && ch <= 'F')
360                   hex += ch - 'A' + 10;
361                 else
362                   return errorReturn(env, "invalid escaped hex character");
363               }
364
365               sbv.append((char)hex);
366
367           }
368           break;
369
370         case '"':
371           return sbv;
372
373         default:
374           sbv.append((char)ch);
375       }
376     }
377
378     return errorReturn(env, "error decoding string");
379   }
380
381   private Value errorReturn(Env env)
382   {
383     return errorReturn(env, null);
384   }
385
386   private Value errorReturn(Env env, String JavaDoc message)
387   {
388     int start;
389     int end;
390
391     if (_offset < _len) {
392       start = _offset - 1;
393       end = _offset;
394     }
395     else {
396       start = _len - 1;
397       end = _len;
398     }
399
400     String JavaDoc token = _string.substring(start, end).toString();
401
402     if (message != null)
403       env.warning(L.l("error parsing '{0}': {1}", token, message));
404     else
405       env.warning(L.l("error parsing '{0}'", token));
406
407     return NullValue.NULL;
408   }
409
410   private void unread()
411   {
412     if (_offset > 0)
413       _offset--;
414   }
415
416   private int peek(int index)
417   {
418     if (0 <= index && index < _len)
419       return _string.charAt(index);
420     else
421       return -1;
422   }
423
424   private int read()
425   {
426     if (_offset < _len)
427       return _string.charAt(_offset++);
428     else
429       return -1;
430   }
431
432   private int skipWhitespace()
433   {
434     int ch = read();
435     for (; ch >= 0; ch = read()) {
436       if (ch != ' ' &&
437           ch != '\n' &&
438           ch != '\r' &&
439           ch != '\t')
440         break;
441     }
442
443     return ch;
444   }
445 }
446
Popular Tags