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

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