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 org.json;
18
19import java.util.ArrayList;
20import java.util.Arrays;
21import java.util.List;
22
23// Note: this class was written without inspecting the non-free org.json sourcecode.
24
25/**
26 * Implements {@link JSONObject#toString} and {@link JSONArray#toString}. Most
27 * application developers should use those methods directly and disregard this
28 * API. For example:<pre>
29 * JSONObject object = ...
30 * String json = object.toString();</pre>
31 *
32 * <p>Stringers only encode well-formed JSON strings. In particular:
33 * <ul>
34 *   <li>The stringer must have exactly one top-level array or object.
35 *   <li>Lexical scopes must be balanced: every call to {@link #array} must
36 *       have a matching call to {@link #endArray} and every call to {@link
37 *       #object} must have a matching call to {@link #endObject}.
38 *   <li>Arrays may not contain keys (property names).
39 *   <li>Objects must alternate keys (property names) and values.
40 *   <li>Values are inserted with either literal {@link #value(Object) value}
41 *       calls, or by nesting arrays or objects.
42 * </ul>
43 * Calls that would result in a malformed JSON string will fail with a
44 * {@link JSONException}.
45 *
46 * <p>This class provides no facility for pretty-printing (ie. indenting)
47 * output. To encode indented output, use {@link JSONObject#toString(int)} or
48 * {@link JSONArray#toString(int)}.
49 *
50 * <p>Some implementations of the API support at most 20 levels of nesting.
51 * Attempts to create more than 20 levels of nesting may fail with a {@link
52 * JSONException}.
53 *
54 * <p>Each stringer may be used to encode a single top level value. Instances of
55 * this class are not thread safe. Although this class is nonfinal, it was not
56 * designed for inheritance and should not be subclassed. In particular,
57 * self-use by overrideable methods is not specified. See <i>Effective Java</i>
58 * Item 17, "Design and Document or inheritance or else prohibit it" for further
59 * information.
60 */
61public class JSONStringer {
62
63    /** The output data, containing at most one top-level array or object. */
64    final StringBuilder out = new StringBuilder();
65
66    /**
67     * Lexical scoping elements within this stringer, necessary to insert the
68     * appropriate separator characters (ie. commas and colons) and to detect
69     * nesting errors.
70     */
71    enum Scope {
72
73        /**
74         * An array with no elements requires no separators or newlines before
75         * it is closed.
76         */
77        EMPTY_ARRAY,
78
79        /**
80         * A array with at least one value requires a comma and newline before
81         * the next element.
82         */
83        NONEMPTY_ARRAY,
84
85        /**
86         * An object with no keys or values requires no separators or newlines
87         * before it is closed.
88         */
89        EMPTY_OBJECT,
90
91        /**
92         * An object whose most recent element is a key. The next element must
93         * be a value.
94         */
95        DANGLING_KEY,
96
97        /**
98         * An object with at least one name/value pair requires a comma and
99         * newline before the next element.
100         */
101        NONEMPTY_OBJECT,
102
103        /**
104         * A special bracketless array needed by JSONStringer.join() and
105         * JSONObject.quote() only. Not used for JSON encoding.
106         */
107        NULL,
108    }
109
110    /**
111     * Unlike the original implementation, this stack isn't limited to 20
112     * levels of nesting.
113     */
114    private final List<Scope> stack = new ArrayList<Scope>();
115
116    /**
117     * A string containing a full set of spaces for a single level of
118     * indentation, or null for no pretty printing.
119     */
120    private final String indent;
121
122    public JSONStringer() {
123        indent = null;
124    }
125
126    JSONStringer(int indentSpaces) {
127        char[] indentChars = new char[indentSpaces];
128        Arrays.fill(indentChars, ' ');
129        indent = new String(indentChars);
130    }
131
132    /**
133     * Begins encoding a new array. Each call to this method must be paired with
134     * a call to {@link #endArray}.
135     *
136     * @return this stringer.
137     */
138    public JSONStringer array() throws JSONException {
139        return open(Scope.EMPTY_ARRAY, "[");
140    }
141
142    /**
143     * Ends encoding the current array.
144     *
145     * @return this stringer.
146     */
147    public JSONStringer endArray() throws JSONException {
148        return close(Scope.EMPTY_ARRAY, Scope.NONEMPTY_ARRAY, "]");
149    }
150
151    /**
152     * Begins encoding a new object. Each call to this method must be paired
153     * with a call to {@link #endObject}.
154     *
155     * @return this stringer.
156     */
157    public JSONStringer object() throws JSONException {
158        return open(Scope.EMPTY_OBJECT, "{");
159    }
160
161    /**
162     * Ends encoding the current object.
163     *
164     * @return this stringer.
165     */
166    public JSONStringer endObject() throws JSONException {
167        return close(Scope.EMPTY_OBJECT, Scope.NONEMPTY_OBJECT, "}");
168    }
169
170    /**
171     * Enters a new scope by appending any necessary whitespace and the given
172     * bracket.
173     */
174    JSONStringer open(Scope empty, String openBracket) throws JSONException {
175        if (stack.isEmpty() && out.length() > 0) {
176            throw new JSONException("Nesting problem: multiple top-level roots");
177        }
178        beforeValue();
179        stack.add(empty);
180        out.append(openBracket);
181        return this;
182    }
183
184    /**
185     * Closes the current scope by appending any necessary whitespace and the
186     * given bracket.
187     */
188    JSONStringer close(Scope empty, Scope nonempty, String closeBracket) throws JSONException {
189        Scope context = peek();
190        if (context != nonempty && context != empty) {
191            throw new JSONException("Nesting problem");
192        }
193
194        stack.remove(stack.size() - 1);
195        if (context == nonempty) {
196            newline();
197        }
198        out.append(closeBracket);
199        return this;
200    }
201
202    /**
203     * Returns the value on the top of the stack.
204     */
205    private Scope peek() throws JSONException {
206        if (stack.isEmpty()) {
207            throw new JSONException("Nesting problem");
208        }
209        return stack.get(stack.size() - 1);
210    }
211
212    /**
213     * Replace the value on the top of the stack with the given value.
214     */
215    private void replaceTop(Scope topOfStack) {
216        stack.set(stack.size() - 1, topOfStack);
217    }
218
219    /**
220     * Encodes {@code value}.
221     *
222     * @param value a {@link JSONObject}, {@link JSONArray}, String, Boolean,
223     *     Integer, Long, Double or null. May not be {@link Double#isNaN() NaNs}
224     *     or {@link Double#isInfinite() infinities}.
225     * @return this stringer.
226     */
227    public JSONStringer value(Object value) throws JSONException {
228        if (stack.isEmpty()) {
229            throw new JSONException("Nesting problem");
230        }
231
232        if (value instanceof JSONArray) {
233            ((JSONArray) value).writeTo(this);
234            return this;
235
236        } else if (value instanceof JSONObject) {
237            ((JSONObject) value).writeTo(this);
238            return this;
239        }
240
241        beforeValue();
242
243        if (value == null
244                || value instanceof Boolean
245                || value == JSONObject.NULL) {
246            out.append(value);
247
248        } else if (value instanceof Number) {
249            out.append(JSONObject.numberToString((Number) value));
250
251        } else {
252            string(value.toString());
253        }
254
255        return this;
256    }
257
258    /**
259     * Encodes {@code value} to this stringer.
260     *
261     * @return this stringer.
262     */
263    public JSONStringer value(boolean value) throws JSONException {
264        if (stack.isEmpty()) {
265            throw new JSONException("Nesting problem");
266        }
267        beforeValue();
268        out.append(value);
269        return this;
270    }
271
272    /**
273     * Encodes {@code value} to this stringer.
274     *
275     * @param value a finite value. May not be {@link Double#isNaN() NaNs} or
276     *     {@link Double#isInfinite() infinities}.
277     * @return this stringer.
278     */
279    public JSONStringer value(double value) throws JSONException {
280        if (stack.isEmpty()) {
281            throw new JSONException("Nesting problem");
282        }
283        beforeValue();
284        out.append(JSONObject.numberToString(value));
285        return this;
286    }
287
288    /**
289     * Encodes {@code value} to this stringer.
290     *
291     * @return this stringer.
292     */
293    public JSONStringer value(long value) throws JSONException {
294        if (stack.isEmpty()) {
295            throw new JSONException("Nesting problem");
296        }
297        beforeValue();
298        out.append(value);
299        return this;
300    }
301
302    private void string(String value) {
303        out.append("\"");
304        for (int i = 0, length = value.length(); i < length; i++) {
305            char c = value.charAt(i);
306
307            /*
308             * From RFC 4627, "All Unicode characters may be placed within the
309             * quotation marks except for the characters that must be escaped:
310             * quotation mark, reverse solidus, and the control characters
311             * (U+0000 through U+001F)."
312             */
313            switch (c) {
314                case '"':
315                case '\\':
316                case '/':
317                    out.append('\\').append(c);
318                    break;
319
320                case '\t':
321                    out.append("\\t");
322                    break;
323
324                case '\b':
325                    out.append("\\b");
326                    break;
327
328                case '\n':
329                    out.append("\\n");
330                    break;
331
332                case '\r':
333                    out.append("\\r");
334                    break;
335
336                case '\f':
337                    out.append("\\f");
338                    break;
339
340                default:
341                    if (c <= 0x1F) {
342                        out.append(String.format("\\u%04x", (int) c));
343                    } else {
344                        out.append(c);
345                    }
346                    break;
347            }
348
349        }
350        out.append("\"");
351    }
352
353    private void newline() {
354        if (indent == null) {
355            return;
356        }
357
358        out.append("\n");
359        for (int i = 0; i < stack.size(); i++) {
360            out.append(indent);
361        }
362    }
363
364    /**
365     * Encodes the key (property name) to this stringer.
366     *
367     * @param name the name of the forthcoming value. May not be null.
368     * @return this stringer.
369     */
370    public JSONStringer key(String name) throws JSONException {
371        if (name == null) {
372            throw new JSONException("Names must be non-null");
373        }
374        beforeKey();
375        string(name);
376        return this;
377    }
378
379    /**
380     * Inserts any necessary separators and whitespace before a name. Also
381     * adjusts the stack to expect the key's value.
382     */
383    private void beforeKey() throws JSONException {
384        Scope context = peek();
385        if (context == Scope.NONEMPTY_OBJECT) { // first in object
386            out.append(',');
387        } else if (context != Scope.EMPTY_OBJECT) { // not in an object!
388            throw new JSONException("Nesting problem");
389        }
390        newline();
391        replaceTop(Scope.DANGLING_KEY);
392    }
393
394    /**
395     * Inserts any necessary separators and whitespace before a literal value,
396     * inline array, or inline object. Also adjusts the stack to expect either a
397     * closing bracket or another element.
398     */
399    private void beforeValue() throws JSONException {
400        if (stack.isEmpty()) {
401            return;
402        }
403
404        Scope context = peek();
405        if (context == Scope.EMPTY_ARRAY) { // first in array
406            replaceTop(Scope.NONEMPTY_ARRAY);
407            newline();
408        } else if (context == Scope.NONEMPTY_ARRAY) { // another in array
409            out.append(',');
410            newline();
411        } else if (context == Scope.DANGLING_KEY) { // value for key
412            out.append(indent == null ? ":" : ": ");
413            replaceTop(Scope.NONEMPTY_OBJECT);
414        } else if (context != Scope.NULL) {
415            throw new JSONException("Nesting problem");
416        }
417    }
418
419    /**
420     * Returns the encoded JSON string.
421     *
422     * <p>If invoked with unterminated arrays or unclosed objects, this method's
423     * return value is undefined.
424     *
425     * <p><strong>Warning:</strong> although it contradicts the general contract
426     * of {@link Object#toString}, this method returns null if the stringer
427     * contains no data.
428     */
429    @Override public String toString() {
430        return out.length() == 0 ? null : out.toString();
431    }
432}
433