View Javadoc

1   /*
2    * Copyright 2002-2009 the original author or authors.
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  package net.sf.json.util;
17  
18  import java.io.IOException;
19  import java.io.Writer;
20  
21  import net.sf.json.JSONException;
22  
23  
24  /**
25   * JSONBuilder provides a quick and convenient way of producing JSON text. The
26   * texts produced strictly conform to JSON syntax rules. No whitespace is added,
27   * so the results are ready for transmission or storage. Each instance of
28   * JSONWriter can produce one JSON text.
29   * <p>
30   * A JSONBuilder instance provides a <code>value</code> method for appending
31   * values to the text, and a <code>key</code> method for adding keys before
32   * values in objects. There are <code>array</code> and <code>endArray</code>
33   * methods that make and bound array values, and <code>object</code> and
34   * <code>endObject</code> methods which make and bound object values. All of
35   * these methods return the JSONBuilder instance, permitting a cascade style.
36   * For example,
37   *
38   * <pre>
39   * new JSONBuilder(myWriter)
40   *     .object()
41   *         .key("JSON")
42   *         .value("Hello, World!")
43   *     .endObject();</pre>
44   *
45   * which writes
46   *
47   * <pre>
48   * {"JSON":"Hello, World!"}</pre>
49   *
50   * <p>
51   * The first method called must be <code>array</code> or <code>object</code>.
52   * There are no methods for adding commas or colons. JSONBuilder adds them for
53   * you. Objects and arrays can be nested up to 20 levels deep.
54   * <p>
55   * This can sometimes be easier than using a JSONObject to build a string.
56   *
57   * @author JSON.org
58   * @version 1
59   */
60  public class JSONBuilder {
61     private static final int MAXDEPTH = 20;
62  
63     /**
64      * The comma flag determines if a comma should be output before the next
65      * value.
66      */
67     private boolean comma;
68  
69     /**
70      * The current mode. Values: 'a' (array), 'd' (done), 'i' (initial), 'k'
71      * (key), 'o' (object).
72      */
73     protected char mode;
74  
75     /**
76      * The object/array stack.
77      */
78     private char stack[];
79  
80     /**
81      * The stack top index. A value of 0 indicates that the stack is empty.
82      */
83     private int top;
84  
85     /**
86      * The writer that will receive the output.
87      */
88     protected Writer writer;
89  
90     /**
91      * Make a fresh JSONBuilder. It can be used to build one JSON text.
92      */
93     public JSONBuilder( Writer w ) {
94        this.comma = false;
95        this.mode = 'i';
96        this.stack = new char[MAXDEPTH];
97        this.top = 0;
98        this.writer = w;
99     }
100 
101    /**
102     * Append a value.
103     *
104     * @param s A string value.
105     * @return this
106     * @throws JSONException If the value is out of sequence.
107     */
108    private JSONBuilder append( String s ) {
109       if( s == null ){
110          throw new JSONException( "Null pointer" );
111       }
112       if( this.mode == 'o' || this.mode == 'a' ){
113          try{
114             if( this.comma && this.mode == 'a' ){
115                this.writer.write( ',' );
116             }
117             this.writer.write( s );
118          }catch( IOException e ){
119             throw new JSONException( e );
120          }
121          if( this.mode == 'o' ){
122             this.mode = 'k';
123          }
124          this.comma = true;
125          return this;
126       }
127       throw new JSONException( "Value out of sequence." );
128    }
129 
130    /**
131     * Begin appending a new array. All values until the balancing
132     * <code>endArray</code> will be appended to this array. The
133     * <code>endArray</code> method must be called to mark the array's end.
134     *
135     * @return this
136     * @throws JSONException If the nesting is too deep, or if the object is
137     *         started in the wrong place (for example as a key or after the end
138     *         of the outermost array or object).
139     */
140    public JSONBuilder array() {
141       if( this.mode == 'i' || this.mode == 'o' || this.mode == 'a' ){
142          this.push( 'a' );
143          this.append( "[" );
144          this.comma = false;
145          return this;
146       }
147       throw new JSONException( "Misplaced array." );
148    }
149 
150    /**
151     * End something.
152     *
153     * @param m Mode
154     * @param c Closing character
155     * @return this
156     * @throws JSONException If unbalanced.
157     */
158    private JSONBuilder end( char m, char c ) {
159       if( this.mode != m ){
160          throw new JSONException( m == 'o' ? "Misplaced endObject." : "Misplaced endArray." );
161       }
162       this.pop( m );
163       try{
164          this.writer.write( c );
165       }catch( IOException e ){
166          throw new JSONException( e );
167       }
168       this.comma = true;
169       return this;
170    }
171 
172    /**
173     * End an array. This method most be called to balance calls to
174     * <code>array</code>.
175     *
176     * @return this
177     * @throws JSONException If incorrectly nested.
178     */
179    public JSONBuilder endArray() {
180       return this.end( 'a', ']' );
181    }
182 
183    /**
184     * End an object. This method most be called to balance calls to
185     * <code>object</code>.
186     *
187     * @return this
188     * @throws JSONException If incorrectly nested.
189     */
190    public JSONBuilder endObject() {
191       return this.end( 'k', '}' );
192    }
193 
194    /**
195     * Append a key. The key will be associated with the next value. In an
196     * object, every value must be preceded by a key.
197     *
198     * @param s A key string.
199     * @return this
200     * @throws JSONException If the key is out of place. For example, keys do not
201     *         belong in arrays or if the key is null.
202     */
203    public JSONBuilder key( String s ) {
204       if( s == null ){
205          throw new JSONException( "Null key." );
206       }
207       if( this.mode == 'k' ){
208          try{
209             if( this.comma ){
210                this.writer.write( ',' );
211             }
212             this.writer.write( JSONUtils.quote( s ) );
213             this.writer.write( ':' );
214             this.comma = false;
215             this.mode = 'o';
216             return this;
217          }catch( IOException e ){
218             throw new JSONException( e );
219          }
220       }
221       throw new JSONException( "Misplaced key." );
222    }
223 
224    /**
225     * Begin appending a new object. All keys and values until the balancing
226     * <code>endObject</code> will be appended to this object. The
227     * <code>endObject</code> method must be called to mark the object's end.
228     *
229     * @return this
230     * @throws JSONException If the nesting is too deep, or if the object is
231     *         started in the wrong place (for example as a key or after the end
232     *         of the outermost array or object).
233     */
234    public JSONBuilder object() {
235       if( this.mode == 'i' ){
236          this.mode = 'o';
237       }
238       if( this.mode == 'o' || this.mode == 'a' ){
239          this.append( "{" );
240          this.push( 'k' );
241          this.comma = false;
242          return this;
243       }
244       throw new JSONException( "Misplaced object." );
245 
246    }
247 
248    /**
249     * Pop an array or object scope.
250     *
251     * @param c The scope to close.
252     * @throws JSONException If nesting is wrong.
253     */
254    private void pop( char c ) {
255       if( this.top <= 0 || this.stack[this.top - 1] != c ){
256          throw new JSONException( "Nesting error." );
257       }
258       this.top -= 1;
259       this.mode = this.top == 0 ? 'd' : this.stack[this.top - 1];
260    }
261 
262    /**
263     * Push an array or object scope.
264     *
265     * @param c The scope to open.
266     * @throws JSONException If nesting is too deep.
267     */
268    private void push( char c ) {
269       if( this.top >= MAXDEPTH ){
270          throw new JSONException( "Nesting too deep." );
271       }
272       this.stack[this.top] = c;
273       this.mode = c;
274       this.top += 1;
275    }
276 
277    /**
278     * Append either the value <code>true</code> or the value
279     * <code>false</code>.
280     *
281     * @param b A boolean.
282     * @return this
283     * @throws JSONException
284     */
285    public JSONBuilder value( boolean b ) {
286       return this.append( b ? "true" : "false" );
287    }
288 
289    /**
290     * Append a double value.
291     *
292     * @param d A double.
293     * @return this
294     * @throws JSONException If the number is not finite.
295     */
296    public JSONBuilder value( double d ) {
297       return this.value( new Double( d ) );
298    }
299 
300    /**
301     * Append a long value.
302     *
303     * @param l A long.
304     * @return this
305     * @throws JSONException
306     */
307    public JSONBuilder value( long l ) {
308       return this.append( Long.toString( l ) );
309    }
310 
311    /**
312     * Append an object value.
313     *
314     * @param o The object to append. It can be null, or a Boolean, Number,
315     *        String, JSONObject, or JSONArray, or an object with a
316     *        toJSONString() method.
317     * @return this
318     * @throws JSONException If the value is out of sequence.
319     */
320    public JSONBuilder value( Object o ) {
321       return this.append( JSONUtils.valueToString( o ) );
322    }
323 }