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