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}