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.internal.document;
017
018import static org.modeshape.schematic.document.Json.ReservedField.BASE_64;
019import static org.modeshape.schematic.document.Json.ReservedField.BINARY_TYPE;
020import static org.modeshape.schematic.document.Json.ReservedField.CODE;
021import static org.modeshape.schematic.document.Json.ReservedField.DATE;
022import static org.modeshape.schematic.document.Json.ReservedField.INCREMENT;
023import static org.modeshape.schematic.document.Json.ReservedField.OBJECT_ID;
024import static org.modeshape.schematic.document.Json.ReservedField.REGEX_OPTIONS;
025import static org.modeshape.schematic.document.Json.ReservedField.REGEX_PATTERN;
026import static org.modeshape.schematic.document.Json.ReservedField.SCOPE;
027import static org.modeshape.schematic.document.Json.ReservedField.TIMESTAMP;
028import static org.modeshape.schematic.document.Json.ReservedField.UUID;
029import java.io.IOException;
030import java.io.InputStream;
031import java.io.InputStreamReader;
032import java.io.Reader;
033import java.io.StringReader;
034import java.net.URL;
035import java.nio.charset.Charset;
036import java.text.CharacterIterator;
037import java.text.ParseException;
038import java.text.StringCharacterIterator;
039import java.util.Date;
040import java.util.Iterator;
041import java.util.LinkedList;
042import java.util.List;
043import java.util.concurrent.atomic.AtomicBoolean;
044import org.modeshape.schematic.Base64;
045import org.modeshape.schematic.annotation.Immutable;
046import org.modeshape.schematic.annotation.NotThreadSafe;
047import org.modeshape.schematic.annotation.ThreadSafe;
048import org.modeshape.schematic.document.Bson;
049import org.modeshape.schematic.document.Document;
050import org.modeshape.schematic.document.DocumentSequence;
051import org.modeshape.schematic.document.Json;
052import org.modeshape.schematic.document.Null;
053import org.modeshape.schematic.document.ParsingException;
054
055/**
056 * A class that reads the <a href="http://www.json.org/">JSON</a> data format and constructs an in-memory <a
057 * href="http://bsonspec.org/">BSON</a> representation.
058 * <p>
059 * This reader is capable of optionally introspecting string values to look for certain string patterns that are commonly used to
060 * represent dates. In introspection is not done by default, but when it is used it looks for the following patterns:
061 * <ul>
062 * <li>a string literal date of the form <code>"<i>yyyy</i>-<i>MM</i>-<i>dd</i>T<i>HH</i>:<i>mm</i>:<i>ss</i>"</code> where
063 * <code>T</code> is a literal character</li>
064 * <li>a string literal date of the form <code>"<i>yyyy</i>-<i>MM</i>-<i>dd</i>T<i>HH</i>:<i>mm</i>:<i>ss</i>Z"</code> where
065 * <code>T</code> and <code>Z</code> are literal characters</li>
066 * <li>a string literal date of the form
067 * <code>"<i>yyyy</i>-<i>MM</i>-<i>dd</i>T<i>HH</i>:<i>mm</i>:<i>ss</i>GMT+<i>00</i>:<i>00</i>"</code> where <code>T</code>, and
068 * <code>GMT</code> are literal characters</li>
069 * <li>a string literal date of the form <code>"/Date(<i>millisOrIso</i>)/"</code></li>
070 * <li>a string literal date of the form <code>"\/Date(<i>millisOrIso</i>)\/"</code></li>
071 * </ul>
072 * Note that in the date forms listed above, <code><i>millisOrIso</i></code> is either a long value representing the number of
073 * milliseconds since epoch or a string literal in ISO-8601 format representing a date and time.
074 * </p>
075 * <p>
076 * This reader also accepts non-string values that are function calls of the form
077 * 
078 * <pre>
079 *     new <i>functionName</i>(<i>parameters</i>)
080 * </pre>
081 * 
082 * where <code><i>parameters</i></code> consists of one or more JSON values (including nested functions). If the function call
083 * cannot be parsed and executed, the string literal form of the function call is kept.
084 * </p>
085 * 
086 * @author Randall Hauch <rhauch@redhat.com> (C) 2011 Red Hat Inc.
087 * @since 5.1
088 */
089@ThreadSafe
090@Immutable
091public class JsonReader {
092
093    protected static final DocumentValueFactory VALUE_FACTORY = new DefaultDocumentValueFactory();
094    protected static final ValueMatcher SIMPLE_VALUE_MATCHER = new SimpleValueMatcher(VALUE_FACTORY);
095    protected static final ValueMatcher DATE_VALUE_MATCHER = new DateValueMatcher(VALUE_FACTORY);
096
097    public static final boolean DEFAULT_INTROSPECT = true;
098
099    /**
100     * Read the JSON representation from supplied URL and construct the {@link Document} representation, using the
101     * {@link Charset#defaultCharset() default character set}.
102     * 
103     * @param url the URL to the JSON document; may not be null and must be resolvable
104     * @return the in-memory {@link Document} representation
105     * @throws ParsingException if there was a problem reading from the URL
106     */
107    public Document read( URL url ) throws ParsingException {
108        try {
109            return read(url.openStream(), DEFAULT_INTROSPECT);
110        } catch (IOException e) {
111            throw new ParsingException(e.getMessage(), e, 0, 0);
112        }
113    }
114
115    /**
116     * Read the JSON representation from supplied input stream and construct the {@link Document} representation, using the
117     * {@link Charset#defaultCharset() default character set}.
118     * 
119     * @param stream the input stream; may not be null
120     * @return the in-memory {@link Document} representation
121     * @throws ParsingException if there was a problem reading from the stream
122     */
123    public Document read( InputStream stream ) throws ParsingException {
124        return read(stream, DEFAULT_INTROSPECT);
125    }
126
127    /**
128     * Read the JSON representation from supplied input stream and construct the {@link Document} representation, using the
129     * supplied {@link Charset character set}.
130     * 
131     * @param stream the input stream; may not be null
132     * @param charset the character set that should be used; may not be null
133     * @return the in-memory {@link Document} representation
134     * @throws ParsingException if there was a problem reading from the stream
135     */
136    public Document read( InputStream stream,
137                          Charset charset ) throws ParsingException {
138        return read(stream, charset, DEFAULT_INTROSPECT);
139    }
140
141    /**
142     * Read the JSON representation from supplied input stream and construct the {@link Document} representation.
143     * 
144     * @param reader the IO reader; may not be null
145     * @return the in-memory {@link Document} representation
146     * @throws ParsingException if there was a problem reading from the stream
147     */
148    public Document read( Reader reader ) throws ParsingException {
149        return read(reader, DEFAULT_INTROSPECT);
150    }
151
152    /**
153     * Read the JSON representation from supplied string and construct the {@link Document} representation.
154     * 
155     * @param json the JSON representation; may not be null
156     * @return the in-memory {@link Document} representation
157     * @throws ParsingException if there was a problem reading from the stream
158     */
159    public Document read( String json ) throws ParsingException {
160        return read(json, DEFAULT_INTROSPECT);
161    }
162
163    /**
164     * Read the JSON representation from supplied input stream and construct the {@link Document} representation, using the
165     * {@link Charset#defaultCharset() default character set}.
166     * 
167     * @param stream the input stream; may not be null
168     * @param introspectStringValues true if the string values should be examined for common patterns, or false otherwise
169     * @return the in-memory {@link Document} representation
170     * @throws ParsingException if there was a problem reading from the stream
171     */
172    public Document read( InputStream stream,
173                          boolean introspectStringValues ) throws ParsingException {
174        return read(stream, Json.UTF8, introspectStringValues);
175    }
176
177    /**
178     * Read the JSON representation from supplied input stream and construct the {@link Document} representation, using the
179     * supplied {@link Charset character set}.
180     *
181     * @param stream the input stream; may not be null
182     * @param charset the character set that should be used; may not be null
183     * @param introspectStringValues true if the string values should be examined for common patterns, or false otherwise
184     * @return the in-memory {@link Document} representation
185     * @throws ParsingException if there was a problem reading from the stream
186     */
187    public Document read( InputStream stream,
188                          Charset charset,
189                          boolean introspectStringValues ) throws ParsingException {
190        return read(new InputStreamReader(stream, charset), introspectStringValues);
191    }
192
193    /**
194     * Read the JSON representation from supplied input stream and construct the {@link Document} representation.
195     * 
196     * @param reader the IO reader; may not be null
197     * @param introspectStringValues true if the string values should be examined for common patterns, or false otherwise
198     * @return the in-memory {@link Document} representation
199     * @throws ParsingException if there was a problem reading from the stream
200     */
201    public Document read( Reader reader,
202                          boolean introspectStringValues ) throws ParsingException {
203        // Create an object so that this reader is thread safe ...
204        ValueMatcher matcher = introspectStringValues ? DATE_VALUE_MATCHER : SIMPLE_VALUE_MATCHER;
205        return new Parser(new Tokenizer(reader), VALUE_FACTORY, matcher).parseDocument();
206    }
207
208    /**
209     * Read the JSON representation from supplied string and construct the {@link Document} representation.
210     * 
211     * @param json the JSON representation; may not be null
212     * @param introspectStringValues true if the string values should be examined for common patterns, or false otherwise
213     * @return the in-memory {@link Document} representation
214     * @throws ParsingException if there was a problem reading from the stream
215     */
216    public Document read( String json,
217                          boolean introspectStringValues ) throws ParsingException {
218        return read(new StringReader(json), introspectStringValues);
219    }
220
221    /**
222     * Return a {@link DocumentSequence} that can be used to pull multiple documents from the stream.
223     * 
224     * @param stream the input stream; may not be null
225     * @return the sequence that can be used to get one or more Document instances from a single input
226     */
227    public DocumentSequence readMultiple( InputStream stream ) {
228        return readMultiple(stream, DEFAULT_INTROSPECT);
229    }
230
231    /**
232     * Return a {@link DocumentSequence} that can be used to pull multiple documents from the stream.
233     * 
234     * @param stream the input stream; may not be null
235     * @param introspectStringValues true if the string values should be examined for common patterns, or false otherwise
236     * @return the sequence that can be used to get one or more Document instances from a single input
237     */
238    public DocumentSequence readMultiple( InputStream stream,
239                                          boolean introspectStringValues ) {
240        return readMultiple(new InputStreamReader(stream, Json.UTF8), introspectStringValues);
241    }
242
243    /**
244     * Return a {@link DocumentSequence} that can be used to pull multiple documents from the stream.
245     * 
246     * @param reader the IO reader; may not be null
247     * @return the sequence that can be used to get one or more Document instances from a single input
248     */
249    public DocumentSequence readMultiple( Reader reader ) {
250        return readMultiple(reader, DEFAULT_INTROSPECT);
251    }
252
253    /**
254     * Return a {@link DocumentSequence} that can be used to pull multiple documents from the stream.
255     * 
256     * @param reader the IO reader; may not be null
257     * @param introspectStringValues true if the string values should be examined for common patterns, or false otherwise
258     * @return the sequence that can be used to get one or more Document instances from a single input
259     */
260    public DocumentSequence readMultiple( Reader reader,
261                                          boolean introspectStringValues ) {
262        // Create an object so that this reader is thread safe ...
263        final Tokenizer tokenizer = new Tokenizer(reader);
264        ValueMatcher matcher = introspectStringValues ? DATE_VALUE_MATCHER : SIMPLE_VALUE_MATCHER;
265        final Parser parser = new Parser(tokenizer, VALUE_FACTORY, matcher);
266        return () -> {
267            if (tokenizer.isFinished()) return null;
268            Document doc = parser.parseDocument(false);
269            // System.out.println(Json.writePretty(doc));
270            return doc;
271        };
272    }
273
274    /**
275     * Parse the number represented by the supplied (unquoted) JSON field value.
276     *
277     * @param value the string representation of the value
278     * @return the number, or null if the value could not be parsed
279     */
280    public static Number parseNumber( String value ) {
281        // Try to parse as a number ...
282        char c = value.charAt(0);
283
284        if ((c >= '0' && c <= '9') || c == '.' || c == '-' || c == '+') {
285            // It's definitely a number ...
286            if (c == '0' && value.length() > 2) {
287                // it might be a hex number that starts with '0x'
288                char two = value.charAt(1);
289                if (two == 'x' || two == 'X') {
290                    try {
291                        // Parse the remainder of the hex number ...
292                        return Integer.parseInt(value.substring(2), 16);
293                    } catch (NumberFormatException e) {
294                        // Ignore and continue ...
295                    }
296                }
297            }
298            // Try parsing as a double ...
299            try {
300                if ((value.indexOf('.') > -1) || (value.indexOf('E') > -1) || (value.indexOf('e') > -1)) {
301                    return Double.parseDouble(value);
302                }
303                Long longObj = new Long(value);
304                long longValue = longObj.longValue();
305                int intValue = longObj.intValue();
306                if (longValue == intValue) {
307                    // Then it's just an integer ...
308                    return new Integer(intValue);
309                }
310                return longObj;
311            } catch (NumberFormatException e) {
312                // ignore ...
313            }
314        }
315        return null;
316    }
317
318    /**
319     * The component that parses a tokenized JSON stream.
320     * 
321     * @author Randall Hauch <rhauch@redhat.com> (C) 2011 Red Hat Inc.
322     * @since 5.1
323     */
324    @NotThreadSafe
325    public static class Parser {
326
327        private final DocumentValueFactory values;
328        private final Tokenizer tokens;
329        private final ValueMatcher valueMatcher;
330
331        /**
332         * Create a new JsonReader that uses the supplied {@link Tokenizer} instance.
333         * 
334         * @param tokenizer the tokenizer that tokenizes the stream of JSON content; may not be null
335         * @param values the factory for creating value objects; may not be null
336         * @param valueMatcher the component that looks for patterns within string values to create alternative objects; may not
337         *        be null
338         */
339        public Parser( Tokenizer tokenizer,
340                       DocumentValueFactory values,
341                       ValueMatcher valueMatcher ) {
342            this.tokens = tokenizer;
343            this.values = values;
344            this.valueMatcher = valueMatcher;
345        }
346
347        /**
348         * Parse the stream for the next JSON document.
349         * 
350         * @return the document, or null if there are no more documents
351         * @throws ParsingException if there is a problem parsing the value
352         */
353        public Document parseDocument() throws ParsingException {
354            return parseDocument(null, true);
355        }
356
357        /**
358         * Parse the stream for the next JSON document.
359         * 
360         * @param failIfNotValidDocument true if this method should throw an exception if the stream does not contain a valid
361         *        document, or false if null should be returned if there is no valid document on the stream
362         * @return the document, or null if there are no more documents
363         * @throws ParsingException if there is a problem parsing the value
364         */
365        public Document parseDocument( boolean failIfNotValidDocument ) throws ParsingException {
366            return parseDocument(null, failIfNotValidDocument);
367        }
368
369        protected BasicDocument newDocument() {
370            return new BasicDocument();
371        }
372
373        /**
374         * Parse the stream for the next JSON document.
375         * 
376         * @param hasReservedFieldNames the flag that should be set if this document contains field names that are reserved
377         * @param failIfNotValidDocument true if this method should throw an exception if the stream does not contain a valid
378         *        document, or false if null should be returned if there is no valid document on the stream
379         * @return the document, or null if there are no more documents
380         * @throws ParsingException if there is a problem parsing the value
381         */
382        protected Document parseDocument( AtomicBoolean hasReservedFieldNames,
383                                          boolean failIfNotValidDocument ) throws ParsingException {
384            if (tokens.nextUsefulChar() != '{') {
385                if (failIfNotValidDocument) {
386                    throw tokens.error("JSON documents must begin with a '{' character");
387                }
388                // otherwise just return ...
389                return null;
390            }
391            BasicDocument doc = newDocument();
392            do {
393                String fieldName = null;
394                // Peek at the next character on the stream ...
395                switch (tokens.peek()) {
396                    case 0:
397                        throw tokens.error("JSON documents must end with a '}' character");
398                    case '}':
399                        tokens.next();
400                        return doc;
401                    default:
402                        // This should be a field name, so read it ...
403                        fieldName = tokens.nextString();
404                        break;
405                }
406                // Now look for any of the following delimiters: ':', "->", or "=>"
407                tokens.nextFieldDelim();
408
409                // Now look for a value ...
410                Object value = parseValue();
411                doc.put(fieldName, value);
412
413                // Determine if this field is a reserved
414                if (hasReservedFieldNames != null && isReservedFieldName(fieldName)) {
415                    hasReservedFieldNames.set(true);
416                }
417
418                // Look for the delimiter between fields ...
419                if (tokens.nextDocumentDelim()) return doc;
420            } while (true);
421        }
422
423        protected final boolean isReservedFieldName( String fieldName ) {
424            return fieldName.length() != 0 && fieldName.charAt(0) == '$';
425        }
426
427        /**
428         * Parse the JSON array on the stream, beginning with the '[' character until the ']' character, which is consumed.
429         * 
430         * @return the array representation; never null but possibly an empty array
431         * @throws ParsingException if there is a problem parsing the value
432         */
433        public BasicArray parseArray() throws ParsingException {
434            if (tokens.nextUsefulChar() != '[') {
435                throw tokens.error("JSON arrays must begin with a '[' character");
436            }
437            BasicArray array = new BasicArray();
438            boolean expectValueSeparator = false;
439            do {
440                // Peek at the next character on the stream ...
441                char c = tokens.peek();
442                switch (c) {
443                    case 0:
444                        throw tokens.error("JSON arrays must end with a ']' character");
445                    case ']':
446                        tokens.next();
447                        return array;
448                    case ',':
449                        tokens.next();
450                        expectValueSeparator = false;
451                        break;
452                    default:
453                        if (expectValueSeparator) {
454                            throw tokens.error("Invalid character in JSON array: '" + c + "'  at line " + tokens.lineNumber() +
455                                               " column " + tokens.columnNumber());
456                        }
457                        // This should be a value ..
458                        Object value = parseValue();
459                        array.addValue(value);
460                        expectValueSeparator = true;
461                        break;
462                }
463            } while (true);
464        }
465
466        /**
467         * Parse the stream for the next field value, which can be one of the following values:
468         * <ul>
469         * <li>a nested document</li>
470         * <li>an array of values</li>
471         * <li>a string literal, surrounded by single-quote characters</li>
472         * <li>a string literal, surrounded by double-quote characters</li>
473         * <li>a string literal date of the form <code>"<i>yyyy</i>-<i>MM</i>-<i>dd</i>T<i>HH</i>:<i>mm</i>:<i>ss</i>"</code>
474         * where <code>T</code> is a literal character</li>
475         * <li>a string literal date of the form <code>"<i>yyyy</i>-<i>MM</i>-<i>dd</i>T<i>HH</i>:<i>mm</i>:<i>ss</i>Z"</code>
476         * where <code>T</code> and <code>Z</code> are literal characters</li>
477         * <li>a string literal date of the form
478         * <code>"<i>yyyy</i>-<i>MM</i>-<i>dd</i>T<i>HH</i>:<i>mm</i>:<i>ss</i>GMT+<i>00</i>:<i>00</i>"</code> where
479         * <code>T</code>, and <code>GMT</code> are literal characters</li>
480         * <li>a string literal date of the form <code>"/Date(<i>millisOrIso</i>)/"</code></li>
481         * <li>a string literal date of the form <code>"\/Date(<i>millisOrIso</i>)\/"</code></li>
482         * <li>a date literal of the form <code>new Date(<i>millisOrIso</i>)</code></li>
483         * <li>a date literal of the form <code>Date(<i>millisOrIso</i>)</code></li>
484         * <li>a function of the form <code>new <i>functionName</i>(<i>parameters</i>)</code> where <code><i>parameters</i></code>
485         * consists of one or more values as parsed by this method
486         * </ul>
487         * Note that in the date forms listed above, <code><i>millisOrIso</i></code> is either a long value representing the
488         * number of milliseconds since epoch or a string literal in ISO-8601 format representing a date and time.
489         * 
490         * @return the field value
491         * @throws ParsingException if there is a problem parsing the value
492         */
493        public Object parseValue() throws ParsingException {
494            char c = tokens.peek();
495            switch (c) {
496                case 0:
497                    // There's nothing left ...
498                    return null;
499                case '{':
500                    // Nested object ...
501                    AtomicBoolean hasReservedFieldNames = new AtomicBoolean();
502                    Document doc = parseDocument(hasReservedFieldNames, true);
503                    if (!hasReservedFieldNames.get()) {
504                        return doc;
505                    }
506                    // Convert the doc with reserved field names ...
507                    return processDocumentWithReservedFieldNames(doc);
508                case '[':
509                    // Nested array ...
510                    return parseArray();
511                case '"':
512                case '\'':
513                    String literal = tokens.nextString();
514                    Object value = valueMatcher.parseValue(literal);
515                    return value != null ? value : literal;
516                case 'd':
517                case 'n':
518                    String newToken = tokens.nextWord(); // read the 'new' token
519                    if ("new".equalsIgnoreCase(newToken) || "date".equalsIgnoreCase(newToken)) {
520                        return parseFunction();
521                    }
522                    break;
523            }
524
525            // Looks like it's a number, so try that ...
526            String number = tokens.nextNumber();
527            return number != null ? parseValue(number, tokens.lineNumber(), tokens.columnNumber()) : number;
528        }
529
530        /**
531         * Parse the value given by the supplied string located at the supplied line and column numbers. This method looks for
532         * known constant values, then attempts to parse the value as a number, and then calls
533         * {@link #parseUnknownValue(String, int, int)}.
534         * 
535         * @param value the string representation of the value
536         * @param lineNumber the line number for the beginning of the value
537         * @param columnNumber the column number for the beginning of the value
538         * @return the value
539         * @throws ParsingException if there is a problem parsing the value
540         */
541        public Object parseValue( String value,
542                                  int lineNumber,
543                                  int columnNumber ) throws ParsingException {
544            if (value.length() == 0) return value;
545            if ("true".equalsIgnoreCase(value)) return Boolean.TRUE;
546            if ("false".equalsIgnoreCase(value)) return Boolean.FALSE;
547            if ("null".equalsIgnoreCase(value)) return Null.getInstance();
548
549            // Try to parse as a number ...
550            Number number = parseNumber(value);
551            if (number != null) return number;
552
553            return parseUnknownValue(value, lineNumber, columnNumber);
554        }
555
556        /**
557         * Parse the number represented by the supplied value. This method is called by the {@link #parseValue(String, int, int)}
558         * method.
559         * 
560         * @param value the string representation of the value
561         * @return the number, or null if the value could not be parsed
562         */
563        protected Number parseNumber( String value ) {
564            return JsonReader.parseNumber(value);
565        }
566
567        /**
568         * Override this method if custom value types are expected.
569         * 
570         * @param value the string representation of the value
571         * @param lineNumber the line number at which the value starts
572         * @param columnNumber the column number at which the value starts
573         * @return the value
574         * @throws ParsingException if there is a problem parsing the value
575         */
576        protected Object parseUnknownValue( String value,
577                                            int lineNumber,
578                                            int columnNumber ) throws ParsingException {
579            return value;
580        }
581
582        /**
583         * Parse a function call on the stream. The 'new' keyword has already been processed.
584         * 
585         * @return the result of the evaluation of the function
586         * @throws ParsingException if there is a problem parsing the value
587         */
588        public Object parseFunction() throws ParsingException {
589            // Parse the function name ...
590            int line = tokens.lineNumber();
591            int col = tokens.columnNumber();
592            String functionName = tokens.nextString();
593            FunctionCall function = new FunctionCall(functionName, line, col);
594
595            // Read the open parenthesis ..
596            char c = tokens.nextUsefulChar();
597            if (c != '(') {
598                throw tokens.error("Expected '(' after function name \"" + functionName + "\" and at line " + tokens.lineNumber()
599                                   + ", column " + tokens.columnNumber());
600            }
601
602            // Read the parameters ...
603            do {
604                line = tokens.lineNumber();
605                col = tokens.columnNumber();
606                Object parameter = parseValue();
607                if (parameter == null) {
608                    break;
609                }
610                function.add(parameter, line, col);
611            } while (true);
612
613            // Now evaluate the function ...
614            Object value = evaluateFunction(function);
615            return value != null ? value : evaluateUnknownFunction(function);
616        }
617
618        /**
619         * Method that is called to evaluate the supplied function. This method may be overridden by subclasses to handle custom
620         * functions.
621         * 
622         * @param function the function definition
623         * @return the value that resulted from evaluating the function, or null if the function call could not be evaluated
624         * @throws ParsingException if there is a problem parsing the value
625         */
626        public Object evaluateFunction( FunctionCall function ) throws ParsingException {
627            int numParams = function.size();
628            if ("date".equalsIgnoreCase(function.getFunctionName())) {
629                if (numParams > 0) {
630                    // The parameter should be a long or a timestamp ...
631                    FunctionParameter param1 = function.get(0);
632                    Object value = param1.getValue();
633                    if (value instanceof Long) {
634                        Long millis = (Long)value;
635                        return values.createDate(millis.longValue());
636                    }
637                    if (value instanceof Integer) {
638                        Integer millis = (Integer)value;
639                        return values.createDate(millis.longValue());
640                    }
641                    if (value instanceof String) {
642                        String valueStr = (String)value;
643                        try {
644                            return values.createDate(valueStr);
645                        } catch (ParseException e) {
646                            // Not a valid date ...
647                            throw tokens.error("Expecting the \"new Date(...)\" parameter to be a valid number of milliseconds or ISO date string, but found \""
648                                               + param1.getValue()
649                                               + "\" at line "
650                                               + param1.getLineNumber()
651                                               + ", column "
652                                               + param1.getColumnNumber());
653                        }
654                    }
655                }
656                // Not a valid date ...
657                throw tokens.error("The date function requires one parameter at line " + function.getLineNumber() + ", column "
658                                   + function.getColumnNumber());
659            }
660            return null;
661        }
662
663        /**
664         * Method that is called when the function call described by the parameter could not be evaluated. By default, the string
665         * representation of the function is returned.
666         * 
667         * @param function the function definition
668         * @return the value that resulted from evaluating the function
669         * @throws ParsingException if there is a problem parsing the value
670         */
671        protected Object evaluateUnknownFunction( FunctionCall function ) throws ParsingException {
672            return function.toString();
673        }
674
675        @SuppressWarnings( "deprecation" )
676        protected Object processDocumentWithReservedFieldNames( Document doc ) {
677            if (doc == null) return null;
678            Object value = null;
679            int numFields = doc.size();
680            if (numFields == 0) return doc;
681            try {
682                if (numFields == 1) {
683                    if (!Null.matches(value = doc.get(OBJECT_ID))) {
684                        String bytesInBase16 = value.toString();
685                        return values.createObjectId(bytesInBase16);
686                    }
687                    if (!Null.matches(value = doc.get(DATE))) {
688                        if (value instanceof Date) {
689                            return value;
690                        }
691                        String isoDate = value.toString();
692                        try {
693                            return values.createDate(isoDate);
694                        } catch (ParseException e) {
695                            Long millis = Long.parseLong(isoDate);
696                            return values.createDate(millis);
697                        }
698                    }
699                    if (!Null.matches(value = doc.get(REGEX_PATTERN))) {
700                        String pattern = value.toString();
701                        return values.createRegex(pattern, null);
702                    }
703                    if (!Null.matches(value = doc.get(UUID))) {
704                        return values.createUuid(value.toString());
705                    }
706                    if (!Null.matches(value = doc.get(CODE))) {
707                        String code = value.toString();
708                        return values.createCode(code);
709                    }
710                } else if (numFields == 2) {
711                    if (!Null.matches(value = doc.get(TIMESTAMP))) {
712                        int time = doc.getInteger(TIMESTAMP);
713                        int inc = doc.getInteger(INCREMENT);
714                        return values.createTimestamp(time, inc);
715                    }
716                    if (!Null.matches(value = doc.get(REGEX_PATTERN))) {
717                        String pattern = value.toString();
718                        String options = doc.getString(REGEX_OPTIONS);
719                        return values.createRegex(pattern, options);
720                    }
721                    if (!Null.matches(value = doc.get(CODE))) {
722                        String code = value.toString();
723                        Document scope = doc.getDocument(SCOPE);
724                        return scope != null ? values.createCode(code, scope) : values.createCode(code);
725                    }
726                    if (!Null.matches(value = doc.get(BINARY_TYPE))) {
727                        char c = value.toString().charAt(0);
728                        byte type = 0x00;
729                        switch (c) {
730                            case '0':
731                                type = Bson.BinaryType.GENERAL;
732                                break;
733                            case '1':
734                                type = Bson.BinaryType.FUNCTION;
735                                break;
736                            case '2':
737                                type = Bson.BinaryType.BINARY;
738                                break;
739                            case '3':
740                                type = Bson.BinaryType.UUID;
741                                break;
742                            case '5':
743                                type = Bson.BinaryType.MD5;
744                                break;
745                            case '8':
746                                c = value.toString().charAt(1);
747                                if (c == '0') {
748                                    type = Bson.BinaryType.USER_DEFINED;
749                                }
750                                break;
751                        }
752                        String data = doc.getString(BASE_64);
753                        return values.createBinary(type, Base64.decode(data));
754                    }
755                }
756            } catch (Throwable e) {
757                // ignore
758            }
759            return doc;
760        }
761
762        protected static class FunctionCall implements Iterable<FunctionParameter> {
763            private final String functionName;
764            private final List<FunctionParameter> parameters = new LinkedList<>();
765            private final int lineNumber;
766            private final int columnNumber;
767
768            public FunctionCall( String functionName,
769                                 int lineNumber,
770                                 int columnNumber ) {
771                this.functionName = functionName;
772                this.lineNumber = lineNumber;
773                this.columnNumber = columnNumber;
774            }
775
776            public String getFunctionName() {
777                return functionName;
778            }
779
780            public void add( Object parameter,
781                             int lineNumber,
782                             int columnNumber ) {
783                this.parameters.add(new FunctionParameter(parameter, lineNumber, columnNumber));
784            }
785
786            @Override
787            public Iterator<FunctionParameter> iterator() {
788                return parameters.iterator();
789            }
790
791            public FunctionParameter get( int index ) {
792                return parameters.get(index);
793            }
794
795            public int size() {
796                return parameters.size();
797            }
798
799            public int getLineNumber() {
800                return lineNumber;
801            }
802
803            public int getColumnNumber() {
804                return columnNumber;
805            }
806
807            @Override
808            public String toString() {
809                StringBuilder sb = new StringBuilder(functionName);
810                sb.append('(');
811                boolean first = true;
812                for (FunctionParameter parameter : parameters) {
813                    if (first) {
814                        first = false;
815                    } else {
816                        sb.append(',');
817                    }
818                    sb.append(parameter.getValue());
819                }
820                sb.append(')');
821                return sb.toString();
822            }
823        }
824
825        @Immutable
826        protected static class FunctionParameter {
827            private final Object value;
828            private final int lineNumber;
829            private final int columnNumber;
830
831            public FunctionParameter( Object value,
832                                      int lineNumber,
833                                      int columnNumber ) {
834                this.value = value;
835                this.lineNumber = lineNumber;
836                this.columnNumber = columnNumber;
837            }
838
839            public Object getValue() {
840                return value;
841            }
842
843            public int getLineNumber() {
844                return lineNumber;
845            }
846
847            public int getColumnNumber() {
848                return columnNumber;
849            }
850
851            @Override
852            public String toString() {
853                return Json.write(value);
854            }
855        }
856    }
857
858    /**
859     * The component that matches a string value for certain patterns. If the value matches a known pattern, it return the
860     * appropriate value object; otherwise, the supplied string value is returned.
861     * 
862     * @author Randall Hauch <rhauch@redhat.com> (C) 2011 Red Hat Inc.
863     * @since 5.1
864     */
865    @NotThreadSafe
866    public static interface ValueMatcher {
867
868        /**
869         * Parse the value given by the supplied string into an appropriate value object. This method looks for specific patterns
870         * of Date strings; if no known pattern is found, it just returns the supplied value.
871         * 
872         * @param value the string representation of the value
873         * @return the value
874         */
875        public Object parseValue( String value );
876    }
877
878    /**
879     * The component that matches a string value for certain patterns. If the value matches a known pattern, it return the
880     * appropriate value object; otherwise, the supplied string value is returned.
881     * 
882     * @author Randall Hauch <rhauch@redhat.com> (C) 2011 Red Hat Inc.
883     * @since 5.1
884     */
885    @NotThreadSafe
886    public static class SimpleValueMatcher implements ValueMatcher {
887
888        protected final DocumentValueFactory values;
889
890        /**
891         * Create a new matcher that uses the supplied {@link DocumentValueFactory} instance.
892         * 
893         * @param values the factory for creating value objects; may not be null
894         */
895        public SimpleValueMatcher( DocumentValueFactory values ) {
896            this.values = values;
897        }
898
899        /**
900         * Parse the value given by the supplied string into an appropriate value object. This method looks for specific patterns
901         * of Date strings; if no known pattern is found, it just returns the supplied value.
902         * 
903         * @param value the string representation of the value
904         * @return the value
905         */
906        @Override
907        public Object parseValue( String value ) {
908            return value;
909        }
910    }
911
912    /**
913     * The component that parses a tokenized JSON stream and attempts to evaluate literal values such as dates
914     * 
915     * @author Randall Hauch <rhauch@redhat.com> (C) 2011 Red Hat Inc.
916     * @since 5.1
917     */
918    @NotThreadSafe
919    public static class DateValueMatcher extends SimpleValueMatcher {
920
921        /**
922         * Create a new matcher that uses the supplied {@link DocumentValueFactory} instance.
923         * 
924         * @param values the factory for creating value objects; may not be null
925         */
926        public DateValueMatcher( DocumentValueFactory values ) {
927            super(values);
928        }
929
930        /**
931         * Parse the value given by the supplied string into an appropriate value object. This method looks for specific patterns
932         * of Date strings; if no known pattern is found, it just returns the supplied value.
933         * 
934         * @param value the string representation of the value
935         * @return the value
936         */
937        @Override
938        public Object parseValue( String value ) {
939            if (value != null) {
940                if (value.length() > 2) {
941                    Date date = parseDateFromLiteral(value);
942                    if (date != null) {
943                        return date;
944                    }
945                }
946                // Unescape escaped characters ...
947                value = unescapeValue(value);
948            }
949            return value;
950        }
951
952        protected String unescapeValue( String value ) {
953            if (value == null || value.length() == 0) return value;
954
955            StringBuilder sb = new StringBuilder(value.length());
956            CharacterIterator iter = new StringCharacterIterator(value);
957            for (char c = iter.first(); c != CharacterIterator.DONE; c = iter.next()) {
958                switch (c) {
959                    case '\\':
960                        // The character might be an escape sequence, so output the backslash ...
961                        char next = iter.next();
962                        switch (next) {
963                            case CharacterIterator.DONE:
964                                // This was the last character, so we're done ...
965                                sb.append(c);
966                                break;
967                            case '/': // optional
968                            case '\b':
969                            case '\f':
970                            case '\n':
971                            case '\r':
972                            case '\t':
973                                // This is an escaped sequence ...
974                                sb.append(next);
975                                break;
976                            case 'u':
977                                // This is an unicode escape sequence, so we already output one of them ...
978                                char first = iter.next();
979                                char second = iter.next();
980                                char third = iter.next();
981                                char fourth = iter.next();
982                                String string = "" + first + second + third + fourth;
983                                try {
984                                    char uni = (char)Integer.parseInt(string, 16);
985                                    sb.append(uni);
986                                } catch (NumberFormatException e) {
987                                    // this is not a valid unicode escape sequence so just append it as is
988                                    sb.append("\\u").append(string);
989                                    continue;
990                                }
991                                break;
992                            default:
993                                // It's not an escape sequence that we care about. We've already written the backslash,
994                                // so just write the character ...
995                                sb.append(c);
996                                sb.append(next);
997                        }
998                        break;
999                    default:
1000                        // Unicode escapes are handled above ...
1001                        sb.append(c);
1002                        break;
1003                }
1004            }
1005            return sb.toString();
1006        }
1007
1008        /**
1009         * Parse the date represented by the supplied value. This method is called by the {@link #parseValue(String)} method. This
1010         * method checks the following formats:
1011         * <ul>
1012         * <li>a string literal date of the form <code>"<i>yyyy</i>-<i>MM</i>-<i>dd</i>T<i>HH</i>:<i>mm</i>:<i>ss</i>"</code>
1013         * where <code>T</code> is a literal character</li>
1014         * <li>a string literal date of the form <code>"<i>yyyy</i>-<i>MM</i>-<i>dd</i>T<i>HH</i>:<i>mm</i>:<i>ss</i>Z"</code>
1015         * where <code>T</code> and <code>Z</code> are literal characters</li>
1016         * <li>a string literal date of the form
1017         * <code>"<i>yyyy</i>-<i>MM</i>-<i>dd</i>T<i>HH</i>:<i>mm</i>:<i>ss</i>GMT+<i>00</i>:<i>00</i>"</code> where
1018         * <code>T</code>, and <code>GMT</code> are literal characters</li>
1019         * <li>a string literal date of the form <code>"/Date(<i>millisOrIso</i>)/"</code></li>
1020         * <li>a string literal date of the form <code>"\/Date(<i>millisOrIso</i>)\/"</code></li>
1021         * </ul>
1022         * <p>
1023         * Note that this method does not handle the <code>new Date(...)</code> or <code>Date(...)</code> representations, as
1024         * that's handled elsewhere.
1025         * </p>
1026         * 
1027         * @param value the string representation of the value; never null and never empty
1028         * @return the number, or null if the value could not be parsed
1029         */
1030        protected Date parseDateFromLiteral( String value ) {
1031            char f = value.charAt(0);
1032            if (Character.isDigit(f)) {
1033                // Try as simply an ISO-8601 formatted date ...
1034                return evaluateDate(value);
1035            }
1036            if (value.startsWith("\\/Date(") && value.endsWith(")\\/")) {
1037                String millisOrIso = value.substring(7, value.length() - 3).trim();
1038                return evaluateDate(millisOrIso);
1039            }
1040            if (value.startsWith("/Date(") && value.endsWith(")/")) {
1041                String millisOrIso = value.substring(6, value.length() - 2).trim();
1042                return evaluateDate(millisOrIso);
1043            }
1044            return null;
1045        }
1046
1047        protected Date evaluateDate( String millisOrIso ) {
1048            try {
1049                return values.createDate(millisOrIso);
1050            } catch (ParseException e) {
1051                // not an ISO-8601 format ...
1052            }
1053            return null;
1054        }
1055
1056    }
1057
1058    /**
1059     * The component that tokenizes a stream of JSON content.
1060     * 
1061     * @author Randall Hauch <rhauch@redhat.com> (C) 2011 Red Hat Inc.
1062     * @since 5.1
1063     */
1064    @NotThreadSafe
1065    public static class Tokenizer {
1066
1067        private final Reader reader;
1068        private int lineNumber;
1069        private int columnNumber;
1070        private boolean finished;
1071        private boolean hasPrevious;
1072        private char previous;
1073        private StringBuilder stringBuilder = new StringBuilder(128);
1074
1075        /**
1076         * Create a new tokenizer that uses the supplied {@link Reader Java IO Reader} instance.
1077         * 
1078         * @param reader the reader for accessing the JSON content; may not be null
1079         */
1080        public Tokenizer( Reader reader ) {
1081            this.reader = reader;
1082        }
1083
1084        public boolean isFinished() {
1085            return finished;
1086        }
1087
1088        protected char next() throws ParsingException {
1089            char c = 0;
1090            if (hasPrevious) {
1091                hasPrevious = false;
1092                c = previous;
1093            } else {
1094                try {
1095                    int x = reader.read();
1096                    if (x <= 0) {
1097                        // We've reached the end of the stream ...
1098                        finished = true;
1099                        c = 0;
1100                    } else {
1101                        c = (char)x;
1102                    }
1103                } catch (IOException e) {
1104                    throw error("Error reading at line " + lineNumber + ", column " + columnNumber + ": "
1105                                + e.getLocalizedMessage(),
1106                                e);
1107                }
1108            }
1109            // It's a valid character, but we have to advance our line & column counts ...
1110            if (previous == '\r') {
1111                ++lineNumber;
1112                columnNumber = (c == '\n' ? 0 : 1);
1113            } else if (c == '\n') {
1114                ++lineNumber;
1115                columnNumber = 0;
1116            } else {
1117                ++columnNumber;
1118            }
1119            previous = c;
1120            return c;
1121        }
1122
1123        public String next( int characterCount ) throws ParsingException {
1124            StringBuilder sb = stringBuilder();
1125            for (int i = 0; i != characterCount; ++i) {
1126                sb.append(next());
1127            }
1128            return complete(sb);
1129        }
1130
1131        protected final StringBuilder stringBuilder() {
1132            StringBuilder stringBuilder = this.stringBuilder != null ? this.stringBuilder : new StringBuilder(128);
1133            this.stringBuilder = null;
1134            stringBuilder.delete(0, stringBuilder.length());
1135            return stringBuilder;
1136        }
1137
1138        protected final String complete( StringBuilder sb ) {
1139            assert sb != null;
1140            assert stringBuilder == null;
1141            stringBuilder = sb;
1142            return sb.toString();
1143        }
1144
1145        public char nextUsefulChar() throws ParsingException {
1146            boolean withinComment = false;
1147            do {
1148                char next = next();
1149                if (next == '/') {
1150                    char afterNext = next();
1151                    if (afterNext != '/') {
1152                        throw error("Invalid character '" + afterNext + "' (expected comment //)");
1153                    }
1154                    withinComment = true;
1155                    continue;
1156                }
1157                boolean isLineSeparator = (next == '\n') || (next == '\r');
1158                if (isLineSeparator && withinComment) {
1159                    withinComment = false;
1160                }
1161                if (next == 0 || (!withinComment && next != ' ' && next != '\t' && !isLineSeparator)) return next;
1162            } while (true);
1163        }
1164
1165        public char peek() throws ParsingException {
1166            if (hasPrevious) {
1167                return previous;
1168            }
1169            char next = nextUsefulChar();
1170            hasPrevious = true;
1171            previous = next;
1172            --columnNumber;
1173            return next;
1174        }
1175
1176        /**
1177         * Read the next quoted string from the stream, where stream begins with the a single-quote or double-quote character and
1178         * the string ends with the same quote character.
1179         * 
1180         * @return the next string; never null
1181         * @throws ParsingException
1182         */
1183        public String nextString() throws ParsingException {
1184            char c = nextUsefulChar();
1185            switch (c) {
1186                case '"':
1187                case '\'':
1188                    return nextString(c);
1189            }
1190            throw error("Expecting a field name at line " + lineNumber + ", column " + columnNumber
1191                        + ". Check for a missing comma.");
1192        }
1193
1194        public String nextString( char endQuote ) throws ParsingException {
1195            StringBuilder sb = stringBuilder();
1196            char c = 0;
1197            do {
1198                c = next();
1199                switch (c) {
1200                    case 0:
1201                    case '\n':
1202                    case '\r':
1203                        // The string was not properly terminated ...
1204                        throw error("The string was not terminated before the end of line or end of document, at line "
1205                                    + lineNumber + ", column " + columnNumber);
1206                    case '\\':
1207                        // Escape sequence ...
1208                        c = next();
1209                        switch (c) {
1210                            case '\'': // single quote
1211                            case '"': // double quote
1212                            case '\\': // reverse solidus
1213                            case '/': // forward solidus
1214                                break;
1215                            case 'b':
1216                                c = '\b';
1217                                break;
1218                            case 'f':
1219                                c = '\f';
1220                                break;
1221                            case 'n':
1222                                c = '\n';
1223                                break;
1224                            case 'r':
1225                                c = '\r';
1226                                break;
1227                            case 't':
1228                                c = '\t';
1229                                break;
1230                            case 'u':
1231                                // Unicode sequence made of exactly 4 hex characters ...
1232                                char[] hex = new char[4];
1233                                hex[0] = next();
1234                                hex[1] = next();
1235                                hex[2] = next();
1236                                hex[3] = next();
1237                                String code = new String(hex, 0, 4);
1238                                try {
1239                                    c = (char)Integer.parseInt(code, 16); // hex
1240                                } catch (NumberFormatException e) {
1241                                    // this is not a valid unicode escape sequence so just append it as is
1242                                    sb.append('\\').append('u').append(code);
1243                                    continue;
1244                                }
1245                                break;
1246                            default:
1247                                // No other characters are valid escaped sequences, so this is actually just a backslash
1248                                // followed by the current character c. So append the backslash ...
1249                                sb.append('\\');
1250                                // then the character ...
1251                                break;
1252                        }
1253                        sb.append(c);
1254                        break;
1255                    default:
1256                        // Just a regular character (or the end quote) ...
1257                        if (c == endQuote) {
1258                            // This is the only way to successfully exit this method!
1259                            return complete(sb);
1260                        }
1261                        // just a regular character ...
1262                        sb.append(c);
1263                }
1264            } while (true);
1265        }
1266
1267        public void nextFieldDelim() throws ParsingException {
1268            try {
1269                switch (nextUsefulChar()) {
1270                    case ':':
1271                    case '=':
1272                        if (peek() == '>') {
1273                            next(); // consume the '>'
1274                        }
1275                        break;
1276                }
1277            } catch (ParsingException e) {
1278                throw error("Expecting a field delimiter (either ':', '=' or '=>') at line " + lineNumber + ", column "
1279                            + columnNumber);
1280            }
1281        }
1282
1283        /**
1284         * Consume the next document delimiter (either a ',' or a ';'), and return whether the end-of-document character (e.g.,
1285         * '}') has been consumed. This will correctly handle repeated delimiters, which are technically incorrect.
1286         * 
1287         * @return true if a '}' has been consumed, or false otherwise
1288         * @throws ParsingException if the document delimiter could not be read
1289         */
1290        public boolean nextDocumentDelim() throws ParsingException {
1291            switch (nextUsefulChar()) {
1292                case ';': // handle ';' delimiters, too!
1293                case ',':
1294                    switch (peek()) {
1295                        case ':':
1296                        case ',':
1297                            // There are multiple delimiters in a row. Strictly speaking, this is invalid but we
1298                            // can easily handle it anyway ...
1299                            return nextDocumentDelim();
1300                        case '}':
1301                            // The comma was before '}' - this is not strictly well-formed, but we'll handle it
1302                            next();
1303                            return true;
1304                    }
1305                    return false;
1306                case '}':
1307                    return true;
1308            }
1309            return false;
1310        }
1311
1312        /**
1313         * Return a string containing the next number on the stream.
1314         * 
1315         * @return the next number as a string, or null if there is no content on the stream
1316         * @throws ParsingException if the number could not be read
1317         */
1318        public String nextNumber() throws ParsingException {
1319            char c = peek();
1320            if (c == 0) {
1321                return null;
1322            }
1323            StringBuilder sb = stringBuilder();
1324            while (c > ' ' && "{}[]:\"=#/\\',;".indexOf(c) <= -1) {
1325                if (c == 0) {
1326                    break;
1327                }
1328                sb.append(next());
1329                c = peek();
1330            }
1331            return complete(sb);
1332        }
1333
1334        /**
1335         * Return a string containing the next alpha-numeric word on the stream.
1336         * 
1337         * @return the next word as a string
1338         * @throws ParsingException if the number could not be read
1339         */
1340        public String nextWord() throws ParsingException {
1341            char c = peek();
1342            StringBuilder sb = stringBuilder();
1343            while (Character.isLetterOrDigit(c)) {
1344                sb.append(next());
1345                c = peek();
1346            }
1347            return complete(sb);
1348        }
1349
1350        public ParsingException error( String message ) {
1351            return new ParsingException(message, lineNumber, columnNumber);
1352        }
1353
1354        public ParsingException error( String message,
1355                                       Throwable t ) {
1356            return new ParsingException(message, t, lineNumber, columnNumber);
1357        }
1358
1359        public int lineNumber() {
1360            return lineNumber;
1361        }
1362
1363        public int columnNumber() {
1364            return columnNumber;
1365        }
1366    }
1367}