View Javadoc

1   /*
2    * $Id: JSONReader.java 799110 2009-07-29 22:44:26Z musachy $
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *  http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  package org.apache.struts2.json;
22  
23  import java.text.CharacterIterator;
24  import java.text.StringCharacterIterator;
25  import java.util.ArrayList;
26  import java.util.HashMap;
27  import java.util.List;
28  import java.util.Map;
29  
30  /***
31   * <p>
32   * Deserializes and object from a JSON string
33   * </p>
34   */
35  class JSONReader {
36      private static final Object OBJECT_END = new Object();
37      private static final Object ARRAY_END = new Object();
38      private static final Object COLON = new Object();
39      private static final Object COMMA = new Object();
40      private static Map<Character, Character> escapes = new HashMap<Character, Character>();
41  
42      static {
43          escapes.put(new Character('"'), new Character('"'));
44          escapes.put(new Character('//'), new Character('//'));
45          escapes.put(new Character('/'), new Character('/'));
46          escapes.put(new Character('b'), new Character('\b'));
47          escapes.put(new Character('f'), new Character('\f'));
48          escapes.put(new Character('n'), new Character('\n'));
49          escapes.put(new Character('r'), new Character('\r'));
50          escapes.put(new Character('t'), new Character('\t'));
51      }
52  
53      private CharacterIterator it;
54      private char c;
55      private Object token;
56      private StringBuilder buf = new StringBuilder();
57  
58      private char next() {
59          this.c = this.it.next();
60  
61          return this.c;
62      }
63  
64      private void skipWhiteSpace() {
65          while (Character.isWhitespace(this.c)) {
66              this.next();
67          }
68      }
69  
70      public Object read(String string) throws JSONException {
71          this.it = new StringCharacterIterator(string);
72          this.c = this.it.first();
73  
74          return this.read();
75      }
76  
77      private Object read() throws JSONException {
78          Object ret = null;
79  
80          this.skipWhiteSpace();
81  
82          if (this.c == '"') {
83              this.next();
84              ret = this.string('"');
85          } else if (this.c == '\'') {
86              this.next();
87              ret = this.string('\'');
88          } else if (this.c == '[') {
89              this.next();
90              ret = this.array();
91          } else if (this.c == ']') {
92              ret = ARRAY_END;
93              this.next();
94          } else if (this.c == ',') {
95              ret = COMMA;
96              this.next();
97          } else if (this.c == '{') {
98              this.next();
99              ret = this.object();
100         } else if (this.c == '}') {
101             ret = OBJECT_END;
102             this.next();
103         } else if (this.c == ':') {
104             ret = COLON;
105             this.next();
106         } else if ((this.c == 't') && (this.next() == 'r') && (this.next() == 'u') && (this.next() == 'e')) {
107             ret = Boolean.TRUE;
108             this.next();
109         } else if ((this.c == 'f') && (this.next() == 'a') && (this.next() == 'l') && (this.next() == 's')
110                 && (this.next() == 'e')) {
111             ret = Boolean.FALSE;
112             this.next();
113         } else if ((this.c == 'n') && (this.next() == 'u') && (this.next() == 'l') && (this.next() == 'l')) {
114             ret = null;
115             this.next();
116         } else if (Character.isDigit(this.c) || (this.c == '-')) {
117             ret = this.number();
118         } else {
119             throw buildInvalidInputException();
120         }
121 
122         this.token = ret;
123 
124         return ret;
125     }
126 
127     @SuppressWarnings("unchecked")
128     private Map object() throws JSONException {
129         Map ret = new HashMap();
130         Object next = this.read();
131         if (next != OBJECT_END) {
132             String key = (String) next;
133             while (this.token != OBJECT_END) {
134                 this.read(); // should be a colon
135 
136                 if (this.token != OBJECT_END) {
137                     ret.put(key, this.read());
138 
139                     if (this.read() == COMMA) {
140                         Object name = this.read();
141 
142                         if (name instanceof String) {
143                             key = (String) name;
144                         } else
145                             throw buildInvalidInputException();
146                     }
147                 }
148             }
149         }
150 
151         return ret;
152     }
153 
154     private JSONException buildInvalidInputException() {
155         return new JSONException("Input string is not well formed JSON (invalid char " + this.c + ")");
156     }
157 
158     @SuppressWarnings("unchecked")
159     private List array() throws JSONException {
160         List ret = new ArrayList();
161         Object value = this.read();
162 
163         while (this.token != ARRAY_END) {
164             ret.add(value);
165 
166             Object read = this.read();
167             if (read == COMMA) {
168                 value = this.read();
169             } else if (read != ARRAY_END) {
170                 throw buildInvalidInputException();
171             }
172         }
173 
174         return ret;
175     }
176 
177     private Object number() {
178         this.buf.setLength(0);
179 
180         if (this.c == '-') {
181             this.add();
182         }
183 
184         this.addDigits();
185 
186         if (this.c == '.') {
187             this.add();
188             this.addDigits();
189         }
190 
191         if ((this.c == 'e') || (this.c == 'E')) {
192             this.add();
193 
194             if ((this.c == '+') || (this.c == '-')) {
195                 this.add();
196             }
197 
198             this.addDigits();
199         }
200 
201         return (this.buf.indexOf(".") >= 0) ? (Object) Double.parseDouble(this.buf.toString())
202                 : (Object) Long.parseLong(this.buf.toString());
203     }
204 
205     private Object string(char quote) {
206         this.buf.setLength(0);
207 
208         while ((this.c != quote) && (this.c != CharacterIterator.DONE)) {
209             if (this.c == '//') {
210                 this.next();
211 
212                 if (this.c == 'u') {
213                     this.add(this.unicode());
214                 } else {
215                     Object value = escapes.get(new Character(this.c));
216 
217                     if (value != null) {
218                         this.add(((Character) value).charValue());
219                     }
220                 }
221             } else {
222                 this.add();
223             }
224         }
225 
226         this.next();
227 
228         return this.buf.toString();
229     }
230 
231     private void add(char cc) {
232         this.buf.append(cc);
233         this.next();
234     }
235 
236     private void add() {
237         this.add(this.c);
238     }
239 
240     private void addDigits() {
241         while (Character.isDigit(this.c)) {
242             this.add();
243         }
244     }
245 
246     private char unicode() {
247         int value = 0;
248 
249         for (int i = 0; i < 4; ++i) {
250             switch (this.next()) {
251             case '0':
252             case '1':
253             case '2':
254             case '3':
255             case '4':
256             case '5':
257             case '6':
258             case '7':
259             case '8':
260             case '9':
261                 value = (value << 4) + (this.c - '0');
262 
263                 break;
264 
265             case 'a':
266             case 'b':
267             case 'c':
268             case 'd':
269             case 'e':
270             case 'f':
271                 value = (value << 4) + (this.c - 'W');
272 
273                 break;
274 
275             case 'A':
276             case 'B':
277             case 'C':
278             case 'D':
279             case 'E':
280             case 'F':
281                 value = (value << 4) + (this.c - '7');
282 
283                 break;
284             }
285         }
286 
287         return (char) value;
288     }
289 }