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 }