001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software GmbH & Co. KG, please see the
018 * company website: http://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: http://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 *
027 * This file is based on:
028 * org.json.JSONTokener
029 * from the JSON in Java implementation.
030 *
031 * Copyright (c) 2002 JSON.org
032 *
033 * Permission is hereby granted, free of charge, to any person obtaining a copy
034 * of this software and associated documentation files (the "Software"), to deal
035 * in the Software without restriction, including without limitation the rights
036 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
037 * copies of the Software, and to permit persons to whom the Software is
038 * furnished to do so, subject to the following conditions:
039 *
040 * The above copyright notice and this permission notice shall be included in all
041 * copies or substantial portions of the Software.
042 *
043 * The Software shall be used for Good, not Evil.
044 *
045 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
046 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
047 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
048 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
049 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
050 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
051 * SOFTWARE.
052 */
053
054package org.opencms.json;
055
056import java.io.BufferedReader;
057import java.io.IOException;
058import java.io.Reader;
059import java.io.StringReader;
060
061/**
062 * A JSONTokener takes a source string and extracts characters and tokens from
063 * it.<p>
064 *
065 * It is used by the JSONObject and JSONArray constructors to parse
066 * JSON source strings.<p>
067 *
068 */
069public class JSONTokener {
070
071    /** The index. */
072    private int m_index;
073
074    /** The last character. */
075    private char m_lastChar;
076
077    /** Flag which controls whether JSONObjects created by this tokener are ordered. */
078    private boolean m_ordered;
079
080    /** The reader. */
081    private Reader m_reader;
082
083    /** Flag indicating if the last character should be used. */
084    private boolean m_useLastChar;
085
086    /**
087     * Construct a JSONTokener from a string.<p>
088     *
089     * @param reader     a reader.
090     */
091    public JSONTokener(Reader reader) {
092
093        m_reader = reader.markSupported() ? reader : new BufferedReader(reader);
094        m_useLastChar = false;
095        m_index = 0;
096    }
097
098    /**
099     * Construct a JSONTokener from a string.<p>
100     *
101     * @param s a source string
102     */
103    public JSONTokener(String s) {
104
105        this(new StringReader(s));
106    }
107
108    /**
109     * Get the hex value of a character (base16).<p>
110     *
111     * @param c a character between '0' and '9' or between 'A' and 'F' or
112     * between 'a' and 'f'
113     * @return  an int between 0 and 15, or -1 if c was not a hex digit
114     */
115    public static int dehexchar(char c) {
116
117        if ((c >= '0') && (c <= '9')) {
118            return c - '0';
119        }
120        if ((c >= 'A') && (c <= 'F')) {
121            return c - ('A' - 10);
122        }
123        if ((c >= 'a') && (c <= 'f')) {
124            return c - ('a' - 10);
125        }
126        return -1;
127    }
128
129    /**
130     * Back up one character.<p>
131     *
132     * This provides a sort of lookahead capability,
133     * so that you can test for a digit or letter before attempting to parse
134     * the next number or identifier.<p>
135     *
136     * @throws JSONException if something goes wrong
137     */
138    public void back() throws JSONException {
139
140        if (m_useLastChar || (m_index <= 0)) {
141            throw new JSONException("Stepping back two steps is not supported");
142        }
143        m_index -= 1;
144        m_useLastChar = true;
145    }
146
147    /**
148     * Determine if the source string still contains characters that next()
149     * can consume.<p>
150     *
151     * @return true if not yet at the end of the source
152     * @throws JSONException if something goes wrong
153     */
154    public boolean more() throws JSONException {
155
156        char nextChar = next();
157        if (nextChar == 0) {
158            return false;
159        }
160        back();
161        return true;
162    }
163
164    /**
165     * Get the next character in the source string.<p>
166     *
167     * @return the next character, or 0 if past the end of the source string
168     * @throws JSONException if something goes wrong
169     */
170    public char next() throws JSONException {
171
172        if (m_useLastChar) {
173            m_useLastChar = false;
174            if (m_lastChar != 0) {
175                m_index += 1;
176            }
177            return m_lastChar;
178        }
179        int c;
180        try {
181            c = m_reader.read();
182        } catch (IOException exc) {
183            throw new JSONException(exc);
184        }
185
186        if (c <= 0) { // End of stream
187            m_lastChar = 0;
188            return 0;
189        }
190        m_index += 1;
191        m_lastChar = (char)c;
192        return m_lastChar;
193    }
194
195    /**
196     * Consume the next character, and check that it matches a specified
197     * character.<p>
198     *
199     * @param c the character to match
200     * @return the character
201     * @throws JSONException if the character does not match
202     */
203    public char next(char c) throws JSONException {
204
205        char n = next();
206        if (n != c) {
207            throw syntaxError("Expected '" + c + "' and instead saw '" + n + "'");
208        }
209        return n;
210    }
211
212    /**
213     * Get the next n characters.<p>
214     *
215     * @param n the number of characters to take
216     * @return a string of n characters
217     *
218     * @throws JSONException substring bounds error if there are not n characters remaining in the source string
219     */
220    public String next(int n) throws JSONException {
221
222        if (n == 0) {
223            return "";
224        }
225
226        char[] buffer = new char[n];
227        int pos = 0;
228
229        if (m_useLastChar) {
230            m_useLastChar = false;
231            buffer[0] = m_lastChar;
232            pos = 1;
233        }
234
235        try {
236            int len = m_reader.read(buffer, pos, n - pos);
237            while ((pos < n) && (len != -1)) {
238                pos += len;
239                len = m_reader.read(buffer, pos, n - pos);
240            }
241        } catch (IOException exc) {
242            throw new JSONException(exc);
243        }
244        m_index += pos;
245
246        if (pos < n) {
247            throw syntaxError("Substring bounds error");
248        }
249
250        m_lastChar = buffer[n - 1];
251        return new String(buffer);
252    }
253
254    /**
255     * Get the next char in the string, skipping whitespace
256     * and comments (slashslash, slashstar, and hash).<p>
257     *
258     * @return  a character, or 0 if there are no more characters
259     * @throws JSONException if something goes wrong
260     */
261    public char nextClean() throws JSONException {
262
263        for (;;) {
264            char c = next();
265            if (c == '/') {
266                switch (next()) {
267                    case '/':
268                        do {
269                            c = next();
270                        } while ((c != '\n') && (c != '\r') && (c != 0));
271                        break;
272                    case '*':
273                        for (;;) {
274                            c = next();
275                            if (c == 0) {
276                                throw syntaxError("Unclosed comment");
277                            }
278                            if (c == '*') {
279                                if (next() == '/') {
280                                    break;
281                                }
282                                back();
283                            }
284                        }
285                        break;
286                    default:
287                        back();
288                        return '/';
289                }
290            } else if (c == '#') {
291                do {
292                    c = next();
293                } while ((c != '\n') && (c != '\r') && (c != 0));
294            } else if ((c == 0) || (c > ' ')) {
295                return c;
296            }
297        }
298    }
299
300    /**
301     * Return the characters up to the next close quote character.<p>
302     *
303     * Backslash processing is done. The formal JSON format does not
304     * allow strings in single quotes, but an implementation is allowed to
305     * accept them.<p>
306     *
307     * @param quote The quoting character, either
308     *      <code>"</code>&nbsp;<small>(double quote)</small> or
309     *      <code>'</code>&nbsp;<small>(single quote)</small>
310     * @return      a String
311     * @throws JSONException in case of an unterminated string
312     */
313    public String nextString(char quote) throws JSONException {
314
315        char c;
316        StringBuffer sb = new StringBuffer();
317        for (;;) {
318            c = next();
319            switch (c) {
320                case 0:
321                case '\n':
322                case '\r':
323                    throw syntaxError("Unterminated string");
324                case '\\':
325                    c = next();
326                    switch (c) {
327                        case 'b':
328                            sb.append('\b');
329                            break;
330                        case 't':
331                            sb.append('\t');
332                            break;
333                        case 'n':
334                            sb.append('\n');
335                            break;
336                        case 'f':
337                            sb.append('\f');
338                            break;
339                        case 'r':
340                            sb.append('\r');
341                            break;
342                        case 'u':
343                            sb.append((char)Integer.parseInt(next(4), 16));
344                            break;
345                        case 'x':
346                            sb.append((char)Integer.parseInt(next(2), 16));
347                            break;
348                        default:
349                            sb.append(c);
350                    }
351                    break;
352                default:
353                    if (c == quote) {
354                        return sb.toString();
355                    }
356                    sb.append(c);
357            }
358        }
359    }
360
361    /**
362     * Get the text up but not including the specified character or the
363     * end of line, whichever comes first.<p>
364     *
365     * @param  d a delimiter character
366     * @return   a string
367     * @throws JSONException if something goes wrong
368     */
369    public String nextTo(char d) throws JSONException {
370
371        StringBuffer sb = new StringBuffer();
372        for (;;) {
373            char c = next();
374            if ((c == d) || (c == 0) || (c == '\n') || (c == '\r')) {
375                if (c != 0) {
376                    back();
377                }
378                return sb.toString().trim();
379            }
380            sb.append(c);
381        }
382    }
383
384    /**
385     * Get the text up but not including one of the specified delimiter
386     * characters or the end of line, whichever comes first.<p>
387     *
388     * @param delimiters a set of delimiter characters
389     * @return a string, trimmed
390     * @throws JSONException if something goes wrong
391     */
392    public String nextTo(String delimiters) throws JSONException {
393
394        char c;
395        StringBuffer sb = new StringBuffer();
396        for (;;) {
397            c = next();
398            if ((delimiters.indexOf(c) >= 0) || (c == 0) || (c == '\n') || (c == '\r')) {
399                if (c != 0) {
400                    back();
401                }
402                return sb.toString().trim();
403            }
404            sb.append(c);
405        }
406    }
407
408    /**
409     * Get the next value. The value can be a Boolean, Double, Integer,
410     * JSONArray, JSONObject, Long, or String, or the JSONObject.NULL object.<p>
411     *
412     * @return an object
413     * @throws JSONException if something goes wrong
414     */
415    public Object nextValue() throws JSONException {
416
417        char c = nextClean();
418        String s;
419
420        switch (c) {
421            case '"':
422            case '\'':
423                return nextString(c);
424            case '{':
425                back();
426                return new JSONObject(this, m_ordered);
427            case '[':
428            case '(':
429                back();
430                return new JSONArray(this);
431            default:
432        }
433
434        /*
435         * Handle unquoted text. This could be the values true, false, or
436         * null, or it can be a number. An implementation (such as this one)
437         * is allowed to also accept non-standard forms.
438         *
439         * Accumulate characters until we reach the end of the text or a
440         * formatting character.
441         */
442
443        StringBuffer sb = new StringBuffer();
444        char b = c;
445        while ((c >= ' ') && (",:]}/\\\"[{;=#".indexOf(c) < 0)) {
446            sb.append(c);
447            c = next();
448        }
449        back();
450
451        /*
452         * If it is true, false, or null, return the proper value.
453         */
454
455        s = sb.toString().trim();
456        if (s.equals("")) {
457            throw syntaxError("Missing value");
458        }
459        if (s.equalsIgnoreCase("true")) {
460            return Boolean.TRUE;
461        }
462        if (s.equalsIgnoreCase("false")) {
463            return Boolean.FALSE;
464        }
465        if (s.equalsIgnoreCase("null")) {
466            return JSONObject.NULL;
467        }
468
469        /*
470         * If it might be a number, try converting it. We support the 0- and 0x-
471         * conventions. If a number cannot be produced, then the value will just
472         * be a string. Note that the 0-, 0x-, plus, and implied string
473         * conventions are non-standard. A JSON parser is free to accept
474         * non-JSON forms as long as it accepts all correct JSON forms.
475         */
476
477        if (((b >= '0') && (b <= '9')) || (b == '.') || (b == '-') || (b == '+')) {
478            if (b == '0') {
479                if ((s.length() > 2) && ((s.charAt(1) == 'x') || (s.charAt(1) == 'X'))) {
480                    try {
481                        return new Integer(Integer.parseInt(s.substring(2), 16));
482                    } catch (Exception e) {
483                        /* Ignore the error */
484                    }
485                } else {
486                    try {
487                        return new Integer(Integer.parseInt(s, 8));
488                    } catch (Exception e) {
489                        /* Ignore the error */
490                    }
491                }
492            }
493            try {
494                return new Integer(s);
495            } catch (Exception e) {
496                try {
497                    return new Long(s);
498                } catch (Exception f) {
499                    try {
500                        return new Double(s);
501                    } catch (Exception g) {
502                        return s;
503                    }
504                }
505            }
506        }
507        return s;
508    }
509
510    /**
511     * Sets a flag which makes JSONObjects created by this tokener ordered.<p>
512     *
513     * @param ordered true if JSONObjects created by this should be ordered
514     */
515    public void setOrdered(boolean ordered) {
516
517        m_ordered = ordered;
518    }
519
520    /**
521     * Skip characters until the next character is the requested character.
522     * If the requested character is not found, no characters are skipped.<p>
523     *
524     * @param to a character to skip to
525     * @return the requested character, or zero if the requested character
526     * is not found
527     * @throws JSONException if something goes wrong
528     */
529    public char skipTo(char to) throws JSONException {
530
531        char c;
532        try {
533            int startIndex = m_index;
534            m_reader.mark(Integer.MAX_VALUE);
535            do {
536                c = next();
537                if (c == 0) {
538                    m_reader.reset();
539                    m_index = startIndex;
540                    return c;
541                }
542            } while (c != to);
543        } catch (IOException exc) {
544            throw new JSONException(exc);
545        }
546
547        back();
548        return c;
549    }
550
551    /**
552     * Make a JSONException to signal a syntax error.<p>
553     *
554     * @param message the error message
555     * @return  a JSONException object, suitable for throwing
556     */
557    public JSONException syntaxError(String message) {
558
559        return new JSONException(message + toString());
560    }
561
562    /**
563     * Make a printable string of this JSONTokener.<p>
564     *
565     * @return " at character [this.index]"
566     */
567    @Override
568    public String toString() {
569
570        return " at character " + m_index;
571    }
572}