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 }