001/* 002 * ModeShape (http://www.modeshape.org) 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.modeshape.schematic.document; 017 018import java.io.IOException; 019import java.io.InputStream; 020import java.io.OutputStream; 021import java.io.Reader; 022import java.io.Writer; 023import java.net.URL; 024import java.nio.charset.Charset; 025import java.util.Date; 026import java.util.UUID; 027import java.util.logging.Level; 028import java.util.logging.Logger; 029import java.util.regex.Pattern; 030import org.modeshape.schematic.internal.document.CompactJsonWriter; 031import org.modeshape.schematic.internal.document.JsonReader; 032import org.modeshape.schematic.internal.document.JsonWriter; 033import org.modeshape.schematic.internal.document.PrettyJsonWriter; 034 035/** 036 * A utility class for working with JSON documents. This class is able to read and write JSON documents that are in a special 037 * modified format. <h3>Modified Format</h3> Any JSON document written in this modified format is still a valid JSON document. 038 * However, certain value types supported by BSON are written as nested objects in particular patterns. In fact, it is nearly 039 * identical to the JSON serialization used by MongoDB. All standard JSON values are written as expected, but the types unique to 040 * BSON are written as follows: 041 * <p> 042 * <table border="1" cellspacing="0" cellpadding="3"> 043 * <tr> 044 * <th>BSON Type</th> 045 * <th>Class</th> 046 * <th>Format</th> 047 * <th>Example</th> 048 * </tr> 049 * <tr> 050 * <td>Symbol</td> 051 * <td>{@link Symbol}</td> 052 * <td>"<i>value</i>"</td> 053 * <td>"The quick brown fox"</td> 054 * </tr> 055 * <tr> 056 * <td>Regular Expression</td> 057 * <td>{@link Pattern}</td> 058 * <td>{ "$regex" : "<i>pattern</i>", "$otions" : "<i>flags</i>" }</td> 059 * <td>{ "$regex" : "[CH]at\sin", "$options" : "im" }</td> 060 * </tr> 061 * <tr> 062 * <td>Date</td> 063 * <td>{@link Date}</td> 064 * <td>{ "$date" : "<i>yyyy-MM-dd</i>T<i>HH:mm:ss</i>Z" }</td> 065 * <td>{ "$date" : "2011-06-11T08:44:25Z" }</td> 066 * </tr> 067 * <tr> 068 * <td>Timestamp</td> 069 * <td>{@link Timestamp}</td> 070 * <td>{ "$ts" : <i>timeValue</i>, "$inc" : <i>incValue</i> }</td> 071 * <td>"\/TS("2011-06-11T08:44:25Z")\/"</td> 072 * </tr> 073 * <tr> 074 * <td>ObjectId</td> 075 * <td>{@link ObjectId}</td> 076 * <td>{ "$oid" : "<i>12bytesOfIdInBase16</i>" }</td> 077 * <td>{ "$oid" : "0000012c0000c8000900000f" }</td> 078 * </tr> 079 * <tr> 080 * <td>Binary</td> 081 * <td>{@link Binary}</td> 082 * <td>{ "$type" : <i>typeAsInt</i>, "$base64" : "<i>bytesInBase64</i>" }"</td> 083 * <td>{ "$type" : 0, "$base64" : "TWFuIGlzIGRpc3R" }"</td> 084 * </tr> 085 * <tr> 086 * <td>UUID</td> 087 * <td>{@link UUID}</td> 088 * <td>{ "$uuid" : "<i>string-form-of-uuid</i>" }</td> 089 * <td>{ "$uuid" : "09e0e949-bba4-459c-bb1d-9352e5ee8958" }</td> 090 * </tr> 091 * <tr> 092 * <td>Code</td> 093 * <td>{@link Code}</td> 094 * <td>{ "$code" : "<i>code</i>" }</td> 095 * <td>{ "$code" : "244-I2" }</td> 096 * </tr> 097 * <tr> 098 * <td>CodeWithScope</td> 099 * <td>{@link CodeWithScope}</td> 100 * <td>{ "$code" : "<i>code</i>", "$scope" : <i>scope document</i> }</td> 101 * <td>{ "$code" : "244-I2", "$scope" : { "name" : "Joe" } }</td> 102 * </tr> 103 * <tr> 104 * <td>MinKey</td> 105 * <td>{@link MinKey}</td> 106 * <td>"MinKey"</td> 107 * <td>"MinKey"</td> 108 * </tr> 109 * <tr> 110 * <td>MaxKey</td> 111 * <td>{@link MaxKey}</td> 112 * <td>"MaxKey"</td> 113 * <td>"MaxKey"</td> 114 * </tr> 115 * <tr> 116 * <td>Null value</td> 117 * <td>n/a</td> 118 * <td>null</td> 119 * <td>null</td> 120 * </tr> 121 * </table> 122 * </p> 123 * 124 * @author Randall Hauch <rhauch@redhat.com> (C) 2011 Red Hat Inc. 125 */ 126public class Json { 127 128 public static final Charset UTF8; 129 static { 130 Charset charset = Charset.defaultCharset(); 131 try { 132 charset = Charset.forName("UTF-8"); 133 } catch (Throwable t) { 134 Logger.getLogger(CompactJsonWriter.class.getName()).log(Level.SEVERE, 135 "Unable to obtain 'UTF-8' character set for JSON writing; using default charset."); 136 } 137 UTF8 = charset; 138 } 139 140 private static final CompactJsonWriter SHARED_COMPACT_WRITER = new CompactJsonWriter(); 141 142 /** 143 * A set of field names that are reserved for special formatting of non-standard JSON value types as nested objects. 144 * 145 * @author Randall Hauch <rhauch@redhat.com> (C) 2011 Red Hat Inc. 146 * @since 5.1 147 */ 148 public class ReservedField { 149 /** 150 * The "$oid" field name, used within an {@link ObjectId} value. A single ObjectId will be written 151 */ 152 public static final String OBJECT_ID = "$oid"; 153 public static final String DATE = "$date"; 154 public static final String TIMESTAMP = "$ts"; 155 public static final String INCREMENT = "$inc"; 156 public static final String REGEX_PATTERN = "$regex"; 157 public static final String REGEX_OPTIONS = "$options"; 158 public static final String BINARY_TYPE = "$type"; 159 public static final String BASE_64 = "$base64"; 160 public static final String UUID = "$uuid"; 161 public static final String CODE = "$code"; 162 public static final String SCOPE = "$scope"; 163 } 164 165 protected static JsonWriter getCompactJsonWriter() { 166 return SHARED_COMPACT_WRITER; 167 } 168 169 protected static JsonWriter getPrettyWriter() { 170 return new PrettyJsonWriter(); 171 } 172 173 private static final JsonReader SHARED_READER = new JsonReader(); 174 175 protected static JsonReader getReader() { 176 return SHARED_READER; 177 } 178 179 /** 180 * Read the JSON representation from supplied URL and construct the {@link Document} representation, using the 181 * {@link Charset#defaultCharset() default character set}. 182 * <p> 183 * This method will read standard JSON and modified JSON, and tolerates whitespace and use of several delimeters, including 184 * the standard ':' as well as '=' and '=>'. 185 * </p> 186 * 187 * @param url the URL of the JSON document; may not be null and must be resolvable 188 * @return the in-memory {@link Document} representation 189 * @throws ParsingException if there was a problem reading from the stream 190 */ 191 public static Document read( URL url ) throws ParsingException { 192 return SHARED_READER.read(url); 193 } 194 195 /** 196 * Read the JSON representation from supplied input stream and construct the {@link Document} representation, using the 197 * {@link Charset#defaultCharset() default character set}. 198 * <p> 199 * This method will read standard JSON and modified JSON, and tolerates whitespace and use of several delimeters, including 200 * the standard ':' as well as '=' and '=>'. 201 * </p> 202 * 203 * @param stream the input stream; may not be null 204 * @return the in-memory {@link Document} representation 205 * @throws ParsingException if there was a problem reading from the stream 206 */ 207 public static Document read( InputStream stream ) throws ParsingException { 208 return SHARED_READER.read(stream); 209 } 210 211 /** 212 * Read the JSON representation from supplied input stream and construct the {@link Document} representation, using the 213 * {@link Charset#defaultCharset() default character set}. 214 * <p> 215 * This method will read standard JSON and modified JSON, and tolerates whitespace and use of several delimeters, including 216 * the standard ':' as well as '=' and '=>'. 217 * </p> 218 * 219 * @param stream the input stream; may not be null 220 * @param introspectStringValues true if the string values should be examined for common patterns, or false otherwise 221 * 222 * @return the in-memory {@link Document} representation 223 * @throws ParsingException if there was a problem reading from the stream 224 */ 225 public static Document read( InputStream stream, boolean introspectStringValues ) throws ParsingException { 226 return SHARED_READER.read(stream, introspectStringValues); 227 } 228 229 /** 230 * Read the JSON representation from supplied input stream and construct the {@link Document} representation, using the 231 * supplied {@link Charset character set}. 232 * <p> 233 * This method will read standard JSON and modified JSON, and tolerates whitespace and use of several delimeters, including 234 * the standard ':' as well as '=' and '=>'. 235 * </p> 236 * 237 * @param stream the input stream; may not be null 238 * @param charset the character set that should be used; may not be null 239 * @return the in-memory {@link Document} representation 240 * @throws ParsingException if there was a problem reading from the stream 241 */ 242 public static Document read( InputStream stream, 243 Charset charset ) throws ParsingException { 244 return SHARED_READER.read(stream, charset); 245 } 246 247 /** 248 * Read the JSON representation from supplied input stream and construct the {@link Document} representation. 249 * <p> 250 * This method will read standard JSON and modified JSON, and tolerates whitespace and use of several delimeters, including 251 * the standard ':' as well as '=' and '=>'. 252 * </p> 253 * 254 * @param reader the IO reader; may not be null 255 * @return the in-memory {@link Document} representation 256 * @throws ParsingException if there was a problem reading from the stream 257 */ 258 public static Document read( Reader reader ) throws ParsingException { 259 return SHARED_READER.read(reader); 260 } 261 262 /** 263 * Read the supplied JSON representation and construct the {@link Document} representation. 264 * <p> 265 * This method will read standard JSON and modified JSON, and tolerates whitespace and use of several delimeters, including 266 * the standard ':' as well as '=' and '=>'. 267 * </p> 268 * 269 * @param json the JSON document string; may not be null 270 * @return the in-memory {@link Document} representation 271 * @throws ParsingException if there was a problem reading from the stream 272 */ 273 public static Document read( String json ) throws ParsingException { 274 return SHARED_READER.read(json); 275 } 276 277 /** 278 * Return a {@link DocumentSequence} that can be used to pull multiple documents from the stream. 279 * 280 * @param introspectStringValues true if the string values should be examined for common patterns, or false otherwise 281 * @param stream the input stream; may not be null 282 * @return the sequence that can be used to get one or more Document instances from a single input 283 */ 284 public static DocumentSequence readMultiple( InputStream stream, boolean introspectStringValues ) { 285 return SHARED_READER.readMultiple(stream, introspectStringValues); 286 } 287 288 /** 289 * Return a {@link DocumentSequence} that can be used to pull multiple documents from the stream. 290 * 291 * @param reader the IO reader; may not be null 292 * @return the sequence that can be used to get one or more Document instances from a single input 293 */ 294 public static DocumentSequence readMultiple( Reader reader ) { 295 return SHARED_READER.readMultiple(reader); 296 } 297 298 /** 299 * Return the modified JSON representation for the supplied in-memory {@link Document}. The resulting JSON will have no 300 * embedded line feeds or extra spaces. 301 * <p> 302 * This format is compact and easy for software to read, but usually very difficult for people to read anything but very small 303 * documents. 304 * </p> 305 * 306 * @param bson the BSON object or BSON value; may not be null 307 * @return the string; may not be null 308 */ 309 public static String write( Document bson ) { 310 return getCompactJsonWriter().write(bson); 311 } 312 313 /** 314 * Return the modified JSON representation for the supplied object value. The resulting JSON will have no embedded line feeds 315 * or extra spaces. 316 * <p> 317 * This format is compact and easy for software to read, but usually very difficult for people to read anything but very small 318 * documents. 319 * </p> 320 * 321 * @param value the BSON object or BSON value; may not be null 322 * @return the string; may not be null 323 */ 324 public static String write( Object value ) { 325 return getCompactJsonWriter().write(value); 326 } 327 328 /** 329 * Write to the supplied writer the modified JSON representation of the supplied in-memory {@link Document}. The resulting 330 * JSON will have no embedded line feeds or extra spaces. 331 * <p> 332 * This format is compact and easy for software to read, but usually very difficult for people to read anything but very small 333 * documents. 334 * </p> 335 * 336 * @param bson the BSON object or BSON value; may not be null 337 * @param writer the writer; may not be null 338 * @throws IOException if there was a problem reading from the stream 339 */ 340 public static void write( Document bson, 341 Writer writer ) throws IOException { 342 getCompactJsonWriter().write(bson, writer); 343 } 344 345 /** 346 * Write to the supplied writer the modified JSON representation of the supplied in-memory {@link Document}. The resulting 347 * JSON will have no embedded line feeds or extra spaces. 348 * <p> 349 * This format is compact and easy for software to read, but usually very difficult for people to read anything but very small 350 * documents. 351 * </p> 352 * 353 * @param bson the BSON object or BSON value; may not be null 354 * @param stream the output stream; may not be null 355 * @throws IOException if there was a problem reading from the stream 356 */ 357 public static void write( Document bson, 358 OutputStream stream ) throws IOException { 359 getCompactJsonWriter().write(bson, stream); 360 } 361 362 /** 363 * Return the modified JSON representation for the supplied in-memory {@link Document}. The resulting JSON will be indented 364 * for each name/value pair and each array value. 365 * <p> 366 * This format is very readable by people and software, but is less compact due to the extra whitespace. 367 * </p> 368 * 369 * @param bson the BSON object or BSON value; may not be null 370 * @return the JSON representation; never null 371 */ 372 public static String writePretty( Document bson ) { 373 return getPrettyWriter().write(bson); 374 } 375 376 /** 377 * Return the modified JSON representation for the supplied object value. The resulting JSON will be indented for each 378 * name/value pair and each array value. 379 * <p> 380 * This format is very readable by people and software, but is less compact due to the extra whitespace. 381 * </p> 382 * 383 * @param value the BSON object or BSON value; may not be null 384 * @return the JSON representation; never null 385 */ 386 public static String writePretty( Object value ) { 387 return getPrettyWriter().write(value); 388 } 389 390 /** 391 * Write to the supplied writer the modified JSON representation of the supplied in-memory {@link Document}. The resulting 392 * JSON will be indented for each name/value pair and each array value. 393 * <p> 394 * This format is very readable by people and software, but is less compact due to the extra whitespace. 395 * </p> 396 * 397 * @param bson the BSON object or BSON value; may not be null 398 * @param writer the writer; may not be null 399 * @throws IOException if there was a problem reading from the stream 400 */ 401 public static void writePretty( Document bson, 402 Writer writer ) throws IOException { 403 getPrettyWriter().write(bson, writer); 404 } 405 406 /** 407 * Write to the supplied writer the modified JSON representation of the supplied in-memory {@link Document}. The resulting 408 * JSON will be indented for each name/value pair and each array value. 409 * <p> 410 * This format is very readable by people and software, but is less compact due to the extra whitespace. 411 * </p> 412 * 413 * @param bson the BSON object or BSON value; may not be null 414 * @param stream the output stream; may not be null 415 * @throws IOException if there was a problem reading from the stream 416 */ 417 public static void writePretty( Document bson, 418 OutputStream stream ) throws IOException { 419 getPrettyWriter().write(bson, stream); 420 } 421 422}