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 
17 package com.android.json.stream;
18 
19 import java.io.Closeable;
20 import java.io.IOException;
21 import java.io.Writer;
22 import java.util.ArrayList;
23 import 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  */
120 public 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     /**
142      * Creates a new instance that writes a JSON-encoded stream to {@code out}.
143      * For best performance, ensure {@link Writer} is buffered; wrapping in
144      * {@link java.io.BufferedWriter BufferedWriter} if necessary.
145      */
JsonWriter(Writer out)146     public JsonWriter(Writer out) {
147         if (out == null) {
148             throw new NullPointerException("out == null");
149         }
150         this.out = out;
151     }
152 
153     /**
154      * Sets the indentation string to be repeated for each level of indentation
155      * in the encoded document. If {@code indent.isEmpty()} the encoded document
156      * will be compact. Otherwise the encoded document will be more
157      * human-readable.
158      *
159      * @param indent a string containing only whitespace.
160      */
setIndent(String indent)161     public void setIndent(String indent) {
162         if (indent.isEmpty()) {
163             this.indent = null;
164             this.separator = ":";
165         } else {
166             this.indent = indent;
167             this.separator = ": ";
168         }
169     }
170 
171     /**
172      * Begins encoding a new array. Each call to this method must be paired with
173      * a call to {@link #endArray}.
174      *
175      * @return this writer.
176      */
beginArray()177     public JsonWriter beginArray() throws IOException {
178         return open(JsonScope.EMPTY_ARRAY, "[");
179     }
180 
181     /**
182      * Ends encoding the current array.
183      *
184      * @return this writer.
185      */
endArray()186     public JsonWriter endArray() throws IOException {
187         return close(JsonScope.EMPTY_ARRAY, JsonScope.NONEMPTY_ARRAY, "]");
188     }
189 
190     /**
191      * Begins encoding a new object. Each call to this method must be paired
192      * with a call to {@link #endObject}.
193      *
194      * @return this writer.
195      */
beginObject()196     public JsonWriter beginObject() throws IOException {
197         return open(JsonScope.EMPTY_OBJECT, "{");
198     }
199 
200     /**
201      * Ends encoding the current object.
202      *
203      * @return this writer.
204      */
endObject()205     public JsonWriter endObject() throws IOException {
206         return close(JsonScope.EMPTY_OBJECT, JsonScope.NONEMPTY_OBJECT, "}");
207     }
208 
209     /**
210      * Enters a new scope by appending any necessary whitespace and the given
211      * bracket.
212      */
open(JsonScope empty, String openBracket)213     private JsonWriter open(JsonScope empty, String openBracket) throws IOException {
214         beforeValue(true);
215         stack.add(empty);
216         out.write(openBracket);
217         return this;
218     }
219 
220     /**
221      * Closes the current scope by appending any necessary whitespace and the
222      * given bracket.
223      */
close(JsonScope empty, JsonScope nonempty, String closeBracket)224     private JsonWriter close(JsonScope empty, JsonScope nonempty, String closeBracket)
225             throws IOException {
226         JsonScope context = peek();
227         if (context != nonempty && context != empty) {
228             throw new IllegalStateException("Nesting problem: " + stack);
229         }
230 
231         stack.remove(stack.size() - 1);
232         if (context == nonempty) {
233             newline();
234         }
235         out.write(closeBracket);
236         return this;
237     }
238 
239     /**
240      * Returns the value on the top of the stack.
241      */
peek()242     private JsonScope peek() {
243         return stack.get(stack.size() - 1);
244     }
245 
246     /**
247      * Replace the value on the top of the stack with the given value.
248      */
replaceTop(JsonScope topOfStack)249     private void replaceTop(JsonScope topOfStack) {
250         stack.set(stack.size() - 1, topOfStack);
251     }
252 
253     /**
254      * Encodes the property name.
255      *
256      * @param name the name of the forthcoming value. May not be null.
257      * @return this writer.
258      */
name(String name)259     public JsonWriter name(String name) throws IOException {
260         if (name == null) {
261             throw new NullPointerException("name == null");
262         }
263         beforeName();
264         string(name);
265         return this;
266     }
267 
268     /**
269      * Encodes {@code value}.
270      *
271      * @param value the literal string value, or null to encode a null literal.
272      * @return this writer.
273      */
value(String value)274     public JsonWriter value(String value) throws IOException {
275         if (value == null) {
276             return nullValue();
277         }
278         beforeValue(false);
279         string(value);
280         return this;
281     }
282 
283     /**
284      * Encodes {@code null}.
285      *
286      * @return this writer.
287      */
nullValue()288     public JsonWriter nullValue() throws IOException {
289         beforeValue(false);
290         out.write("null");
291         return this;
292     }
293 
294     /**
295      * Encodes {@code value}.
296      *
297      * @return this writer.
298      */
value(boolean value)299     public JsonWriter value(boolean value) throws IOException {
300         beforeValue(false);
301         out.write(value ? "true" : "false");
302         return this;
303     }
304 
305     /**
306      * Encodes {@code value}.
307      *
308      * @param value a finite value. May not be {@link Double#isNaN() NaNs} or
309      *     {@link Double#isInfinite() infinities}.
310      * @return this writer.
311      */
value(double value)312     public JsonWriter value(double value) throws IOException {
313         if (Double.isNaN(value) || Double.isInfinite(value)) {
314             throw new IllegalArgumentException("Numeric values must be finite, but was " + value);
315         }
316         beforeValue(false);
317         out.append(Double.toString(value));
318         return this;
319     }
320 
321     /**
322      * Encodes {@code value}.
323      *
324      * @return this writer.
325      */
value(long value)326     public JsonWriter value(long value) throws IOException {
327         beforeValue(false);
328         out.write(Long.toString(value));
329         return this;
330     }
331 
332     /**
333      * Ensures all buffered data is written to the underlying {@link Writer}
334      * and flushes that writer.
335      */
flush()336     public void flush() throws IOException {
337         out.flush();
338     }
339 
340     /**
341      * Flushes and closes this writer and the underlying {@link Writer}.
342      *
343      * @throws IOException if the JSON document is incomplete.
344      */
close()345     public void close() throws IOException {
346         out.close();
347 
348         if (peek() != JsonScope.NONEMPTY_DOCUMENT) {
349             throw new IOException("Incomplete document");
350         }
351     }
352 
string(String value)353     private void string(String value) throws IOException {
354         out.write("\"");
355         for (int i = 0, length = value.length(); i < length; i++) {
356             char c = value.charAt(i);
357 
358             /*
359              * From RFC 4627, "All Unicode characters may be placed within the
360              * quotation marks except for the characters that must be escaped:
361              * quotation mark, reverse solidus, and the control characters
362              * (U+0000 through U+001F)."
363              */
364             switch (c) {
365                 case '"':
366                 case '\\':
367                 case '/':
368                     out.write('\\');
369                     out.write(c);
370                     break;
371 
372                 case '\t':
373                     out.write("\\t");
374                     break;
375 
376                 case '\b':
377                     out.write("\\b");
378                     break;
379 
380                 case '\n':
381                     out.write("\\n");
382                     break;
383 
384                 case '\r':
385                     out.write("\\r");
386                     break;
387 
388                 case '\f':
389                     out.write("\\f");
390                     break;
391 
392                 default:
393                     if (c <= 0x1F) {
394                         out.write(String.format("\\u%04x", (int) c));
395                     } else {
396                         out.write(c);
397                     }
398                     break;
399             }
400 
401         }
402         out.write("\"");
403     }
404 
newline()405     private void newline() throws IOException {
406         if (indent == null) {
407             return;
408         }
409 
410         out.write("\n");
411         for (int i = 1; i < stack.size(); i++) {
412             out.write(indent);
413         }
414     }
415 
416     /**
417      * Inserts any necessary separators and whitespace before a name. Also
418      * adjusts the stack to expect the name's value.
419      */
beforeName()420     private void beforeName() throws IOException {
421         JsonScope context = peek();
422         if (context == JsonScope.NONEMPTY_OBJECT) { // first in object
423             out.write(',');
424         } else if (context != JsonScope.EMPTY_OBJECT) { // not in an object!
425             throw new IllegalStateException("Nesting problem: " + stack);
426         }
427         newline();
428         replaceTop(JsonScope.DANGLING_NAME);
429     }
430 
431     /**
432      * Inserts any necessary separators and whitespace before a literal value,
433      * inline array, or inline object. Also adjusts the stack to expect either a
434      * closing bracket or another element.
435      *
436      * @param root true if the value is a new array or object, the two values
437      *     permitted as top-level elements.
438      */
beforeValue(boolean root)439     private void beforeValue(boolean root) throws IOException {
440         switch (peek()) {
441             case EMPTY_DOCUMENT: // first in document
442                 if (!root) {
443                     throw new IllegalStateException(
444                             "JSON must start with an array or an object.");
445                 }
446                 replaceTop(JsonScope.NONEMPTY_DOCUMENT);
447                 break;
448 
449             case EMPTY_ARRAY: // first in array
450                 replaceTop(JsonScope.NONEMPTY_ARRAY);
451                 newline();
452                 break;
453 
454             case NONEMPTY_ARRAY: // another in array
455                 out.append(',');
456                 newline();
457                 break;
458 
459             case DANGLING_NAME: // value for name
460                 out.append(separator);
461                 replaceTop(JsonScope.NONEMPTY_OBJECT);
462                 break;
463 
464             case NONEMPTY_DOCUMENT:
465                 throw new IllegalStateException(
466                         "JSON must have only one top-level value.");
467 
468             default:
469                 throw new IllegalStateException("Nesting problem: " + stack);
470         }
471     }
472 }
473