JAL-3438 spotless for 2.11.2.0
[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 JSON
37  * source strings.
38  * 
39  * @author JSON.org
40  * @version 2014-05-03
41  */
42 public class JSONTokener
43 {
44   /** current read character position on the current line. */
45   private long character;
46
47   /** flag to indicate if the end of the input has been found. */
48   private boolean eof;
49
50   /** current read index of the input. */
51   private long index;
52
53   /** current line of the input. */
54   private long line;
55
56   /** previous character read from the input. */
57   private char previous;
58
59   /** Reader for the input. */
60   private final Reader reader;
61
62   /** flag to indicate that a previous character was requested. */
63   private boolean usePrevious;
64
65   /** the number of characters read in the previous line. */
66   private long characterPreviousLine;
67
68   /**
69    * Construct a JSONTokener from a Reader. The caller must close the Reader.
70    *
71    * @param reader
72    *          A reader.
73    */
74   public JSONTokener(Reader reader)
75   {
76     this.reader = reader.markSupported() ? reader
77             : new BufferedReader(reader);
78     this.eof = false;
79     this.usePrevious = false;
80     this.previous = 0;
81     this.index = 0;
82     this.character = 1;
83     this.characterPreviousLine = 0;
84     this.line = 1;
85   }
86
87   /**
88    * Construct a JSONTokener from an InputStream. The caller must close the
89    * input stream.
90    * 
91    * @param inputStream
92    *          The source.
93    */
94   public JSONTokener(InputStream inputStream)
95   {
96     this(new InputStreamReader(inputStream));
97   }
98
99   /**
100    * Construct a JSONTokener from a string.
101    *
102    * @param s
103    *          A source string.
104    */
105   public JSONTokener(String s)
106   {
107     this(new StringReader(s));
108   }
109
110   /**
111    * Back up one character. This provides a sort of lookahead capability, so
112    * that you can test for a digit or letter before attempting to parse the next
113    * number or identifier.
114    * 
115    * @throws JSONException
116    *           Thrown if trying to step back more than 1 step or if already at
117    *           the start of the string
118    */
119   public void back() throws JSONException
120   {
121     if (this.usePrevious || this.index <= 0)
122     {
123       throw new JSONException("Stepping back two steps is not supported");
124     }
125     this.decrementIndexes();
126     this.usePrevious = true;
127     this.eof = false;
128   }
129
130   /**
131    * Decrements the indexes for the {@link #back()} method based on the previous
132    * character read.
133    */
134   private void decrementIndexes()
135   {
136     this.index--;
137     if (this.previous == '\r' || this.previous == '\n')
138     {
139       this.line--;
140       this.character = this.characterPreviousLine;
141     }
142     else if (this.character > 0)
143     {
144       this.character--;
145     }
146   }
147
148   /**
149    * Get the hex value of a character (base16).
150    * 
151    * @param c
152    *          A character between '0' and '9' or between 'A' and 'F' or between
153    *          'a' and 'f'.
154    * @return An int between 0 and 15, or -1 if c was not a hex digit.
155    */
156   public static int dehexchar(char c)
157   {
158     if (c >= '0' && c <= '9')
159     {
160       return c - '0';
161     }
162     if (c >= 'A' && c <= 'F')
163     {
164       return c - ('A' - 10);
165     }
166     if (c >= 'a' && c <= 'f')
167     {
168       return c - ('a' - 10);
169     }
170     return -1;
171   }
172
173   /**
174    * Checks if the end of the input has been reached.
175    * 
176    * @return true if at the end of the file and we didn't step back
177    */
178   public boolean end()
179   {
180     return this.eof && !this.usePrevious;
181   }
182
183   /**
184    * Determine if the source string still contains characters that next() can
185    * consume.
186    * 
187    * @return true if not yet at the end of the source.
188    * @throws JSONException
189    *           thrown if there is an error stepping forward or backward while
190    *           checking for more data.
191    */
192   public boolean more() throws JSONException
193   {
194     if (this.usePrevious)
195     {
196       return true;
197     }
198     try
199     {
200       this.reader.mark(1);
201     } catch (IOException e)
202     {
203       throw new JSONException("Unable to preserve stream position", e);
204     }
205     try
206     {
207       // -1 is EOF, but next() can not consume the null character '\0'
208       if (this.reader.read() <= 0)
209       {
210         this.eof = true;
211         return false;
212       }
213       this.reader.reset();
214     } catch (IOException e)
215     {
216       throw new JSONException(
217               "Unable to read the next character from the stream", e);
218     }
219     return true;
220   }
221
222   /**
223    * Get the next character in the source string.
224    *
225    * @return The next character, or 0 if past the end of the source string.
226    * @throws JSONException
227    *           Thrown if there is an error reading the source string.
228    */
229   public char next() throws JSONException
230   {
231     int c;
232     if (this.usePrevious)
233     {
234       this.usePrevious = false;
235       c = this.previous;
236     }
237     else
238     {
239       try
240       {
241         c = this.reader.read();
242       } catch (IOException exception)
243       {
244         throw new JSONException(exception);
245       }
246     }
247     if (c <= 0)
248     { // End of stream
249       this.eof = true;
250       return 0;
251     }
252     this.incrementIndexes(c);
253     this.previous = (char) c;
254     return this.previous;
255   }
256
257   /**
258    * Increments the internal indexes according to the previous character read
259    * and the character passed as the current character.
260    * 
261    * @param c
262    *          the current character read.
263    */
264   private void incrementIndexes(int c)
265   {
266     if (c > 0)
267     {
268       this.index++;
269       if (c == '\r')
270       {
271         this.line++;
272         this.characterPreviousLine = this.character;
273         this.character = 0;
274       }
275       else if (c == '\n')
276       {
277         if (this.previous != '\r')
278         {
279           this.line++;
280           this.characterPreviousLine = this.character;
281         }
282         this.character = 0;
283       }
284       else
285       {
286         this.character++;
287       }
288     }
289   }
290
291   /**
292    * Consume the next character, and check that it matches a specified
293    * character.
294    * 
295    * @param c
296    *          The character to match.
297    * @return The character.
298    * @throws JSONException
299    *           if the character does not match.
300    */
301   public char next(char c) throws JSONException
302   {
303     char n = this.next();
304     if (n != c)
305     {
306       if (n > 0)
307       {
308         throw this.syntaxError(
309                 "Expected '" + c + "' and instead saw '" + n + "'");
310       }
311       throw this.syntaxError("Expected '" + c + "' and instead saw ''");
312     }
313     return n;
314   }
315
316   /**
317    * Get the next n characters.
318    *
319    * @param n
320    *          The number of characters to take.
321    * @return A string of n characters.
322    * @throws JSONException
323    *           Substring bounds error if there are not n characters remaining in
324    *           the source string.
325    */
326   public String next(int n) throws JSONException
327   {
328     if (n == 0)
329     {
330       return "";
331     }
332
333     char[] chars = new char[n];
334     int pos = 0;
335
336     while (pos < n)
337     {
338       chars[pos] = this.next();
339       if (this.end())
340       {
341         throw this.syntaxError("Substring bounds error");
342       }
343       pos += 1;
344     }
345     return new String(chars);
346   }
347
348   /**
349    * Get the next char in the string, skipping whitespace.
350    * 
351    * @throws JSONException
352    *           Thrown if there is an error reading the source string.
353    * @return A character, or 0 if there are no more characters.
354    */
355   public char nextClean() throws JSONException
356   {
357     for (;;)
358     {
359       char c = this.next();
360       if (c == 0 || c > ' ')
361       {
362         return c;
363       }
364     }
365   }
366
367   /**
368    * Return the characters up to the next close quote character. Backslash
369    * processing is done. The formal JSON format does not allow strings in single
370    * quotes, but an implementation is allowed to accept them.
371    * 
372    * @param quote
373    *          The quoting character, either <code>"</code>&nbsp;<small>(double
374    *          quote)</small> or <code>'</code>&nbsp;<small>(single
375    *          quote)</small>.
376    * @return A String.
377    * @throws JSONException
378    *           Unterminated string.
379    */
380   public String nextString(char quote) throws JSONException
381   {
382     char c;
383     StringBuilder sb = new StringBuilder();
384     for (;;)
385     {
386       c = this.next();
387       switch (c)
388       {
389       case 0:
390       case '\n':
391       case '\r':
392         throw this.syntaxError("Unterminated string");
393       case '\\':
394         c = this.next();
395         switch (c)
396         {
397         case 'b':
398           sb.append('\b');
399           break;
400         case 't':
401           sb.append('\t');
402           break;
403         case 'n':
404           sb.append('\n');
405           break;
406         case 'f':
407           sb.append('\f');
408           break;
409         case 'r':
410           sb.append('\r');
411           break;
412         case 'u':
413           try
414           {
415             sb.append((char) Integer.parseInt(this.next(4), 16));
416           } catch (NumberFormatException e)
417           {
418             throw this.syntaxError("Illegal escape.", e);
419           }
420           break;
421         case '"':
422         case '\'':
423         case '\\':
424         case '/':
425           sb.append(c);
426           break;
427         default:
428           throw this.syntaxError("Illegal escape.");
429         }
430         break;
431       default:
432         if (c == quote)
433         {
434           return sb.toString();
435         }
436         sb.append(c);
437       }
438     }
439   }
440
441   /**
442    * Get the text up but not including the specified character or the end of
443    * line, whichever comes first.
444    * 
445    * @param delimiter
446    *          A delimiter character.
447    * @return A string.
448    * @throws JSONException
449    *           Thrown if there is an error while searching for the delimiter
450    */
451   public String nextTo(char delimiter) throws JSONException
452   {
453     StringBuilder sb = new StringBuilder();
454     for (;;)
455     {
456       char c = this.next();
457       if (c == delimiter || c == 0 || c == '\n' || c == '\r')
458       {
459         if (c != 0)
460         {
461           this.back();
462         }
463         return sb.toString().trim();
464       }
465       sb.append(c);
466     }
467   }
468
469   /**
470    * Get the text up but not including one of the specified delimiter characters
471    * or the end of line, whichever comes first.
472    * 
473    * @param delimiters
474    *          A set of delimiter characters.
475    * @return A string, trimmed.
476    * @throws JSONException
477    *           Thrown if there is an error while searching for the delimiter
478    */
479   public String nextTo(String delimiters) throws JSONException
480   {
481     char c;
482     StringBuilder sb = new StringBuilder();
483     for (;;)
484     {
485       c = this.next();
486       if (delimiters.indexOf(c) >= 0 || c == 0 || c == '\n' || c == '\r')
487       {
488         if (c != 0)
489         {
490           this.back();
491         }
492         return sb.toString().trim();
493       }
494       sb.append(c);
495     }
496   }
497
498   /**
499    * Get the next value. The value can be a Boolean, Double, Integer, JSONArray,
500    * JSONObject, Long, or String, or the JSONObject.NULL object.
501    * 
502    * @throws JSONException
503    *           If syntax error.
504    *
505    * @return An object.
506    */
507   public Object nextValue() throws JSONException
508   {
509     char c = this.nextClean();
510     String string;
511
512     switch (c)
513     {
514     case '"':
515     case '\'':
516       return this.nextString(c);
517     case '{':
518       this.back();
519       return new JSONObject(this);
520     case '[':
521       this.back();
522       return new JSONArray(this);
523     }
524
525     /*
526      * Handle unquoted text. This could be the values true, false, or
527      * null, or it can be a number. An implementation (such as this one)
528      * is allowed to also accept non-standard forms.
529      *
530      * Accumulate characters until we reach the end of the text or a
531      * formatting character.
532      */
533
534     StringBuilder sb = new StringBuilder();
535     while (c >= ' ' && ",:]}/\\\"[{;=#".indexOf(c) < 0)
536     {
537       sb.append(c);
538       c = this.next();
539     }
540     this.back();
541
542     string = sb.toString().trim();
543     if ("".equals(string))
544     {
545       throw this.syntaxError("Missing value");
546     }
547     return JSONObject.stringToValue(string);
548   }
549
550   /**
551    * Skip characters until the next character is the requested character. If the
552    * requested character is not found, no characters are skipped.
553    * 
554    * @param to
555    *          A character to skip to.
556    * @return The requested character, or zero if the requested character is not
557    *         found.
558    * @throws JSONException
559    *           Thrown if there is an error while searching for the to character
560    */
561   public char skipTo(char to) throws JSONException
562   {
563     char c;
564     try
565     {
566       long startIndex = this.index;
567       long startCharacter = this.character;
568       long startLine = this.line;
569       this.reader.mark(1000000);
570       do
571       {
572         c = this.next();
573         if (c == 0)
574         {
575           // in some readers, reset() may throw an exception if
576           // the remaining portion of the input is greater than
577           // the mark size (1,000,000 above).
578           this.reader.reset();
579           this.index = startIndex;
580           this.character = startCharacter;
581           this.line = startLine;
582           return 0;
583         }
584       } while (c != to);
585       this.reader.mark(1);
586     } catch (IOException exception)
587     {
588       throw new JSONException(exception);
589     }
590     this.back();
591     return c;
592   }
593
594   /**
595    * Make a JSONException to signal a syntax error.
596    *
597    * @param message
598    *          The error message.
599    * @return A JSONException object, suitable for throwing
600    */
601   public JSONException syntaxError(String message)
602   {
603     return new JSONException(message + this.toString());
604   }
605
606   /**
607    * Make a JSONException to signal a syntax error.
608    *
609    * @param message
610    *          The error message.
611    * @param causedBy
612    *          The throwable that caused the error.
613    * @return A JSONException object, suitable for throwing
614    */
615   public JSONException syntaxError(String message, Throwable causedBy)
616   {
617     return new JSONException(message + this.toString(), causedBy);
618   }
619
620   /**
621    * Make a printable string of this JSONTokener.
622    *
623    * @return " at {index} [character {character} line {line}]"
624    */
625   @Override
626   public String toString()
627   {
628     return " at " + this.index + " [character " + this.character + " line "
629             + this.line + "]";
630   }
631 }