content: /*
   * Copyright 1999-2017 Alibaba Group.
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  package com.alibaba.fastjson.parser;
  
  import java.io.CharArrayReader;
  import java.io.IOException;
  import java.io.Reader;
  import java.io.StringReader;
  import java.math.BigDecimal;
  import java.math.MathContext;
  
  import com.alibaba.fastjson.JSON;
  import com.alibaba.fastjson.JSONException;
  import com.alibaba.fastjson.util.IOUtils;
  
  //这个类，为了性能优化做了很多特别处理，一切都是为了性能！！！
  
  /**
   * @author wenshao[szujobs@hotmail.com]
   */
  public final class JSONReaderScanner extends JSONLexerBase {
  
      private final static ThreadLocal<char[]> BUF_LOCAL = new ThreadLocal<char[]>();
  
      private Reader                           reader;
      private char[]                           buf;
      private int                              bufLength;
  
      public JSONReaderScanner(String input){
          this(input, JSON.DEFAULT_PARSER_FEATURE);
      }
  
      public JSONReaderScanner(String input, int features){
          this(new StringReader(input), features);
      }
  
      public JSONReaderScanner(char[] input, int inputLength){
          this(input, inputLength, JSON.DEFAULT_PARSER_FEATURE);
      }
  
      public JSONReaderScanner(Reader reader){
          this(reader, JSON.DEFAULT_PARSER_FEATURE);
      }
  
      public JSONReaderScanner(Reader reader, int features){
          super(features);
          this.reader = reader;
  
          buf = BUF_LOCAL.get();
          if (buf != null) {
              BUF_LOCAL.set(null);
          }
  
          if (buf == null) {
              buf = new char[1024 * 16];
          }
  
          try {
              bufLength = reader.read(buf);
          } catch (IOException e) {
              throw new JSONException(e.getMessage(), e);
          }
  
          bp = -1;
  
          next();
          if (ch == 65279) { // utf8 bom
              next();
          }
      }
  
      public JSONReaderScanner(char[] input, int inputLength, int features){
          this(new CharArrayReader(input, 0, inputLength), features);
      }
  
      public final char charAt(int index) {
          if (index >= bufLength) {
              if (bufLength == -1) {
                  if (index < sp) {
                      return buf[index];
                  }
                  return EOI;
              }
  
              if (bp == 0) {
                  char[] buf = new char[(this.buf.length * 3) / 2];
                  System.arraycopy(this.buf, bp, buf, 0, bufLength);
  
                  int rest = buf.length - bufLength;
                  try {
                      int len = reader.read(buf, bufLength, rest);
                      bufLength += len;
                      this.buf = buf;
                  } catch (IOException e) {
                      throw new JSONException(e.getMessage(), e);
                  }
              } else {
                  int rest = bufLength - bp;
                  if (rest > 0) {
                      System.arraycopy(buf, bp, buf, 0, rest);
                  }
  
                  try {
                      bufLength = reader.read(buf, rest, buf.length - rest);
                  } catch (IOException e) {
                      throw new JSONException(e.getMessage(), e);
                  }
  
                  if (bufLength == 0) {
                      throw new JSONException("illegal state, textLength is zero");
                  }
  
                  if (bufLength == -1) {
                      return EOI;
                  }
  
                  bufLength += rest;
                  index -= bp;
                  np -= bp;
                  bp = 0;
              }
          }
  
          return buf[index];
      }
  
      public final int indexOf(char ch, int startIndex) {
          int offset = startIndex - bp;
          for (;; ++offset) {
              final int index = bp + offset;
              char chLoal = charAt(index);
              if (ch == chLoal) {
                  return offset + bp;
              }
              if (chLoal == EOI) {
                  return -1;
              }
          }
      }
  
      public final String addSymbol(int offset, int len, int hash, final SymbolTable symbolTable) {
          return symbolTable.addSymbol(buf, offset, len, hash);
      }
  
      public final char next() {
          int index = ++bp;
  
          if (index >= bufLength) {
              if (bufLength == -1) {
                  return EOI;
              }
  
              if (sp > 0) {
                  int offset;
                  offset = bufLength - sp;
                  if (ch == '"' && offset > 0) {
                      offset--;
                  }
                  System.arraycopy(buf, offset, buf, 0, sp);
              }
              np = -1;
  
              index = bp = sp;
  
              try {
                  int startPos = bp;
                  int readLength = buf.length - startPos;
                  if (readLength == 0) {
                      char[] newBuf = new char[buf.length * 2];
                      System.arraycopy(buf, 0, newBuf, 0, buf.length);
                      buf = newBuf;
                      readLength = buf.length - startPos;
                  }
                  bufLength = reader.read(buf, bp, readLength);
              } catch (IOException e) {
                  throw new JSONException(e.getMessage(), e);
              }
  
              if (bufLength == 0) {
                  throw new JSONException("illegal stat, textLength is zero");
              }
  
              if (bufLength == -1) {
                  return ch = EOI;
              }
  
              bufLength += bp;
          }
  
          return ch = buf[index];
      }
  
      protected final void copyTo(int offset, int count, char[] dest) {
          System.arraycopy(buf, offset, dest, 0, count);
      }
  
      public final boolean charArrayCompare(char[] chars) {
          for (int i = 0; i < chars.length; ++i) {
              if (charAt(bp + i) != chars[i]) {
                  return false;
              }
          }
  
          return true;
      }
  
      public byte[] bytesValue() {
          if (token == JSONToken.HEX) {
              throw new JSONException("TODO");
          }
  
          return IOUtils.decodeBase64(buf, np + 1, sp);
      }
  
      protected final void arrayCopy(int srcPos, char[] dest, int destPos, int length) {
          System.arraycopy(buf, srcPos, dest, destPos, length);
      }
  
      /**
       * The value of a literal token, recorded as a string. For integers, leading 0x and 'l' suffixes are suppressed.
       */
      public final String stringVal() {
          if (!hasSpecial) {
              int offset = np + 1;
              if (offset < 0) {
                  throw new IllegalStateException();
              }
              if (offset > buf.length - sp) {
                  throw new IllegalStateException();
              }
              return new String(buf, offset, sp);
              // return text.substring(np + 1, np + 1 + sp);
          } else {
              return new String(sbuf, 0, sp);
          }
      }
  
      public final String subString(int offset, int count) {
          if (count < 0) {
              throw new StringIndexOutOfBoundsException(count);
          }
          return new String(buf, offset, count);
          // return text.substring(offset, offset + count);
      }
  
      public final char[] sub_chars(int offset, int count) {
          if (count < 0) {
              throw new StringIndexOutOfBoundsException(count);
          }
          
          if (offset == 0) {
              return buf;
          }
          char[] chars = new char[count];
          System.arraycopy(buf, offset, chars, 0, count);
          return chars;
      }
  
      public final String numberString() {
          int offset = np;
          if (offset == -1) {
              offset = 0;
          }
          char chLocal = charAt(offset + sp - 1);
  
          int sp = this.sp;
          if (chLocal == 'L' || chLocal == 'S' || chLocal == 'B' || chLocal == 'F' || chLocal == 'D') {
              sp--;
          }
  
          String value = new String(buf, offset, sp);
          return value;
      }
  
      public final BigDecimal decimalValue() {
          int offset = np;
          if (offset == -1) {
              offset = 0;
          }
          char chLocal = charAt(offset + sp - 1);
  
          int sp = this.sp;
          if (chLocal == 'L' || chLocal == 'S' || chLocal == 'B' || chLocal == 'F' || chLocal == 'D') {
              sp--;
          }
  
          if (sp > 65535) {
              throw new JSONException("decimal overflow");
          }
  
          return new BigDecimal(buf, offset, sp, MathContext.UNLIMITED);
      }
  
      public void close() {
          super.close();
  
          if (buf.length <= 1024 * 64) {
              BUF_LOCAL.set(buf);
          }
          this.buf = null;
  
          IOUtils.close(reader);
      }
  
      @Override
      public boolean isEOF() {
          return bufLength == -1 || bp == buf.length || ch == EOI && bp + 1 >= buf.length;
      }
  
      public final boolean isBlankInput() {
          for (int i = 0;; ++i) {
              char chLocal = buf[i];
              if (chLocal == EOI) {
                  token = JSONToken.EOF;
                  break;
              }
  
              if (!isWhitespace(chLocal)) {
                  return false;
              }
          }
  
          return true;
      }
  }
  