EMMA Coverage Report (generated Wed Aug 10 12:30:04 EDT 2011)
[all classes][com.google.gson.stream]

COVERAGE SUMMARY FOR SOURCE FILE [JsonWriter.java]

nameclass, %method, %block, %line, %
JsonWriter.java100% (2/2)79%  (23/29)57%  (327/575)62%  (84.4/137)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class JsonWriter100% (1/1)79%  (22/28)55%  (292/535)62%  (84.5/137)
close (): void 0%   (0/1)0%   (0/13)0%   (0/4)
flush (): void 0%   (0/1)0%   (0/4)0%   (0/2)
nullValue (): JsonWriter 0%   (0/1)0%   (0/9)0%   (0/3)
setIndent (String): void 0%   (0/1)0%   (0/17)0%   (0/6)
value (double): JsonWriter 0%   (0/1)0%   (0/29)0%   (0/5)
value (long): JsonWriter 0%   (0/1)0%   (0/10)0%   (0/3)
newline (): void 100% (1/1)17%  (4/23)33%  (2/6)
value (Number): JsonWriter 100% (1/1)40%  (18/45)67%  (5.3/8)
string (String): void 100% (1/1)53%  (53/100)46%  (12/26)
beforeValue (boolean): void 100% (1/1)60%  (38/63)80%  (13.6/17)
beforeName (): void 100% (1/1)61%  (20/33)88%  (7/8)
close (JsonScope, JsonScope, String): JsonWriter 100% (1/1)62%  (26/42)83%  (6.7/8)
name (String): JsonWriter 100% (1/1)64%  (9/14)80%  (4/5)
value (String): JsonWriter 100% (1/1)77%  (10/13)80%  (4/5)
JsonWriter (Writer): void 100% (1/1)81%  (21/26)88%  (7/8)
value (boolean): JsonWriter 100% (1/1)92%  (12/13)97%  (2.9/3)
beginArray (): JsonWriter 100% (1/1)100% (5/5)100% (1/1)
beginObject (): JsonWriter 100% (1/1)100% (5/5)100% (1/1)
endArray (): JsonWriter 100% (1/1)100% (6/6)100% (1/1)
endObject (): JsonWriter 100% (1/1)100% (6/6)100% (1/1)
isHtmlSafe (): boolean 100% (1/1)100% (3/3)100% (1/1)
isLenient (): boolean 100% (1/1)100% (3/3)100% (1/1)
open (JsonScope, String): JsonWriter 100% (1/1)100% (14/14)100% (4/4)
peek (): JsonScope 100% (1/1)100% (10/10)100% (1/1)
replaceTop (JsonScope): void 100% (1/1)100% (11/11)100% (2/2)
setHtmlSafe (boolean): void 100% (1/1)100% (4/4)100% (2/2)
setLenient (boolean): void 100% (1/1)100% (4/4)100% (2/2)
value (JsonLiteral): JsonWriter 100% (1/1)100% (10/10)100% (3/3)
     
class JsonWriter$1100% (1/1)100% (1/1)88%  (35/40)87%  (0.9/1)
<static initializer> 100% (1/1)88%  (35/40)87%  (0.9/1)

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

[all classes][com.google.gson.stream]
EMMA 2.0.5312 (C) Vladimir Roubtsov