JAL-3619 fix up error messages and try to bail quietly if the viewer doesn’t open
[jalview.git] / src / org / json / JSONTokener.java
1 package org.json;
2
3 import java.io.BufferedReader;
4 import java.io.IOException;
5 import java.io.InputStream;
6 import java.io.InputStreamReader;
7 import java.io.Reader;
8 import java.io.StringReader;
9
10 /*
11 Copyright (c) 2002 JSON.org
12
13 Permission is hereby granted, free of charge, to any person obtaining a copy
14 of this software and associated documentation files (the "Software"), to deal
15 in the Software without restriction, including without limitation the rights
16 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 copies of the Software, and to permit persons to whom the Software is
18 furnished to do so, subject to the following conditions:
19
20 The above copyright notice and this permission notice shall be included in all
21 copies or substantial portions of the Software.
22
23 The Software shall be used for Good, not Evil.
24
25 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
26 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
27 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
28 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
29 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
30 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
31 SOFTWARE.
32  */
33
34 /**
35  * A JSONTokener takes a source string and extracts characters and tokens from
36  * it. It is used by the JSONObject and JSONArray constructors to parse
37  * JSON source strings.
38  * @author JSON.org
39  * @version 2014-05-03
40  */
41 public class JSONTokener {
42     /** current read character position on the current line. */
43     private long character;
44     /** flag to indicate if the end of the input has been found. */
45     private boolean eof;
46     /** current read index of the input. */
47     private long index;
48     /** current line of the input. */
49     private long line;
50     /** previous character read from the input. */
51     private char previous;
52     /** Reader for the input. */
53     private final Reader reader;
54     /** flag to indicate that a previous character was requested. */
55     private boolean usePrevious;
56     /** the number of characters read in the previous line. */
57     private long characterPreviousLine;
58
59
60     /**
61      * Construct a JSONTokener from a Reader. The caller must close the Reader.
62      *
63      * @param reader     A reader.
64      */
65     public JSONTokener(Reader reader) {
66         this.reader = reader.markSupported()
67                 ? reader
68                         : new BufferedReader(reader);
69         this.eof = false;
70         this.usePrevious = false;
71         this.previous = 0;
72         this.index = 0;
73         this.character = 1;
74         this.characterPreviousLine = 0;
75         this.line = 1;
76     }
77
78
79     /**
80      * Construct a JSONTokener from an InputStream. The caller must close the input stream.
81      * @param inputStream The source.
82      */
83     public JSONTokener(InputStream inputStream) {
84         this(new InputStreamReader(inputStream));
85     }
86
87
88     /**
89      * Construct a JSONTokener from a string.
90      *
91      * @param s     A source string.
92      */
93     public JSONTokener(String s) {
94         this(new StringReader(s));
95     }
96
97
98     /**
99      * Back up one character. This provides a sort of lookahead capability,
100      * so that you can test for a digit or letter before attempting to parse
101      * the next number or identifier.
102      * @throws JSONException Thrown if trying to step back more than 1 step
103      *  or if already at the start of the string
104      */
105     public void back() throws JSONException {
106         if (this.usePrevious || this.index <= 0) {
107             throw new JSONException("Stepping back two steps is not supported");
108         }
109         this.decrementIndexes();
110         this.usePrevious = true;
111         this.eof = false;
112     }
113
114     /**
115      * Decrements the indexes for the {@link #back()} method based on the previous character read.
116      */
117     private void decrementIndexes() {
118         this.index--;
119         if(this.previous=='\r' || this.previous == '\n') {
120             this.line--;
121             this.character=this.characterPreviousLine ;
122         } else if(this.character > 0){
123             this.character--;
124         }
125     }
126
127     /**
128      * Get the hex value of a character (base16).
129      * @param c A character between '0' and '9' or between 'A' and 'F' or
130      * between 'a' and 'f'.
131      * @return  An int between 0 and 15, or -1 if c was not a hex digit.
132      */
133     public static int dehexchar(char c) {
134         if (c >= '0' && c <= '9') {
135             return c - '0';
136         }
137         if (c >= 'A' && c <= 'F') {
138             return c - ('A' - 10);
139         }
140         if (c >= 'a' && c <= 'f') {
141             return c - ('a' - 10);
142         }
143         return -1;
144     }
145
146     /**
147      * Checks if the end of the input has been reached.
148      *  
149      * @return true if at the end of the file and we didn't step back
150      */
151     public boolean end() {
152         return this.eof && !this.usePrevious;
153     }
154
155
156     /**
157      * Determine if the source string still contains characters that next()
158      * can consume.
159      * @return true if not yet at the end of the source.
160      * @throws JSONException thrown if there is an error stepping forward
161      *  or backward while checking for more data.
162      */
163     public boolean more() throws JSONException {
164         if(this.usePrevious) {
165             return true;
166         }
167         try {
168             this.reader.mark(1);
169         } catch (IOException e) {
170             throw new JSONException("Unable to preserve stream position", e);
171         }
172         try {
173             // -1 is EOF, but next() can not consume the null character '\0'
174             if(this.reader.read() <= 0) {
175                 this.eof = true;
176                 return false;
177             }
178             this.reader.reset();
179         } catch (IOException e) {
180             throw new JSONException("Unable to read the next character from the stream", e);
181         }
182         return true;
183     }
184
185
186     /**
187      * Get the next character in the source string.
188      *
189      * @return The next character, or 0 if past the end of the source string.
190      * @throws JSONException Thrown if there is an error reading the source string.
191      */
192     public char next() throws JSONException {
193         int c;
194         if (this.usePrevious) {
195             this.usePrevious = false;
196             c = this.previous;
197         } else {
198             try {
199                 c = this.reader.read();
200             } catch (IOException exception) {
201                 throw new JSONException(exception);
202             }
203         }
204         if (c <= 0) { // End of stream
205             this.eof = true;
206             return 0;
207         }
208         this.incrementIndexes(c);
209         this.previous = (char) c;
210         return this.previous;
211     }
212
213     /**
214      * Increments the internal indexes according to the previous character
215      * read and the character passed as the current character.
216      * @param c the current character read.
217      */
218     private void incrementIndexes(int c) {
219         if(c > 0) {
220             this.index++;
221             if(c=='\r') {
222                 this.line++;
223                 this.characterPreviousLine = this.character;
224                 this.character=0;
225             }else if (c=='\n') {
226                 if(this.previous != '\r') {
227                     this.line++;
228                     this.characterPreviousLine = this.character;
229                 }
230                 this.character=0;
231             } else {
232                 this.character++;
233             }
234         }
235     }
236
237     /**
238      * Consume the next character, and check that it matches a specified
239      * character.
240      * @param c The character to match.
241      * @return The character.
242      * @throws JSONException if the character does not match.
243      */
244     public char next(char c) throws JSONException {
245         char n = this.next();
246         if (n != c) {
247             if(n > 0) {
248                 throw this.syntaxError("Expected '" + c + "' and instead saw '" +
249                         n + "'");
250             }
251             throw this.syntaxError("Expected '" + c + "' and instead saw ''");
252         }
253         return n;
254     }
255
256
257     /**
258      * Get the next n characters.
259      *
260      * @param n     The number of characters to take.
261      * @return      A string of n characters.
262      * @throws JSONException
263      *   Substring bounds error if there are not
264      *   n characters remaining in the source string.
265      */
266     public String next(int n) throws JSONException {
267         if (n == 0) {
268             return "";
269         }
270
271         char[] chars = new char[n];
272         int pos = 0;
273
274         while (pos < n) {
275             chars[pos] = this.next();
276             if (this.end()) {
277                 throw this.syntaxError("Substring bounds error");
278             }
279             pos += 1;
280         }
281         return new String(chars);
282     }
283
284
285     /**
286      * Get the next char in the string, skipping whitespace.
287      * @throws JSONException Thrown if there is an error reading the source string.
288      * @return  A character, or 0 if there are no more characters.
289      */
290     public char nextClean() throws JSONException {
291         for (;;) {
292             char c = this.next();
293             if (c == 0 || c > ' ') {
294                 return c;
295             }
296         }
297     }
298
299
300     /**
301      * Return the characters up to the next close quote character.
302      * Backslash processing is done. The formal JSON format does not
303      * allow strings in single quotes, but an implementation is allowed to
304      * accept them.
305      * @param quote The quoting character, either
306      *      <code>"</code>&nbsp;<small>(double quote)</small> or
307      *      <code>'</code>&nbsp;<small>(single quote)</small>.
308      * @return      A String.
309      * @throws JSONException Unterminated string.
310      */
311     public String nextString(char quote) throws JSONException {
312         char c;
313         StringBuilder sb = new StringBuilder();
314         for (;;) {
315             c = this.next();
316             switch (c) {
317             case 0:
318             case '\n':
319             case '\r':
320                 throw this.syntaxError("Unterminated string");
321             case '\\':
322                 c = this.next();
323                 switch (c) {
324                 case 'b':
325                     sb.append('\b');
326                     break;
327                 case 't':
328                     sb.append('\t');
329                     break;
330                 case 'n':
331                     sb.append('\n');
332                     break;
333                 case 'f':
334                     sb.append('\f');
335                     break;
336                 case 'r':
337                     sb.append('\r');
338                     break;
339                 case 'u':
340                     try {
341                         sb.append((char)Integer.parseInt(this.next(4), 16));
342                     } catch (NumberFormatException e) {
343                         throw this.syntaxError("Illegal escape.", e);
344                     }
345                     break;
346                 case '"':
347                 case '\'':
348                 case '\\':
349                 case '/':
350                     sb.append(c);
351                     break;
352                 default:
353                     throw this.syntaxError("Illegal escape.");
354                 }
355                 break;
356             default:
357                 if (c == quote) {
358                     return sb.toString();
359                 }
360                 sb.append(c);
361             }
362         }
363     }
364
365
366     /**
367      * Get the text up but not including the specified character or the
368      * end of line, whichever comes first.
369      * @param  delimiter A delimiter character.
370      * @return   A string.
371      * @throws JSONException Thrown if there is an error while searching
372      *  for the delimiter
373      */
374     public String nextTo(char delimiter) throws JSONException {
375         StringBuilder sb = new StringBuilder();
376         for (;;) {
377             char c = this.next();
378             if (c == delimiter || c == 0 || c == '\n' || c == '\r') {
379                 if (c != 0) {
380                     this.back();
381                 }
382                 return sb.toString().trim();
383             }
384             sb.append(c);
385         }
386     }
387
388
389     /**
390      * Get the text up but not including one of the specified delimiter
391      * characters or the end of line, whichever comes first.
392      * @param delimiters A set of delimiter characters.
393      * @return A string, trimmed.
394      * @throws JSONException Thrown if there is an error while searching
395      *  for the delimiter
396      */
397     public String nextTo(String delimiters) throws JSONException {
398         char c;
399         StringBuilder sb = new StringBuilder();
400         for (;;) {
401             c = this.next();
402             if (delimiters.indexOf(c) >= 0 || c == 0 ||
403                     c == '\n' || c == '\r') {
404                 if (c != 0) {
405                     this.back();
406                 }
407                 return sb.toString().trim();
408             }
409             sb.append(c);
410         }
411     }
412
413
414     /**
415      * Get the next value. The value can be a Boolean, Double, Integer,
416      * JSONArray, JSONObject, Long, or String, or the JSONObject.NULL object.
417      * @throws JSONException If syntax error.
418      *
419      * @return An object.
420      */
421     public Object nextValue() throws JSONException {
422         char c = this.nextClean();
423         String string;
424
425         switch (c) {
426         case '"':
427         case '\'':
428             return this.nextString(c);
429         case '{':
430             this.back();
431             return new JSONObject(this);
432         case '[':
433             this.back();
434             return new JSONArray(this);
435         }
436
437         /*
438          * Handle unquoted text. This could be the values true, false, or
439          * null, or it can be a number. An implementation (such as this one)
440          * is allowed to also accept non-standard forms.
441          *
442          * Accumulate characters until we reach the end of the text or a
443          * formatting character.
444          */
445
446         StringBuilder sb = new StringBuilder();
447         while (c >= ' ' && ",:]}/\\\"[{;=#".indexOf(c) < 0) {
448             sb.append(c);
449             c = this.next();
450         }
451         this.back();
452
453         string = sb.toString().trim();
454         if ("".equals(string)) {
455             throw this.syntaxError("Missing value");
456         }
457         return JSONObject.stringToValue(string);
458     }
459
460
461     /**
462      * Skip characters until the next character is the requested character.
463      * If the requested character is not found, no characters are skipped.
464      * @param to A character to skip to.
465      * @return The requested character, or zero if the requested character
466      * is not found.
467      * @throws JSONException Thrown if there is an error while searching
468      *  for the to character
469      */
470     public char skipTo(char to) throws JSONException {
471         char c;
472         try {
473             long startIndex = this.index;
474             long startCharacter = this.character;
475             long startLine = this.line;
476             this.reader.mark(1000000);
477             do {
478                 c = this.next();
479                 if (c == 0) {
480                     // in some readers, reset() may throw an exception if
481                     // the remaining portion of the input is greater than
482                     // the mark size (1,000,000 above).
483                     this.reader.reset();
484                     this.index = startIndex;
485                     this.character = startCharacter;
486                     this.line = startLine;
487                     return 0;
488                 }
489             } while (c != to);
490             this.reader.mark(1);
491         } catch (IOException exception) {
492             throw new JSONException(exception);
493         }
494         this.back();
495         return c;
496     }
497
498     /**
499      * Make a JSONException to signal a syntax error.
500      *
501      * @param message The error message.
502      * @return  A JSONException object, suitable for throwing
503      */
504     public JSONException syntaxError(String message) {
505         return new JSONException(message + this.toString());
506     }
507
508     /**
509      * Make a JSONException to signal a syntax error.
510      *
511      * @param message The error message.
512      * @param causedBy The throwable that caused the error.
513      * @return  A JSONException object, suitable for throwing
514      */
515     public JSONException syntaxError(String message, Throwable causedBy) {
516         return new JSONException(message + this.toString(), causedBy);
517     }
518
519     /**
520      * Make a printable string of this JSONTokener.
521      *
522      * @return " at {index} [character {character} line {line}]"
523      */
524     @Override
525     public String toString() {
526         return " at " + this.index + " [character " + this.character + " line " +
527                 this.line + "]";
528     }
529 }