001 /*
002 * Copyright (c) 2007 Mozilla Foundation
003 *
004 * Permission is hereby granted, free of charge, to any person obtaining a
005 * copy of this software and associated documentation files (the "Software"),
006 * to deal in the Software without restriction, including without limitation
007 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
008 * and/or sell copies of the Software, and to permit persons to whom the
009 * Software is furnished to do so, subject to the following conditions:
010 *
011 * The above copyright notice and this permission notice shall be included in
012 * all copies or substantial portions of the Software.
013 *
014 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
015 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
016 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
017 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
018 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
019 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
020 * DEALINGS IN THE SOFTWARE.
021 */
022
023 package nu.validator.json;
024
025 import java.io.IOException;
026 import java.io.OutputStream;
027 import java.io.OutputStreamWriter;
028 import java.io.Writer;
029 import java.nio.charset.Charset;
030 import java.nio.charset.CharsetEncoder;
031 import java.nio.charset.CodingErrorAction;
032 import java.util.ArrayList;
033 import java.util.List;
034
035 import org.xml.sax.SAXException;
036
037 public class Serializer implements JsonHandler {
038
039 private enum State {
040 INITIAL, DOCUMENT, ARRAY, OBJECT, VALUE, STRING
041 }
042
043 private final List<State> stack = new ArrayList<State>();
044
045 private boolean hadCallback = false;
046
047 private boolean first = false;
048
049 private final Writer writer;
050
051 private static Writer newOutputStreamWriter(OutputStream out) {
052 CharsetEncoder enc = Charset.forName("UTF-8").newEncoder();
053 enc.onMalformedInput(CodingErrorAction.REPLACE);
054 enc.onUnmappableCharacter(CodingErrorAction.REPLACE);
055 return new OutputStreamWriter(out, enc);
056 }
057
058 public Serializer(OutputStream out) {
059 this.writer = newOutputStreamWriter(out);
060 push(State.INITIAL);
061 }
062
063 private void push(State state) {
064 stack.add(state);
065 }
066
067 private void pop() {
068 stack.remove(stack.size() - 1);
069 }
070
071 private State peek() {
072 int size = stack.size();
073 if (size == 0) {
074 return null;
075 } else {
076 return stack.get(size - 1);
077 }
078 }
079
080 public void bool(boolean bool) throws SAXException {
081 try {
082 State state = peek();
083 switch (state) {
084 case ARRAY:
085 if (!first) {
086 writer.write(',');
087 }
088 // fall thru
089 case DOCUMENT:
090 case VALUE:
091 writer.write(Boolean.toString(bool));
092 if (state == State.VALUE) {
093 pop();
094 }
095 first = false;
096 break;
097 default:
098 throw new SAXException("Illegal state for callback.");
099 }
100 } catch (IOException e) {
101 throw new SAXException(e.getMessage(), e);
102 }
103 }
104
105 private void charactersImpl(char[] ch, int start, int length)
106 throws IOException {
107 int s = start;
108 int end = start + length;
109 for (int i = start; i < end; i++) {
110 char c = ch[i];
111 if (c <= '\u001F' || c == '\"' || c == '\\') {
112 if (s < i) {
113 writer.write(ch, s, i - s);
114 }
115 s = i + 1;
116 writer.write('\\');
117 switch (c) {
118 case '\"':
119 writer.write('\"');
120 break;
121 case '\\':
122 writer.write('\\');
123 break;
124 case '\u0008':
125 writer.write('b');
126 break;
127 case '\u000C':
128 writer.write('f');
129 break;
130 case '\n':
131 writer.write('n');
132 break;
133 case '\r':
134 writer.write('r');
135 break;
136 case '\t':
137 writer.write('t');
138 break;
139 default:
140 String hex = Integer.toHexString(c);
141 if (hex.length() == 1) {
142 writer.write("u000");
143 writer.write(hex);
144 } else {
145 writer.write("u00");
146 writer.write(hex);
147 }
148 break;
149 }
150 }
151 }
152 if (s < end) {
153 writer.write(ch, s, end - s);
154 }
155 }
156
157 public void characters(char[] ch, int start, int length)
158 throws SAXException {
159 try {
160 State state = peek();
161 switch (state) {
162 case STRING:
163 charactersImpl(ch, start, length);
164 break;
165 default:
166 throw new SAXException("Illegal state for callback.");
167 }
168 } catch (IOException e) {
169 throw new SAXException(e.getMessage(), e);
170 }
171 }
172
173 public void endArray() throws SAXException {
174 try {
175 State state = peek();
176 switch (state) {
177 case ARRAY:
178 writer.write(']');
179 pop();
180 first = false;
181 if (peek() == State.VALUE) {
182 pop();
183 }
184 break;
185 default:
186 throw new SAXException("Illegal state for callback.");
187 }
188 } catch (IOException e) {
189 throw new SAXException(e.getMessage(), e);
190 }
191 }
192
193 public void endDocument() throws SAXException {
194 try {
195 State state = peek();
196 switch (state) {
197 case DOCUMENT:
198 if (hadCallback) {
199 writer.write(')');
200 }
201 writer.write('\n');
202 writer.flush();
203 writer.close();
204 pop();
205 break;
206 default:
207 throw new SAXException("Illegal state for callback.");
208 }
209 } catch (IOException e) {
210 throw new SAXException(e.getMessage(), e);
211 }
212 }
213
214 public void endObject() throws SAXException {
215 try {
216 State state = peek();
217 switch (state) {
218 case OBJECT:
219 writer.write('}');
220 pop();
221 first = false;
222 if (peek() == State.VALUE) {
223 pop();
224 }
225 break;
226 default:
227 throw new SAXException("Illegal state for callback.");
228 }
229 } catch (IOException e) {
230 throw new SAXException(e.getMessage(), e);
231 }
232 }
233
234 public void endString() throws SAXException {
235 try {
236 State state = peek();
237 switch (state) {
238 case STRING:
239 writer.write('\"');
240 pop();
241 first = false;
242 if (peek() == State.VALUE) {
243 pop();
244 }
245 break;
246 default:
247 throw new SAXException("Illegal state for callback.");
248 }
249 } catch (IOException e) {
250 throw new SAXException(e.getMessage(), e);
251 }
252 }
253
254 public void key(String key) throws SAXException {
255 try {
256 State state = peek();
257 switch (state) {
258 case OBJECT:
259 if (!first) {
260 writer.write(',');
261 }
262 writer.write('\"');
263 charactersImpl(key.toCharArray(), 0, key.length());
264 writer.write('\"');
265 writer.write(':');
266 push(State.VALUE);
267 break;
268 default:
269 throw new SAXException("Illegal state for callback.");
270 }
271 } catch (IOException e) {
272 throw new SAXException(e.getMessage(), e);
273 }
274 }
275
276 public void number(int number) throws SAXException {
277 try {
278 State state = peek();
279 switch (state) {
280 case ARRAY:
281 if (!first) {
282 writer.write(',');
283 }
284 // fall thru
285 case DOCUMENT:
286 case VALUE:
287 writer.write(Integer.toString(number));
288 if (state == State.VALUE) {
289 pop();
290 }
291 first = false;
292 break;
293 default:
294 throw new SAXException("Illegal state for callback.");
295 }
296 } catch (IOException e) {
297 throw new SAXException(e.getMessage(), e);
298 }
299 }
300
301 public void number(long number) throws SAXException {
302 try {
303 State state = peek();
304 switch (state) {
305 case ARRAY:
306 if (!first) {
307 writer.write(',');
308 }
309 // fall thru
310 case DOCUMENT:
311 case VALUE:
312 writer.write(Long.toString(number));
313 if (state == State.VALUE) {
314 pop();
315 }
316 first = false;
317 break;
318 default:
319 throw new SAXException("Illegal state for callback.");
320 }
321 } catch (IOException e) {
322 throw new SAXException(e.getMessage(), e);
323 }
324 }
325
326 public void number(float number) throws SAXException {
327 try {
328 State state = peek();
329 switch (state) {
330 case ARRAY:
331 if (!first) {
332 writer.write(',');
333 }
334 // fall thru
335 case DOCUMENT:
336 case VALUE:
337 writer.write(Float.toString(number));
338 if (state == State.VALUE) {
339 pop();
340 }
341 first = false;
342 break;
343 default:
344 throw new SAXException("Illegal state for callback.");
345 }
346 } catch (IOException e) {
347 throw new SAXException(e.getMessage(), e);
348 }
349 }
350
351 public void number(double number) throws SAXException {
352 try {
353 State state = peek();
354 switch (state) {
355 case ARRAY:
356 if (!first) {
357 writer.write(',');
358 }
359 // fall thru
360 case DOCUMENT:
361 case VALUE:
362 writer.write(Double.toString(number));
363 if (state == State.VALUE) {
364 pop();
365 }
366 first = false;
367 break;
368 default:
369 throw new SAXException("Illegal state for callback.");
370 }
371 } catch (IOException e) {
372 throw new SAXException(e.getMessage(), e);
373 }
374 }
375
376 public void startArray() throws SAXException {
377 try {
378 State state = peek();
379 switch (state) {
380 case ARRAY:
381 if (!first) {
382 writer.write(',');
383 }
384 // fall thru
385 case DOCUMENT:
386 case VALUE:
387 writer.write('[');
388 push(State.ARRAY);
389 first = true;
390 break;
391 default:
392 throw new SAXException("Illegal state for callback.");
393 }
394 } catch (IOException e) {
395 throw new SAXException(e.getMessage(), e);
396 }
397 }
398
399 public void startDocument(String callback) throws SAXException {
400 try {
401 State state = peek();
402 switch (state) {
403 case INITIAL:
404 if (callback == null) {
405 hadCallback = false;
406 } else {
407 hadCallback = true;
408 writer.write(callback);
409 writer.write('(');
410 }
411 push(State.DOCUMENT);
412 first = true;
413 break;
414 default:
415 throw new SAXException("Illegal state for callback.");
416 }
417 } catch (IOException e) {
418 throw new SAXException(e.getMessage(), e);
419 }
420 }
421
422 public void startObject() throws SAXException {
423 try {
424 State state = peek();
425 switch (state) {
426 case ARRAY:
427 if (!first) {
428 writer.write(',');
429 }
430 // fall thru
431 case DOCUMENT:
432 case VALUE:
433 writer.write('{');
434 push(State.OBJECT);
435 first = true;
436 break;
437 default:
438 throw new SAXException("Illegal state for callback.");
439 }
440 } catch (IOException e) {
441 throw new SAXException(e.getMessage(), e);
442 }
443 }
444
445 public void startString() throws SAXException {
446 try {
447 State state = peek();
448 switch (state) {
449 case ARRAY:
450 if (!first) {
451 writer.write(',');
452 }
453 // fall thru
454 case DOCUMENT:
455 case VALUE:
456 writer.write('\"');
457 push(State.STRING);
458 break;
459 default:
460 throw new SAXException("Illegal state for callback.");
461 }
462 } catch (IOException e) {
463 throw new SAXException(e.getMessage(), e);
464 }
465 }
466
467 public void string(String string) throws SAXException {
468 try {
469 State state = peek();
470 switch (state) {
471 case ARRAY:
472 if (!first) {
473 writer.write(',');
474 }
475 // fall thru
476 case DOCUMENT:
477 case VALUE:
478 if (string == null) {
479 writer.write("null");
480 } else {
481 writer.write('\"');
482 charactersImpl(string.toCharArray(), 0, string.length());
483 writer.write('\"');
484 }
485 if (state == State.VALUE) {
486 pop();
487 }
488 first = false;
489 break;
490 default:
491 throw new SAXException("Illegal state for callback.");
492 }
493 } catch (IOException e) {
494 throw new SAXException(e.getMessage(), e);
495 }
496 }
497
498 }