Home | History | Annotate | Download | only in json
      1 package com.google.polo.json;
      2 
      3 import java.io.BufferedReader;
      4 import java.io.IOException;
      5 import java.io.Reader;
      6 import java.io.StringReader;
      7 
      8 /*
      9 Copyright (c) 2002 JSON.org
     10 
     11 Permission is hereby granted, free of charge, to any person obtaining a copy
     12 of this software and associated documentation files (the "Software"), to deal
     13 in the Software without restriction, including without limitation the rights
     14 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     15 copies of the Software, and to permit persons to whom the Software is
     16 furnished to do so, subject to the following conditions:
     17 
     18 The above copyright notice and this permission notice shall be included in all
     19 copies or substantial portions of the Software.
     20 
     21 The Software shall be used for Good, not Evil.
     22 
     23 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
     24 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
     25 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
     26 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
     27 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
     28 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
     29 SOFTWARE.
     30 */
     31 
     32 /**
     33  * A JSONTokener takes a source string and extracts characters and tokens from
     34  * it. It is used by the JSONObject and JSONArray constructors to parse
     35  * JSON source strings.
     36  * @author JSON.org
     37  * @version 2008-09-18
     38  */
     39 public class JSONTokener {
     40 
     41     private int index;
     42     private Reader reader;
     43     private char lastChar;
     44     private boolean useLastChar;
     45 
     46 
     47     /**
     48      * Construct a JSONTokener from a string.
     49      *
     50      * @param reader     A reader.
     51      */
     52     public JSONTokener(Reader reader) {
     53         this.reader = reader.markSupported() ?
     54         		reader : new BufferedReader(reader);
     55         this.useLastChar = false;
     56         this.index = 0;
     57     }
     58 
     59 
     60     /**
     61      * Construct a JSONTokener from a string.
     62      *
     63      * @param s     A source string.
     64      */
     65     public JSONTokener(String s) {
     66         this(new StringReader(s));
     67     }
     68 
     69 
     70     /**
     71      * Back up one character. This provides a sort of lookahead capability,
     72      * so that you can test for a digit or letter before attempting to parse
     73      * the next number or identifier.
     74      */
     75     public void back() throws JSONException {
     76         if (useLastChar || index <= 0) {
     77             throw new JSONException("Stepping back two steps is not supported");
     78         }
     79         index -= 1;
     80         useLastChar = true;
     81     }
     82 
     83 
     84 
     85     /**
     86      * Get the hex value of a character (base16).
     87      * @param c A character between '0' and '9' or between 'A' and 'F' or
     88      * between 'a' and 'f'.
     89      * @return  An int between 0 and 15, or -1 if c was not a hex digit.
     90      */
     91     public static int dehexchar(char c) {
     92         if (c >= '0' && c <= '9') {
     93             return c - '0';
     94         }
     95         if (c >= 'A' && c <= 'F') {
     96             return c - ('A' - 10);
     97         }
     98         if (c >= 'a' && c <= 'f') {
     99             return c - ('a' - 10);
    100         }
    101         return -1;
    102     }
    103 
    104 
    105     /**
    106      * Determine if the source string still contains characters that next()
    107      * can consume.
    108      * @return true if not yet at the end of the source.
    109      */
    110     public boolean more() throws JSONException {
    111         char nextChar = next();
    112         if (nextChar == 0) {
    113             return false;
    114         }
    115         back();
    116         return true;
    117     }
    118 
    119 
    120     /**
    121      * Get the next character in the source string.
    122      *
    123      * @return The next character, or 0 if past the end of the source string.
    124      */
    125     public char next() throws JSONException {
    126         if (this.useLastChar) {
    127         	this.useLastChar = false;
    128             if (this.lastChar != 0) {
    129             	this.index += 1;
    130             }
    131             return this.lastChar;
    132         }
    133         int c;
    134         try {
    135             c = this.reader.read();
    136         } catch (IOException exc) {
    137             throw new JSONException(exc);
    138         }
    139 
    140         if (c <= 0) { // End of stream
    141         	this.lastChar = 0;
    142             return 0;
    143         }
    144     	this.index += 1;
    145     	this.lastChar = (char) c;
    146         return this.lastChar;
    147     }
    148 
    149 
    150     /**
    151      * Consume the next character, and check that it matches a specified
    152      * character.
    153      * @param c The character to match.
    154      * @return The character.
    155      * @throws JSONException if the character does not match.
    156      */
    157     public char next(char c) throws JSONException {
    158         char n = next();
    159         if (n != c) {
    160             throw syntaxError("Expected '" + c + "' and instead saw '" +
    161                     n + "'");
    162         }
    163         return n;
    164     }
    165 
    166 
    167     /**
    168      * Get the next n characters.
    169      *
    170      * @param n     The number of characters to take.
    171      * @return      A string of n characters.
    172      * @throws JSONException
    173      *   Substring bounds error if there are not
    174      *   n characters remaining in the source string.
    175      */
    176      public String next(int n) throws JSONException {
    177          if (n == 0) {
    178              return "";
    179          }
    180 
    181          char[] buffer = new char[n];
    182          int pos = 0;
    183 
    184          if (this.useLastChar) {
    185         	 this.useLastChar = false;
    186              buffer[0] = this.lastChar;
    187              pos = 1;
    188          }
    189 
    190          try {
    191              int len;
    192              while ((pos < n) && ((len = reader.read(buffer, pos, n - pos)) != -1)) {
    193                  pos += len;
    194              }
    195          } catch (IOException exc) {
    196              throw new JSONException(exc);
    197          }
    198          this.index += pos;
    199 
    200          if (pos < n) {
    201              throw syntaxError("Substring bounds error");
    202          }
    203 
    204          this.lastChar = buffer[n - 1];
    205          return new String(buffer);
    206      }
    207 
    208 
    209     /**
    210      * Get the next char in the string, skipping whitespace.
    211      * @throws JSONException
    212      * @return  A character, or 0 if there are no more characters.
    213      */
    214     public char nextClean() throws JSONException {
    215         for (;;) {
    216             char c = next();
    217             if (c == 0 || c > ' ') {
    218                 return c;
    219             }
    220         }
    221     }
    222 
    223 
    224     /**
    225      * Return the characters up to the next close quote character.
    226      * Backslash processing is done. The formal JSON format does not
    227      * allow strings in single quotes, but an implementation is allowed to
    228      * accept them.
    229      * @param quote The quoting character, either
    230      *      <code>"</code>&nbsp;<small>(double quote)</small> or
    231      *      <code>'</code>&nbsp;<small>(single quote)</small>.
    232      * @return      A String.
    233      * @throws JSONException Unterminated string.
    234      */
    235     public String nextString(char quote) throws JSONException {
    236         char c;
    237         StringBuffer sb = new StringBuffer();
    238         for (;;) {
    239             c = next();
    240             switch (c) {
    241             case 0:
    242             case '\n':
    243             case '\r':
    244                 throw syntaxError("Unterminated string");
    245             case '\\':
    246                 c = next();
    247                 switch (c) {
    248                 case 'b':
    249                     sb.append('\b');
    250                     break;
    251                 case 't':
    252                     sb.append('\t');
    253                     break;
    254                 case 'n':
    255                     sb.append('\n');
    256                     break;
    257                 case 'f':
    258                     sb.append('\f');
    259                     break;
    260                 case 'r':
    261                     sb.append('\r');
    262                     break;
    263                 case 'u':
    264                     sb.append((char)Integer.parseInt(next(4), 16));
    265                     break;
    266                 case 'x' :
    267                     sb.append((char) Integer.parseInt(next(2), 16));
    268                     break;
    269                 default:
    270                     sb.append(c);
    271                 }
    272                 break;
    273             default:
    274                 if (c == quote) {
    275                     return sb.toString();
    276                 }
    277                 sb.append(c);
    278             }
    279         }
    280     }
    281 
    282 
    283     /**
    284      * Get the text up but not including the specified character or the
    285      * end of line, whichever comes first.
    286      * @param  d A delimiter character.
    287      * @return   A string.
    288      */
    289     public String nextTo(char d) throws JSONException {
    290         StringBuffer sb = new StringBuffer();
    291         for (;;) {
    292             char c = next();
    293             if (c == d || c == 0 || c == '\n' || c == '\r') {
    294                 if (c != 0) {
    295                     back();
    296                 }
    297                 return sb.toString().trim();
    298             }
    299             sb.append(c);
    300         }
    301     }
    302 
    303 
    304     /**
    305      * Get the text up but not including one of the specified delimiter
    306      * characters or the end of line, whichever comes first.
    307      * @param delimiters A set of delimiter characters.
    308      * @return A string, trimmed.
    309      */
    310     public String nextTo(String delimiters) throws JSONException {
    311         char c;
    312         StringBuffer sb = new StringBuffer();
    313         for (;;) {
    314             c = next();
    315             if (delimiters.indexOf(c) >= 0 || c == 0 ||
    316                     c == '\n' || c == '\r') {
    317                 if (c != 0) {
    318                     back();
    319                 }
    320                 return sb.toString().trim();
    321             }
    322             sb.append(c);
    323         }
    324     }
    325 
    326 
    327     /**
    328      * Get the next value. The value can be a Boolean, Double, Integer,
    329      * JSONArray, JSONObject, Long, or String, or the JSONObject.NULL object.
    330      * @throws JSONException If syntax error.
    331      *
    332      * @return An object.
    333      */
    334     public Object nextValue() throws JSONException {
    335         char c = nextClean();
    336         String s;
    337 
    338         switch (c) {
    339             case '"':
    340             case '\'':
    341                 return nextString(c);
    342             case '{':
    343                 back();
    344                 return new JSONObject(this);
    345             case '[':
    346             case '(':
    347                 back();
    348                 return new JSONArray(this);
    349         }
    350 
    351         /*
    352          * Handle unquoted text. This could be the values true, false, or
    353          * null, or it can be a number. An implementation (such as this one)
    354          * is allowed to also accept non-standard forms.
    355          *
    356          * Accumulate characters until we reach the end of the text or a
    357          * formatting character.
    358          */
    359 
    360         StringBuffer sb = new StringBuffer();
    361         while (c >= ' ' && ",:]}/\\\"[{;=#".indexOf(c) < 0) {
    362             sb.append(c);
    363             c = next();
    364         }
    365         back();
    366 
    367         s = sb.toString().trim();
    368         if (s.equals("")) {
    369             throw syntaxError("Missing value");
    370         }
    371         return JSONObject.stringToValue(s);
    372     }
    373 
    374 
    375     /**
    376      * Skip characters until the next character is the requested character.
    377      * If the requested character is not found, no characters are skipped.
    378      * @param to A character to skip to.
    379      * @return The requested character, or zero if the requested character
    380      * is not found.
    381      */
    382     public char skipTo(char to) throws JSONException {
    383         char c;
    384         try {
    385             int startIndex = this.index;
    386             reader.mark(Integer.MAX_VALUE);
    387             do {
    388                 c = next();
    389                 if (c == 0) {
    390                     reader.reset();
    391                     this.index = startIndex;
    392                     return c;
    393                 }
    394             } while (c != to);
    395         } catch (IOException exc) {
    396             throw new JSONException(exc);
    397         }
    398 
    399         back();
    400         return c;
    401     }
    402 
    403     /**
    404      * Make a JSONException to signal a syntax error.
    405      *
    406      * @param message The error message.
    407      * @return  A JSONException object, suitable for throwing
    408      */
    409     public JSONException syntaxError(String message) {
    410         return new JSONException(message + toString());
    411     }
    412 
    413 
    414     /**
    415      * Make a printable string of this JSONTokener.
    416      *
    417      * @return " at character [this.index]"
    418      */
    419     public String toString() {
    420         return " at character " + index;
    421     }
    422 }