1 /*
2 * $Id: JSONWriter.java,v 1.1 2007/10/22 16:23:08 oeuillot Exp $
3 */
4 package org.rcfaces.core.internal.util.json;
5
6 import java.io.IOException;
7 import java.io.Writer;
8
9 /*
10 * Copyright (c) 2006 JSON.org
11 *
12 * Permission is hereby granted, free of charge, to any person obtaining a copy
13 * of this software and associated documentation files (the "Software"), to deal
14 * in the Software without restriction, including without limitation the rights
15 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16 * copies of the Software, and to permit persons to whom the Software is
17 * furnished to do so, subject to the following conditions:
18 *
19 * The above copyright notice and this permission notice shall be included in
20 * all copies or substantial portions of the Software.
21 *
22 * The Software shall be used for Good, not Evil.
23 *
24 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30 * SOFTWARE.
31 */
32
33 /**
34 * JSONWriter provides a quick and convenient way of producing JSON text. The
35 * texts produced strictly conform to JSON syntax rules. No whitespace is added,
36 * so the results are ready for transmission or storage. Each instance of
37 * JSONWriter can produce one JSON text.
38 * <p>
39 * A JSONWriter instance provides a <code>value</code> method for appending
40 * values to the text, and a <code>key</code> method for adding keys before
41 * values in objects. There are <code>array</code> and <code>endArray</code>
42 * methods that make and bound array values, and <code>object</code> and
43 * <code>endObject</code> methods which make and bound object values. All of
44 * these methods return the JSONWriter instance, permitting a cascade style. For
45 * example,
46 *
47 * <pre>
48 * new JSONWriter(myWriter).object().key("JSON").value("Hello, World!")
49 * .endObject();
50 * </pre>
51 *
52 * which writes
53 *
54 * <pre>
55 * {"JSON":"Hello, World!"}
56 * </pre>
57 *
58 * <p>
59 * The first method called must be <code>array</code> or <code>object</code>.
60 * There are no methods for adding commas or colons. JSONWriter adds them for
61 * you. Objects and arrays can be nested up to 20 levels deep.
62 * <p>
63 * This can sometimes be easier than using a JSONObject to build a string.
64 *
65 * @author JSON.org
66 * @version 2
67 */
68 public class JSONWriter {
69 private static final int maxdepth = 20;
70
71 /**
72 * The comma flag determines if a comma should be output before the next
73 * value.
74 */
75 private boolean comma;
76
77 /**
78 * The current mode. Values: 'a' (array), 'd' (done), 'i' (initial), 'k'
79 * (key), 'o' (object).
80 */
81 protected char mode;
82
83 /**
84 * The object/array stack.
85 */
86 private char stack[];
87
88 /**
89 * The stack top index. A value of 0 indicates that the stack is empty.
90 */
91 private int top;
92
93 /**
94 * The writer that will receive the output.
95 */
96 protected Writer writer;
97
98 /**
99 * Make a fresh JSONWriter. It can be used to build one JSON text.
100 */
101 public JSONWriter(Writer w) {
102 this.comma = false;
103 this.mode = 'i';
104 this.stack = new char[maxdepth];
105 this.top = 0;
106 this.writer = w;
107 }
108
109 /**
110 * Append a value.
111 *
112 * @param s
113 * A string value.
114 * @return this
115 * @throws JSONException
116 * If the value is out of sequence.
117 */
118 private JSONWriter append(String s) throws JSONException {
119 if (s == null) {
120 throw new NullPointerException();
121 }
122
123 if (this.mode == 'o' || this.mode == 'a') {
124 try {
125 if (this.comma && this.mode == 'a') {
126 this.writer.write(',');
127 }
128 this.writer.write(s);
129
130 } catch (IOException e) {
131 throw new JSONException(e);
132 }
133
134 if (this.mode == 'o') {
135 this.mode = 'k';
136 }
137 this.comma = true;
138 return this;
139 }
140 throw new JSONException("Value out of sequence.");
141 }
142
143 /**
144 * Begin appending a new array. All values until the balancing
145 * <code>endArray</code> will be appended to this array. The
146 * <code>endArray</code> method must be called to mark the array's end.
147 *
148 * @return this
149 * @throws JSONException
150 * If the nesting is too deep, or if the object is started in
151 * the wrong place (for example as a key or after the end of the
152 * outermost array or object).
153 */
154 public JSONWriter array() throws JSONException {
155 if (this.mode == 'i' || this.mode == 'o' || this.mode == 'a') {
156 this.push('a');
157 this.append("[");
158 this.comma = false;
159 return this;
160 }
161 throw new JSONException("Misplaced array.");
162 }
163
164 /**
165 * End something.
166 *
167 * @param m
168 * Mode
169 * @param c
170 * Closing character
171 * @return this
172 * @throws JSONException
173 * If unbalanced.
174 */
175 private JSONWriter end(char m, char c) throws JSONException {
176 if (this.mode != m) {
177 throw new JSONException(m == 'o' ? "Misplaced endObject."
178 : "Misplaced endArray.");
179 }
180 this.pop(m);
181 try {
182 this.writer.write(c);
183 } catch (IOException e) {
184 throw new JSONException(e);
185 }
186 this.comma = true;
187 return this;
188 }
189
190 /**
191 * End an array. This method most be called to balance calls to
192 * <code>array</code>.
193 *
194 * @return this
195 * @throws JSONException
196 * If incorrectly nested.
197 */
198 public JSONWriter endArray() throws JSONException {
199 return this.end('a', ']');
200 }
201
202 /**
203 * End an object. This method most be called to balance calls to
204 * <code>object</code>.
205 *
206 * @return this
207 * @throws JSONException
208 * If incorrectly nested.
209 */
210 public JSONWriter endObject() throws JSONException {
211 return this.end('k', '}');
212 }
213
214 /**
215 * Append a key. The key will be associated with the next value. In an
216 * object, every value must be preceded by a key.
217 *
218 * @param s
219 * A key string.
220 * @return this
221 * @throws JSONException
222 * If the key is out of place. For example, keys do not belong
223 * in arrays or if the key is null.
224 */
225 public JSONWriter key(String s) throws JSONException {
226 if (s == null) {
227 throw new JSONException("Null key.");
228 }
229 if (this.mode == 'k') {
230 try {
231 if (this.comma) {
232 this.writer.write(',');
233 }
234 this.writer.write(JSONObject.quote(s));
235 this.writer.write(':');
236 this.comma = false;
237 this.mode = 'o';
238 return this;
239 } catch (IOException e) {
240 throw new JSONException(e);
241 }
242 }
243 throw new JSONException("Misplaced key.");
244 }
245
246 /**
247 * Begin appending a new object. All keys and values until the balancing
248 * <code>endObject</code> will be appended to this object. The
249 * <code>endObject</code> method must be called to mark the object's end.
250 *
251 * @return this
252 * @throws JSONException
253 * If the nesting is too deep, or if the object is started in
254 * the wrong place (for example as a key or after the end of the
255 * outermost array or object).
256 */
257 public JSONWriter object() throws JSONException {
258 if (this.mode == 'i') {
259 this.mode = 'o';
260 }
261 if (this.mode == 'o' || this.mode == 'a') {
262 this.append("{");
263 this.push('k');
264 this.comma = false;
265 return this;
266 }
267 throw new JSONException("Misplaced object.");
268
269 }
270
271 /**
272 * Pop an array or object scope.
273 *
274 * @param c
275 * The scope to close.
276 * @throws JSONException
277 * If nesting is wrong.
278 */
279 private void pop(char c) throws JSONException {
280 if (this.top <= 0 || this.stack[this.top - 1] != c) {
281 throw new JSONException("Nesting error.");
282 }
283 this.top -= 1;
284 this.mode = this.top == 0 ? 'd' : this.stack[this.top - 1];
285 }
286
287 /**
288 * Push an array or object scope.
289 *
290 * @param c
291 * The scope to open.
292 * @throws JSONException
293 * If nesting is too deep.
294 */
295 private void push(char c) throws JSONException {
296 if (this.top >= maxdepth) {
297 throw new JSONException("Nesting too deep.");
298 }
299 this.stack[this.top] = c;
300 this.mode = c;
301 this.top += 1;
302 }
303
304 /**
305 * Append either the value <code>true</code> or the value
306 * <code>false</code>.
307 *
308 * @param b
309 * A boolean.
310 * @return this
311 * @throws JSONException
312 */
313 public JSONWriter value(boolean b) throws JSONException {
314 return this.append(b ? "true" : "false");
315 }
316
317 /**
318 * Append a double value.
319 *
320 * @param d
321 * A double.
322 * @return this
323 * @throws JSONException
324 * If the number is not finite.
325 */
326 public JSONWriter value(double d) throws JSONException {
327 return this.value(new Double(d));
328 }
329
330 /**
331 * Append a long value.
332 *
333 * @param l
334 * A long.
335 * @return this
336 * @throws JSONException
337 */
338 public JSONWriter value(long l) throws JSONException {
339 return this.append(Long.toString(l));
340 }
341
342 /**
343 * Append an object value.
344 *
345 * @param o
346 * The object to append. It can be null, or a Boolean, Number,
347 * String, JSONObject, or JSONArray, or an object with a
348 * toJSONString() method.
349 * @return this
350 * @throws JSONException
351 * If the value is out of sequence.
352 */
353 public JSONWriter value(Object o) throws JSONException {
354 return this.append(JSONObject.valueToString(o));
355 }
356 }