github.com/keysonZZZ/kmg@v0.0.0-20151121023212-05317bfd7d39/kmgRpc/kmgRpcJava/java/src/com/google/gson/stream/JsonWriter.java (about) 1 /* 2 * Copyright (C) 2010 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.gson.stream; 18 19 import java.io.Closeable; 20 import java.io.Flushable; 21 import java.io.IOException; 22 import java.io.Writer; 23 24 import static com.google.gson.stream.JsonScope.DANGLING_NAME; 25 import static com.google.gson.stream.JsonScope.EMPTY_ARRAY; 26 import static com.google.gson.stream.JsonScope.EMPTY_DOCUMENT; 27 import static com.google.gson.stream.JsonScope.EMPTY_OBJECT; 28 import static com.google.gson.stream.JsonScope.NONEMPTY_ARRAY; 29 import static com.google.gson.stream.JsonScope.NONEMPTY_DOCUMENT; 30 import static com.google.gson.stream.JsonScope.NONEMPTY_OBJECT; 31 32 /** 33 * Writes a JSON (<a href="http://www.ietf.org/rfc/rfc4627.txt">RFC 4627</a>) 34 * encoded value to a stream, one token at a time. The stream includes both 35 * literal values (strings, numbers, booleans and nulls) as well as the begin 36 * and end delimiters of objects and arrays. 37 * 38 * <h3>Encoding JSON</h3> 39 * To encode your data as JSON, create a new {@code JsonWriter}. Each JSON 40 * document must contain one top-level array or object. Call methods on the 41 * writer as you walk the structure's contents, nesting arrays and objects as 42 * necessary: 43 * <ul> 44 * <li>To write <strong>arrays</strong>, first call {@link #beginArray()}. 45 * Write each of the array's elements with the appropriate {@link #value} 46 * methods or by nesting other arrays and objects. Finally close the array 47 * using {@link #endArray()}. 48 * <li>To write <strong>objects</strong>, first call {@link #beginObject()}. 49 * Write each of the object's properties by alternating calls to 50 * {@link #name} with the property's value. Write property values with the 51 * appropriate {@link #value} method or by nesting other objects or arrays. 52 * Finally close the object using {@link #endObject()}. 53 * </ul> 54 * 55 * <h3>Example</h3> 56 * Suppose we'd like to encode a stream of messages such as the following: <pre> {@code 57 * [ 58 * { 59 * "id": 912345678901, 60 * "text": "How do I stream JSON in Java?", 61 * "geo": null, 62 * "user": { 63 * "name": "json_newb", 64 * "followers_count": 41 65 * } 66 * }, 67 * { 68 * "id": 912345678902, 69 * "text": "@json_newb just use JsonWriter!", 70 * "geo": [50.454722, -104.606667], 71 * "user": { 72 * "name": "jesse", 73 * "followers_count": 2 74 * } 75 * } 76 * ]}</pre> 77 * This code encodes the above structure: <pre> {@code 78 * public void writeJsonStream(OutputStream out, List<Message> messages) throws IOException { 79 * JsonWriter writer = new JsonWriter(new OutputStreamWriter(out, "UTF-8")); 80 * writer.setIndentSpaces(4); 81 * writeMessagesArray(writer, messages); 82 * writer.close(); 83 * } 84 * 85 * public void writeMessagesArray(JsonWriter writer, List<Message> messages) throws IOException { 86 * writer.beginArray(); 87 * for (Message message : messages) { 88 * writeMessage(writer, message); 89 * } 90 * writer.endArray(); 91 * } 92 * 93 * public void writeMessage(JsonWriter writer, Message message) throws IOException { 94 * writer.beginObject(); 95 * writer.name("id").value(message.getId()); 96 * writer.name("text").value(message.getText()); 97 * if (message.getGeo() != null) { 98 * writer.name("geo"); 99 * writeDoublesArray(writer, message.getGeo()); 100 * } else { 101 * writer.name("geo").nullValue(); 102 * } 103 * writer.name("user"); 104 * writeUser(writer, message.getUser()); 105 * writer.endObject(); 106 * } 107 * 108 * public void writeUser(JsonWriter writer, User user) throws IOException { 109 * writer.beginObject(); 110 * writer.name("name").value(user.getName()); 111 * writer.name("followers_count").value(user.getFollowersCount()); 112 * writer.endObject(); 113 * } 114 * 115 * public void writeDoublesArray(JsonWriter writer, List<Double> doubles) throws IOException { 116 * writer.beginArray(); 117 * for (Double value : doubles) { 118 * writer.value(value); 119 * } 120 * writer.endArray(); 121 * }}</pre> 122 * 123 * <p>Each {@code JsonWriter} may be used to write a single JSON stream. 124 * Instances of this class are not thread safe. Calls that would result in a 125 * malformed JSON string will fail with an {@link IllegalStateException}. 126 * 127 * @author Jesse Wilson 128 * @since 1.6 129 */ 130 public class JsonWriter implements Closeable, Flushable { 131 132 /* 133 * From RFC 4627, "All Unicode characters may be placed within the 134 * quotation marks except for the characters that must be escaped: 135 * quotation mark, reverse solidus, and the control characters 136 * (U+0000 through U+001F)." 137 * 138 * We also escape '\u2028' and '\u2029', which JavaScript interprets as 139 * newline characters. This prevents eval() from failing with a syntax 140 * error. http://code.google.com/p/google-gson/issues/detail?id=341 141 */ 142 private static final String[] REPLACEMENT_CHARS; 143 private static final String[] HTML_SAFE_REPLACEMENT_CHARS; 144 static { 145 REPLACEMENT_CHARS = new String[128]; 146 for (int i = 0; i <= 0x1f; i++) { 147 REPLACEMENT_CHARS[i] = String.format("\\u%04x", (int) i); 148 } 149 REPLACEMENT_CHARS['"'] = "\\\""; 150 REPLACEMENT_CHARS['\\'] = "\\\\"; 151 REPLACEMENT_CHARS['\t'] = "\\t"; 152 REPLACEMENT_CHARS['\b'] = "\\b"; 153 REPLACEMENT_CHARS['\n'] = "\\n"; 154 REPLACEMENT_CHARS['\r'] = "\\r"; 155 REPLACEMENT_CHARS['\f'] = "\\f"; 156 HTML_SAFE_REPLACEMENT_CHARS = REPLACEMENT_CHARS.clone(); 157 HTML_SAFE_REPLACEMENT_CHARS['<'] = "\\u003c"; 158 HTML_SAFE_REPLACEMENT_CHARS['>'] = "\\u003e"; 159 HTML_SAFE_REPLACEMENT_CHARS['&'] = "\\u0026"; 160 HTML_SAFE_REPLACEMENT_CHARS['='] = "\\u003d"; 161 HTML_SAFE_REPLACEMENT_CHARS['\''] = "\\u0027"; 162 } 163 164 /** The output data, containing at most one top-level array or object. */ 165 private final Writer out; 166 167 private int[] stack = new int[32]; 168 private int stackSize = 0; 169 { 170 push(EMPTY_DOCUMENT); 171 } 172 173 /** 174 * A string containing a full set of spaces for a single level of 175 * indentation, or null for no pretty printing. 176 */ 177 private String indent; 178 179 /** 180 * The name/value separator; either ":" or ": ". 181 */ 182 private String separator = ":"; 183 184 private boolean lenient; 185 186 private boolean htmlSafe; 187 188 private String deferredName; 189 190 private boolean serializeNulls = true; 191 192 /** 193 * Creates a new instance that writes a JSON-encoded stream to {@code out}. 194 * For best performance, ensure {@link Writer} is buffered; wrapping in 195 * {@link java.io.BufferedWriter BufferedWriter} if necessary. 196 */ 197 public JsonWriter(Writer out) { 198 if (out == null) { 199 throw new NullPointerException("out == null"); 200 } 201 this.out = out; 202 } 203 204 /** 205 * Sets the indentation string to be repeated for each level of indentation 206 * in the encoded document. If {@code indent.isEmpty()} the encoded document 207 * will be compact. Otherwise the encoded document will be more 208 * human-readable. 209 * 210 * @param indent a string containing only whitespace. 211 */ 212 public final void setIndent(String indent) { 213 if (indent.length() == 0) { 214 this.indent = null; 215 this.separator = ":"; 216 } else { 217 this.indent = indent; 218 this.separator = ": "; 219 } 220 } 221 222 /** 223 * Configure this writer to relax its syntax rules. By default, this writer 224 * only emits well-formed JSON as specified by <a 225 * href="http://www.ietf.org/rfc/rfc4627.txt">RFC 4627</a>. Setting the writer 226 * to lenient permits the following: 227 * <ul> 228 * <li>Top-level values of any type. With strict writing, the top-level 229 * value must be an object or an array. 230 * <li>Numbers may be {@link Double#isNaN() NaNs} or {@link 231 * Double#isInfinite() infinities}. 232 * </ul> 233 */ 234 public final void setLenient(boolean lenient) { 235 this.lenient = lenient; 236 } 237 238 /** 239 * Returns true if this writer has relaxed syntax rules. 240 */ 241 public boolean isLenient() { 242 return lenient; 243 } 244 245 /** 246 * Configure this writer to emit JSON that's safe for direct inclusion in HTML 247 * and XML documents. This escapes the HTML characters {@code <}, {@code >}, 248 * {@code &} and {@code =} before writing them to the stream. Without this 249 * setting, your XML/HTML encoder should replace these characters with the 250 * corresponding escape sequences. 251 */ 252 public final void setHtmlSafe(boolean htmlSafe) { 253 this.htmlSafe = htmlSafe; 254 } 255 256 /** 257 * Returns true if this writer writes JSON that's safe for inclusion in HTML 258 * and XML documents. 259 */ 260 public final boolean isHtmlSafe() { 261 return htmlSafe; 262 } 263 264 /** 265 * Sets whether object members are serialized when their value is null. 266 * This has no impact on array elements. The default is true. 267 */ 268 public final void setSerializeNulls(boolean serializeNulls) { 269 this.serializeNulls = serializeNulls; 270 } 271 272 /** 273 * Returns true if object members are serialized when their value is null. 274 * This has no impact on array elements. The default is true. 275 */ 276 public final boolean getSerializeNulls() { 277 return serializeNulls; 278 } 279 280 /** 281 * Begins encoding a new array. Each call to this method must be paired with 282 * a call to {@link #endArray}. 283 * 284 * @return this writer. 285 */ 286 public JsonWriter beginArray() throws IOException { 287 writeDeferredName(); 288 return open(EMPTY_ARRAY, "["); 289 } 290 291 /** 292 * Ends encoding the current array. 293 * 294 * @return this writer. 295 */ 296 public JsonWriter endArray() throws IOException { 297 return close(EMPTY_ARRAY, NONEMPTY_ARRAY, "]"); 298 } 299 300 /** 301 * Begins encoding a new object. Each call to this method must be paired 302 * with a call to {@link #endObject}. 303 * 304 * @return this writer. 305 */ 306 public JsonWriter beginObject() throws IOException { 307 writeDeferredName(); 308 return open(EMPTY_OBJECT, "{"); 309 } 310 311 /** 312 * Ends encoding the current object. 313 * 314 * @return this writer. 315 */ 316 public JsonWriter endObject() throws IOException { 317 return close(EMPTY_OBJECT, NONEMPTY_OBJECT, "}"); 318 } 319 320 /** 321 * Enters a new scope by appending any necessary whitespace and the given 322 * bracket. 323 */ 324 private JsonWriter open(int empty, String openBracket) throws IOException { 325 beforeValue(true); 326 push(empty); 327 out.write(openBracket); 328 return this; 329 } 330 331 /** 332 * Closes the current scope by appending any necessary whitespace and the 333 * given bracket. 334 */ 335 private JsonWriter close(int empty, int nonempty, String closeBracket) 336 throws IOException { 337 int context = peek(); 338 if (context != nonempty && context != empty) { 339 throw new IllegalStateException("Nesting problem."); 340 } 341 if (deferredName != null) { 342 throw new IllegalStateException("Dangling name: " + deferredName); 343 } 344 345 stackSize--; 346 if (context == nonempty) { 347 newline(); 348 } 349 out.write(closeBracket); 350 return this; 351 } 352 353 private void push(int newTop) { 354 if (stackSize == stack.length) { 355 int[] newStack = new int[stackSize * 2]; 356 System.arraycopy(stack, 0, newStack, 0, stackSize); 357 stack = newStack; 358 } 359 stack[stackSize++] = newTop; 360 } 361 362 /** 363 * Returns the value on the top of the stack. 364 */ 365 private int peek() { 366 if (stackSize == 0) { 367 throw new IllegalStateException("JsonWriter is closed."); 368 } 369 return stack[stackSize - 1]; 370 } 371 372 /** 373 * Replace the value on the top of the stack with the given value. 374 */ 375 private void replaceTop(int topOfStack) { 376 stack[stackSize - 1] = topOfStack; 377 } 378 379 /** 380 * Encodes the property name. 381 * 382 * @param name the name of the forthcoming value. May not be null. 383 * @return this writer. 384 */ 385 public JsonWriter name(String name) throws IOException { 386 if (name == null) { 387 throw new NullPointerException("name == null"); 388 } 389 if (deferredName != null) { 390 throw new IllegalStateException(); 391 } 392 if (stackSize == 0) { 393 throw new IllegalStateException("JsonWriter is closed."); 394 } 395 deferredName = name; 396 return this; 397 } 398 399 private void writeDeferredName() throws IOException { 400 if (deferredName != null) { 401 beforeName(); 402 string(deferredName); 403 deferredName = null; 404 } 405 } 406 407 /** 408 * Encodes {@code value}. 409 * 410 * @param value the literal string value, or null to encode a null literal. 411 * @return this writer. 412 */ 413 public JsonWriter value(String value) throws IOException { 414 if (value == null) { 415 return nullValue(); 416 } 417 writeDeferredName(); 418 beforeValue(false); 419 string(value); 420 return this; 421 } 422 423 /** 424 * Encodes {@code null}. 425 * 426 * @return this writer. 427 */ 428 public JsonWriter nullValue() throws IOException { 429 if (deferredName != null) { 430 if (serializeNulls) { 431 writeDeferredName(); 432 } else { 433 deferredName = null; 434 return this; // skip the name and the value 435 } 436 } 437 beforeValue(false); 438 out.write("null"); 439 return this; 440 } 441 442 /** 443 * Encodes {@code value}. 444 * 445 * @return this writer. 446 */ 447 public JsonWriter value(boolean value) throws IOException { 448 writeDeferredName(); 449 beforeValue(false); 450 out.write(value ? "true" : "false"); 451 return this; 452 } 453 454 /** 455 * Encodes {@code value}. 456 * 457 * @param value a finite value. May not be {@link Double#isNaN() NaNs} or 458 * {@link Double#isInfinite() infinities}. 459 * @return this writer. 460 */ 461 public JsonWriter value(double value) throws IOException { 462 if (Double.isNaN(value) || Double.isInfinite(value)) { 463 throw new IllegalArgumentException("Numeric values must be finite, but was " + value); 464 } 465 writeDeferredName(); 466 beforeValue(false); 467 out.append(Double.toString(value)); 468 return this; 469 } 470 471 /** 472 * Encodes {@code value}. 473 * 474 * @return this writer. 475 */ 476 public JsonWriter value(long value) throws IOException { 477 writeDeferredName(); 478 beforeValue(false); 479 out.write(Long.toString(value)); 480 return this; 481 } 482 483 /** 484 * Encodes {@code value}. 485 * 486 * @param value a finite value. May not be {@link Double#isNaN() NaNs} or 487 * {@link Double#isInfinite() infinities}. 488 * @return this writer. 489 */ 490 public JsonWriter value(Number value) throws IOException { 491 if (value == null) { 492 return nullValue(); 493 } 494 495 writeDeferredName(); 496 String string = value.toString(); 497 if (!lenient 498 && (string.equals("-Infinity") || string.equals("Infinity") || string.equals("NaN"))) { 499 throw new IllegalArgumentException("Numeric values must be finite, but was " + value); 500 } 501 beforeValue(false); 502 out.append(string); 503 return this; 504 } 505 506 /** 507 * Ensures all buffered data is written to the underlying {@link Writer} 508 * and flushes that writer. 509 */ 510 public void flush() throws IOException { 511 if (stackSize == 0) { 512 throw new IllegalStateException("JsonWriter is closed."); 513 } 514 out.flush(); 515 } 516 517 /** 518 * Flushes and closes this writer and the underlying {@link Writer}. 519 * 520 * @throws IOException if the JSON document is incomplete. 521 */ 522 public void close() throws IOException { 523 out.close(); 524 525 int size = stackSize; 526 if (size > 1 || size == 1 && stack[size - 1] != NONEMPTY_DOCUMENT) { 527 throw new IOException("Incomplete document"); 528 } 529 stackSize = 0; 530 } 531 532 private void string(String value) throws IOException { 533 String[] replacements = htmlSafe ? HTML_SAFE_REPLACEMENT_CHARS : REPLACEMENT_CHARS; 534 out.write("\""); 535 int last = 0; 536 int length = value.length(); 537 for (int i = 0; i < length; i++) { 538 char c = value.charAt(i); 539 String replacement; 540 if (c < 128) { 541 replacement = replacements[c]; 542 if (replacement == null) { 543 continue; 544 } 545 } else if (c == '\u2028') { 546 replacement = "\\u2028"; 547 } else if (c == '\u2029') { 548 replacement = "\\u2029"; 549 } else { 550 continue; 551 } 552 if (last < i) { 553 out.write(value, last, i - last); 554 } 555 out.write(replacement); 556 last = i + 1; 557 } 558 if (last < length) { 559 out.write(value, last, length - last); 560 } 561 out.write("\""); 562 } 563 564 private void newline() throws IOException { 565 if (indent == null) { 566 return; 567 } 568 569 out.write("\n"); 570 for (int i = 1, size = stackSize; i < size; i++) { 571 out.write(indent); 572 } 573 } 574 575 /** 576 * Inserts any necessary separators and whitespace before a name. Also 577 * adjusts the stack to expect the name's value. 578 */ 579 private void beforeName() throws IOException { 580 int context = peek(); 581 if (context == NONEMPTY_OBJECT) { // first in object 582 out.write(','); 583 } else if (context != EMPTY_OBJECT) { // not in an object! 584 throw new IllegalStateException("Nesting problem."); 585 } 586 newline(); 587 replaceTop(DANGLING_NAME); 588 } 589 590 /** 591 * Inserts any necessary separators and whitespace before a literal value, 592 * inline array, or inline object. Also adjusts the stack to expect either a 593 * closing bracket or another element. 594 * 595 * @param root true if the value is a new array or object, the two values 596 * permitted as top-level elements. 597 */ 598 @SuppressWarnings("fallthrough") 599 private void beforeValue(boolean root) throws IOException { 600 switch (peek()) { 601 case NONEMPTY_DOCUMENT: 602 if (!lenient) { 603 throw new IllegalStateException( 604 "JSON must have only one top-level value."); 605 } 606 // fall-through 607 case EMPTY_DOCUMENT: // first in document 608 if (!lenient && !root) { 609 throw new IllegalStateException( 610 "JSON must start with an array or an object."); 611 } 612 replaceTop(NONEMPTY_DOCUMENT); 613 break; 614 615 case EMPTY_ARRAY: // first in array 616 replaceTop(NONEMPTY_ARRAY); 617 newline(); 618 break; 619 620 case NONEMPTY_ARRAY: // another in array 621 out.append(','); 622 newline(); 623 break; 624 625 case DANGLING_NAME: // value for name 626 out.append(separator); 627 replaceTop(NONEMPTY_OBJECT); 628 break; 629 630 default: 631 throw new IllegalStateException("Nesting problem."); 632 } 633 } 634 }