View Javadoc

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(&quot;JSON&quot;).value(&quot;Hello, World!&quot;)
49   *         .endObject();
50   * </pre>
51   * 
52   * which writes
53   * 
54   * <pre>
55   * {&quot;JSON&quot;:&quot;Hello, World!&quot;}
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 }