1 /* 2 * Copyright (C) 2010 The Android Open Source Project 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 17 package org.json; 18 19 import java.util.ArrayList; 20 import java.util.Arrays; 21 import java.util.List; 22 23 // Note: this class was written without inspecting the non-free org.json sourcecode. 24 25 /** 26 * Implements {@link JSONObject#toString} and {@link JSONArray#toString}. Most 27 * application developers should use those methods directly and disregard this 28 * API. For example:<pre> 29 * JSONObject object = ... 30 * String json = object.toString();</pre> 31 * 32 * <p>Stringers only encode well-formed JSON strings. In particular: 33 * <ul> 34 * <li>The stringer must have exactly one top-level array or object. 35 * <li>Lexical scopes must be balanced: every call to {@link #array} must 36 * have a matching call to {@link #endArray} and every call to {@link 37 * #object} must have a matching call to {@link #endObject}. 38 * <li>Arrays may not contain keys (property names). 39 * <li>Objects must alternate keys (property names) and values. 40 * <li>Values are inserted with either literal {@link #value(Object) value} 41 * calls, or by nesting arrays or objects. 42 * </ul> 43 * Calls that would result in a malformed JSON string will fail with a 44 * {@link JSONException}. 45 * 46 * <p>This class provides no facility for pretty-printing (ie. indenting) 47 * output. To encode indented output, use {@link JSONObject#toString(int)} or 48 * {@link JSONArray#toString(int)}. 49 * 50 * <p>Some implementations of the API support at most 20 levels of nesting. 51 * Attempts to create more than 20 levels of nesting may fail with a {@link 52 * JSONException}. 53 * 54 * <p>Each stringer may be used to encode a single top level value. Instances of 55 * this class are not thread safe. Although this class is nonfinal, it was not 56 * designed for inheritance and should not be subclassed. In particular, 57 * self-use by overrideable methods is not specified. See <i>Effective Java</i> 58 * Item 17, "Design and Document or inheritance or else prohibit it" for further 59 * information. 60 */ 61 public class JSONStringer { 62 63 /** The output data, containing at most one top-level array or object. */ 64 final StringBuilder out = new StringBuilder(); 65 66 /** 67 * Lexical scoping elements within this stringer, necessary to insert the 68 * appropriate separator characters (ie. commas and colons) and to detect 69 * nesting errors. 70 */ 71 enum Scope { 72 73 /** 74 * An array with no elements requires no separators or newlines before 75 * it is closed. 76 */ 77 EMPTY_ARRAY, 78 79 /** 80 * A array with at least one value requires a comma and newline before 81 * the next element. 82 */ 83 NONEMPTY_ARRAY, 84 85 /** 86 * An object with no keys or values requires no separators or newlines 87 * before it is closed. 88 */ 89 EMPTY_OBJECT, 90 91 /** 92 * An object whose most recent element is a key. The next element must 93 * be a value. 94 */ 95 DANGLING_KEY, 96 97 /** 98 * An object with at least one name/value pair requires a comma and 99 * newline before the next element. 100 */ 101 NONEMPTY_OBJECT, 102 103 /** 104 * A special bracketless array needed by JSONStringer.join() and 105 * JSONObject.quote() only. Not used for JSON encoding. 106 */ 107 NULL, 108 } 109 110 /** 111 * Unlike the original implementation, this stack isn't limited to 20 112 * levels of nesting. 113 */ 114 private final List<Scope> stack = new ArrayList<Scope>(); 115 116 /** 117 * A string containing a full set of spaces for a single level of 118 * indentation, or null for no pretty printing. 119 */ 120 private final String indent; 121 JSONStringer()122 public JSONStringer() { 123 indent = null; 124 } 125 JSONStringer(int indentSpaces)126 JSONStringer(int indentSpaces) { 127 char[] indentChars = new char[indentSpaces]; 128 Arrays.fill(indentChars, ' '); 129 indent = new String(indentChars); 130 } 131 132 /** 133 * Begins encoding a new array. Each call to this method must be paired with 134 * a call to {@link #endArray}. 135 * 136 * @return this stringer. 137 */ array()138 public JSONStringer array() throws JSONException { 139 return open(Scope.EMPTY_ARRAY, "["); 140 } 141 142 /** 143 * Ends encoding the current array. 144 * 145 * @return this stringer. 146 */ endArray()147 public JSONStringer endArray() throws JSONException { 148 return close(Scope.EMPTY_ARRAY, Scope.NONEMPTY_ARRAY, "]"); 149 } 150 151 /** 152 * Begins encoding a new object. Each call to this method must be paired 153 * with a call to {@link #endObject}. 154 * 155 * @return this stringer. 156 */ object()157 public JSONStringer object() throws JSONException { 158 return open(Scope.EMPTY_OBJECT, "{"); 159 } 160 161 /** 162 * Ends encoding the current object. 163 * 164 * @return this stringer. 165 */ endObject()166 public JSONStringer endObject() throws JSONException { 167 return close(Scope.EMPTY_OBJECT, Scope.NONEMPTY_OBJECT, "}"); 168 } 169 170 /** 171 * Enters a new scope by appending any necessary whitespace and the given 172 * bracket. 173 */ open(Scope empty, String openBracket)174 JSONStringer open(Scope empty, String openBracket) throws JSONException { 175 if (stack.isEmpty() && out.length() > 0) { 176 throw new JSONException("Nesting problem: multiple top-level roots"); 177 } 178 beforeValue(); 179 stack.add(empty); 180 out.append(openBracket); 181 return this; 182 } 183 184 /** 185 * Closes the current scope by appending any necessary whitespace and the 186 * given bracket. 187 */ close(Scope empty, Scope nonempty, String closeBracket)188 JSONStringer close(Scope empty, Scope nonempty, String closeBracket) throws JSONException { 189 Scope context = peek(); 190 if (context != nonempty && context != empty) { 191 throw new JSONException("Nesting problem"); 192 } 193 194 stack.remove(stack.size() - 1); 195 if (context == nonempty) { 196 newline(); 197 } 198 out.append(closeBracket); 199 return this; 200 } 201 202 /** 203 * Returns the value on the top of the stack. 204 */ peek()205 private Scope peek() throws JSONException { 206 if (stack.isEmpty()) { 207 throw new JSONException("Nesting problem"); 208 } 209 return stack.get(stack.size() - 1); 210 } 211 212 /** 213 * Replace the value on the top of the stack with the given value. 214 */ replaceTop(Scope topOfStack)215 private void replaceTop(Scope topOfStack) { 216 stack.set(stack.size() - 1, topOfStack); 217 } 218 219 /** 220 * Encodes {@code value}. 221 * 222 * @param value a {@link JSONObject}, {@link JSONArray}, String, Boolean, 223 * Integer, Long, Double or null. May not be {@link Double#isNaN() NaNs} 224 * or {@link Double#isInfinite() infinities}. 225 * @return this stringer. 226 */ value(Object value)227 public JSONStringer value(Object value) throws JSONException { 228 if (stack.isEmpty()) { 229 throw new JSONException("Nesting problem"); 230 } 231 232 if (value instanceof JSONArray) { 233 ((JSONArray) value).writeTo(this); 234 return this; 235 236 } else if (value instanceof JSONObject) { 237 ((JSONObject) value).writeTo(this); 238 return this; 239 } 240 241 beforeValue(); 242 243 if (value == null 244 || value instanceof Boolean 245 || value == JSONObject.NULL) { 246 out.append(value); 247 248 } else if (value instanceof Number) { 249 out.append(JSONObject.numberToString((Number) value)); 250 251 } else { 252 string(value.toString()); 253 } 254 255 return this; 256 } 257 258 /** 259 * Encodes {@code value} to this stringer. 260 * 261 * @return this stringer. 262 */ value(boolean value)263 public JSONStringer value(boolean value) throws JSONException { 264 if (stack.isEmpty()) { 265 throw new JSONException("Nesting problem"); 266 } 267 beforeValue(); 268 out.append(value); 269 return this; 270 } 271 272 /** 273 * Encodes {@code value} to this stringer. 274 * 275 * @param value a finite value. May not be {@link Double#isNaN() NaNs} or 276 * {@link Double#isInfinite() infinities}. 277 * @return this stringer. 278 */ value(double value)279 public JSONStringer value(double value) throws JSONException { 280 if (stack.isEmpty()) { 281 throw new JSONException("Nesting problem"); 282 } 283 beforeValue(); 284 out.append(JSONObject.numberToString(value)); 285 return this; 286 } 287 288 /** 289 * Encodes {@code value} to this stringer. 290 * 291 * @return this stringer. 292 */ value(long value)293 public JSONStringer value(long value) throws JSONException { 294 if (stack.isEmpty()) { 295 throw new JSONException("Nesting problem"); 296 } 297 beforeValue(); 298 out.append(value); 299 return this; 300 } 301 string(String value)302 private void string(String value) { 303 out.append("\""); 304 for (int i = 0, length = value.length(); i < length; i++) { 305 char c = value.charAt(i); 306 307 /* 308 * From RFC 4627, "All Unicode characters may be placed within the 309 * quotation marks except for the characters that must be escaped: 310 * quotation mark, reverse solidus, and the control characters 311 * (U+0000 through U+001F)." 312 */ 313 switch (c) { 314 case '"': 315 case '\\': 316 case '/': 317 out.append('\\').append(c); 318 break; 319 320 case '\t': 321 out.append("\\t"); 322 break; 323 324 case '\b': 325 out.append("\\b"); 326 break; 327 328 case '\n': 329 out.append("\\n"); 330 break; 331 332 case '\r': 333 out.append("\\r"); 334 break; 335 336 case '\f': 337 out.append("\\f"); 338 break; 339 340 default: 341 if (c <= 0x1F) { 342 out.append(String.format("\\u%04x", (int) c)); 343 } else { 344 out.append(c); 345 } 346 break; 347 } 348 349 } 350 out.append("\""); 351 } 352 newline()353 private void newline() { 354 if (indent == null) { 355 return; 356 } 357 358 out.append("\n"); 359 for (int i = 0; i < stack.size(); i++) { 360 out.append(indent); 361 } 362 } 363 364 /** 365 * Encodes the key (property name) to this stringer. 366 * 367 * @param name the name of the forthcoming value. May not be null. 368 * @return this stringer. 369 */ key(String name)370 public JSONStringer key(String name) throws JSONException { 371 if (name == null) { 372 throw new JSONException("Names must be non-null"); 373 } 374 beforeKey(); 375 string(name); 376 return this; 377 } 378 379 /** 380 * Inserts any necessary separators and whitespace before a name. Also 381 * adjusts the stack to expect the key's value. 382 */ beforeKey()383 private void beforeKey() throws JSONException { 384 Scope context = peek(); 385 if (context == Scope.NONEMPTY_OBJECT) { // first in object 386 out.append(','); 387 } else if (context != Scope.EMPTY_OBJECT) { // not in an object! 388 throw new JSONException("Nesting problem"); 389 } 390 newline(); 391 replaceTop(Scope.DANGLING_KEY); 392 } 393 394 /** 395 * Inserts any necessary separators and whitespace before a literal value, 396 * inline array, or inline object. Also adjusts the stack to expect either a 397 * closing bracket or another element. 398 */ beforeValue()399 private void beforeValue() throws JSONException { 400 if (stack.isEmpty()) { 401 return; 402 } 403 404 Scope context = peek(); 405 if (context == Scope.EMPTY_ARRAY) { // first in array 406 replaceTop(Scope.NONEMPTY_ARRAY); 407 newline(); 408 } else if (context == Scope.NONEMPTY_ARRAY) { // another in array 409 out.append(','); 410 newline(); 411 } else if (context == Scope.DANGLING_KEY) { // value for key 412 out.append(indent == null ? ":" : ": "); 413 replaceTop(Scope.NONEMPTY_OBJECT); 414 } else if (context != Scope.NULL) { 415 throw new JSONException("Nesting problem"); 416 } 417 } 418 419 /** 420 * Returns the encoded JSON string. 421 * 422 * <p>If invoked with unterminated arrays or unclosed objects, this method's 423 * return value is undefined. 424 * 425 * <p><strong>Warning:</strong> although it contradicts the general contract 426 * of {@link Object#toString}, this method returns null if the stringer 427 * contains no data. 428 */ toString()429 @Override public String toString() { 430 return out.length() == 0 ? null : out.toString(); 431 } 432 } 433