001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software GmbH & Co. KG, please see the 018 * company website: http://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: http://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 * 027 * This file is based on: 028 * org.json.JSONWriter 029 * from the JSON in Java implementation. 030 * 031 * Copyright (c) 2002 JSON.org 032 * 033 * Permission is hereby granted, free of charge, to any person obtaining a copy 034 * of this software and associated documentation files (the "Software"), to deal 035 * in the Software without restriction, including without limitation the rights 036 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 037 * copies of the Software, and to permit persons to whom the Software is 038 * furnished to do so, subject to the following conditions: 039 * 040 * The above copyright notice and this permission notice shall be included in all 041 * copies or substantial portions of the Software. 042 * 043 * The Software shall be used for Good, not Evil. 044 * 045 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 046 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 047 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 048 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 049 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 050 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 051 * SOFTWARE. 052 */ 053 054package org.opencms.json; 055 056import java.io.IOException; 057import java.io.Writer; 058 059/** 060 * JSONWriter provides a quick and convenient way of producing JSON text. 061 * The texts produced strictly conform to JSON syntax rules. No whitespace is 062 * added, so the results are ready for transmission or storage. Each instance of 063 * JSONWriter can produce one JSON text. 064 * <p> 065 * A JSONWriter instance provides a <code>value</code> method for appending 066 * values to the 067 * text, and a <code>key</code> 068 * method for adding keys before values in objects. There are <code>array</code> 069 * and <code>endArray</code> methods that make and bound array values, and 070 * <code>object</code> and <code>endObject</code> methods which make and bound 071 * object values. All of these methods return the JSONWriter instance, 072 * permitting a cascade style. For example, <pre> 073 * new JSONWriter(myWriter) 074 * .object() 075 * .key("JSON") 076 * .value("Hello, World!") 077 * .endObject();</pre> which writes <pre> 078 * {"JSON":"Hello, World!"}</pre> 079 * <p> 080 * The first method called must be <code>array</code> or <code>object</code>. 081 * There are no methods for adding commas or colons. JSONWriter adds them for 082 * you. Objects and arrays can be nested up to 20 levels deep. 083 * <p> 084 * This can sometimes be easier than using a JSONObject to build a string.<p> 085 * 086 */ 087public class JSONWriter { 088 089 /** The maximum depth. */ 090 private static final int MAXDEPTH = 20; 091 092 /** 093 * The current m_mode. Values: 094 * 'a' (array), 095 * 'd' (done), 096 * 'i' (initial), 097 * 'k' (key), 098 * 'o' (object). 099 */ 100 protected char m_mode; 101 102 /** 103 * The m_writer that will receive the output. 104 */ 105 protected Writer m_writer; 106 107 /** 108 * The m_comma flag determines if a m_comma should be output before the next 109 * value. 110 */ 111 private boolean m_comma; 112 113 /** 114 * The object/array m_stack. 115 */ 116 private char[] m_stack; 117 118 /** 119 * The m_stack m_top index. A value of 0 indicates that the m_stack is empty. 120 */ 121 private int m_top; 122 123 /** 124 * Make a fresh JSONWriter.<p> 125 * 126 * It can be used to build one JSON text.<p> 127 * 128 * @param w the writer to use 129 */ 130 public JSONWriter(Writer w) { 131 132 m_comma = false; 133 m_mode = 'i'; 134 m_stack = new char[MAXDEPTH]; 135 m_top = 0; 136 m_writer = w; 137 } 138 139 /** 140 * Begin appending a new array.<p> 141 * 142 * All values until the balancing 143 * <code>endArray</code> will be appended to this array. The 144 * <code>endArray</code> method must be called to mark the array's end.<p> 145 * 146 * @return this 147 * @throws JSONException if the nesting is too deep, or if the object is 148 * started in the wrong place (for example as a key or after the end of the 149 * outermost array or object) 150 */ 151 public JSONWriter array() throws JSONException { 152 153 if ((m_mode == 'i') || (m_mode == 'o') || (m_mode == 'a')) { 154 push('a'); 155 append("["); 156 m_comma = false; 157 return this; 158 } 159 throw new JSONException("Misplaced array."); 160 } 161 162 /** 163 * End an array. This method most be called to balance calls to 164 * <code>array</code>.<p> 165 * 166 * @return this 167 * @throws JSONException if incorrectly nested 168 */ 169 public JSONWriter endArray() throws JSONException { 170 171 return end('a', ']'); 172 } 173 174 /** 175 * End an object. This method most be called to balance calls to 176 * <code>object</code>.<p> 177 * 178 * @return this 179 * @throws JSONException if incorrectly nested 180 */ 181 public JSONWriter endObject() throws JSONException { 182 183 return end('k', '}'); 184 } 185 186 /** 187 * Append a key. The key will be associated with the next value. In an 188 * object, every value must be preceded by a key.<p> 189 * 190 * @param s a key string 191 * @return this 192 * @throws JSONException if the key is out of place. For example, keys 193 * do not belong in arrays or if the key is null 194 */ 195 public JSONWriter key(String s) throws JSONException { 196 197 if (s == null) { 198 throw new JSONException("Null key."); 199 } 200 if (m_mode == 'k') { 201 try { 202 if (m_comma) { 203 m_writer.write(','); 204 } 205 m_writer.write(JSONObject.quote(s)); 206 m_writer.write(':'); 207 m_comma = false; 208 m_mode = 'o'; 209 return this; 210 } catch (IOException e) { 211 throw new JSONException(e); 212 } 213 } 214 throw new JSONException("Misplaced key."); 215 } 216 217 /** 218 * Begin appending a new object.<p> 219 * 220 * All keys and values until the balancing 221 * <code>endObject</code> will be appended to this object. The 222 * <code>endObject</code> method must be called to mark the object's end.<p> 223 * 224 * @return this 225 * @throws JSONException if the nesting is too deep, or if the object is 226 * started in the wrong place (for example as a key or after the end of the 227 * outermost array or object) 228 */ 229 public JSONWriter object() throws JSONException { 230 231 if (m_mode == 'i') { 232 m_mode = 'o'; 233 } 234 if ((m_mode == 'o') || (m_mode == 'a')) { 235 append("{"); 236 push('k'); 237 m_comma = false; 238 return this; 239 } 240 throw new JSONException("Misplaced object."); 241 242 } 243 244 /** 245 * Append either the value <code>true</code> or the value 246 * <code>false</code>.<p> 247 * 248 * @param b a boolean 249 * @return this 250 * @throws JSONException if something goes wrong 251 */ 252 public JSONWriter value(boolean b) throws JSONException { 253 254 return append(b ? "true" : "false"); 255 } 256 257 /** 258 * Append a double value.<p> 259 * 260 * @param d a double 261 * @return this 262 * @throws JSONException if the number is not finite 263 */ 264 public JSONWriter value(double d) throws JSONException { 265 266 return this.value(Double.valueOf(d)); 267 } 268 269 /** 270 * Append a long value.<p> 271 * 272 * @param l a long 273 * @return this 274 * @throws JSONException if something goes wrong 275 */ 276 public JSONWriter value(long l) throws JSONException { 277 278 return append(Long.toString(l)); 279 } 280 281 /** 282 * Append an object value.<p> 283 * 284 * @param o the object to append. It can be null, or a Boolean, Number, 285 * String, JSONObject, or JSONArray, or an object with a toJSONString() 286 * method 287 * @return this 288 * @throws JSONException if the value is out of sequence 289 */ 290 public JSONWriter value(Object o) throws JSONException { 291 292 return append(JSONObject.valueToString(o)); 293 } 294 295 /** 296 * Append a value.<p> 297 * 298 * @param s a string value 299 * @return this 300 * @throws JSONException if the value is out of sequence 301 */ 302 private JSONWriter append(String s) throws JSONException { 303 304 if (s == null) { 305 throw new JSONException("Null pointer"); 306 } 307 if ((m_mode == 'o') || (m_mode == 'a')) { 308 try { 309 if (m_comma && (m_mode == 'a')) { 310 m_writer.write(','); 311 } 312 m_writer.write(s); 313 } catch (IOException e) { 314 throw new JSONException(e); 315 } 316 if (m_mode == 'o') { 317 m_mode = 'k'; 318 } 319 m_comma = true; 320 return this; 321 } 322 throw new JSONException("Value out of sequence."); 323 } 324 325 /** 326 * End something.<p> 327 * 328 * @param m mode 329 * @param c closing character 330 * @return this 331 * @throws JSONException if unbalanced 332 */ 333 private JSONWriter end(char m, char c) throws JSONException { 334 335 if (m_mode != m) { 336 throw new JSONException(m == 'o' ? "Misplaced endObject." : "Misplaced endArray."); 337 } 338 pop(m); 339 try { 340 m_writer.write(c); 341 } catch (IOException e) { 342 throw new JSONException(e); 343 } 344 m_comma = true; 345 return this; 346 } 347 348 /** 349 * Pop an array or object scope.<p> 350 * 351 * @param c the scope to close 352 * @throws JSONException zf nesting is wrong 353 */ 354 private void pop(char c) throws JSONException { 355 356 if ((m_top <= 0) || (m_stack[m_top - 1] != c)) { 357 throw new JSONException("Nesting error."); 358 } 359 m_top -= 1; 360 m_mode = m_top == 0 ? 'd' : m_stack[m_top - 1]; 361 } 362 363 /** 364 * Push an array or object scope.<p> 365 * 366 * @param c the scope to open 367 * @throws JSONException if nesting is too deep 368 */ 369 private void push(char c) throws JSONException { 370 371 if (m_top >= MAXDEPTH) { 372 throw new JSONException("Nesting too deep."); 373 } 374 m_stack[m_top] = c; 375 m_mode = c; 376 m_top += 1; 377 } 378}