Coverage Report - net.sf.json.util.JSONBuilder
 
Classes in this File Line Coverage Branch Coverage Complexity
JSONBuilder
0%
0/74
0%
0/44
3.286
 
 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  0
    public JSONBuilder( Writer w ) {
 94  0
       this.comma = false;
 95  0
       this.mode = 'i';
 96  0
       this.stack = new char[MAXDEPTH];
 97  0
       this.top = 0;
 98  0
       this.writer = w;
 99  0
    }
 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  0
       if( s == null ){
 110  0
          throw new JSONException( "Null pointer" );
 111  
       }
 112  0
       if( this.mode == 'o' || this.mode == 'a' ){
 113  
          try{
 114  0
             if( this.comma && this.mode == 'a' ){
 115  0
                this.writer.write( ',' );
 116  
             }
 117  0
             this.writer.write( s );
 118  0
          }catch( IOException e ){
 119  0
             throw new JSONException( e );
 120  0
          }
 121  0
          if( this.mode == 'o' ){
 122  0
             this.mode = 'k';
 123  
          }
 124  0
          this.comma = true;
 125  0
          return this;
 126  
       }
 127  0
       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  0
       if( this.mode == 'i' || this.mode == 'o' || this.mode == 'a' ){
 142  0
          this.push( 'a' );
 143  0
          this.append( "[" );
 144  0
          this.comma = false;
 145  0
          return this;
 146  
       }
 147  0
       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  0
       if( this.mode != m ){
 160  0
          throw new JSONException( m == 'o' ? "Misplaced endObject." : "Misplaced endArray." );
 161  
       }
 162  0
       this.pop( m );
 163  
       try{
 164  0
          this.writer.write( c );
 165  0
       }catch( IOException e ){
 166  0
          throw new JSONException( e );
 167  0
       }
 168  0
       this.comma = true;
 169  0
       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  0
       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  0
       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  0
       if( s == null ){
 205  0
          throw new JSONException( "Null key." );
 206  
       }
 207  0
       if( this.mode == 'k' ){
 208  
          try{
 209  0
             if( this.comma ){
 210  0
                this.writer.write( ',' );
 211  
             }
 212  0
             this.writer.write( JSONUtils.quote( s ) );
 213  0
             this.writer.write( ':' );
 214  0
             this.comma = false;
 215  0
             this.mode = 'o';
 216  0
             return this;
 217  0
          }catch( IOException e ){
 218  0
             throw new JSONException( e );
 219  
          }
 220  
       }
 221  0
       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  0
       if( this.mode == 'i' ){
 236  0
          this.mode = 'o';
 237  
       }
 238  0
       if( this.mode == 'o' || this.mode == 'a' ){
 239  0
          this.append( "{" );
 240  0
          this.push( 'k' );
 241  0
          this.comma = false;
 242  0
          return this;
 243  
       }
 244  0
       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  0
       if( this.top <= 0 || this.stack[this.top - 1] != c ){
 256  0
          throw new JSONException( "Nesting error." );
 257  
       }
 258  0
       this.top -= 1;
 259  0
       this.mode = this.top == 0 ? 'd' : this.stack[this.top - 1];
 260  0
    }
 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  0
       if( this.top >= MAXDEPTH ){
 270  0
          throw new JSONException( "Nesting too deep." );
 271  
       }
 272  0
       this.stack[this.top] = c;
 273  0
       this.mode = c;
 274  0
       this.top += 1;
 275  0
    }
 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  0
       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  0
       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  0
       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  0
       return this.append( JSONUtils.valueToString( o ) );
 322  
    }
 323  
 }