content: /**
   * Licensed to the Apache Software Foundation (ASF) under one
   * or more contributor license agreements.  See the NOTICE file
   * distributed with this work for additional information
   * regarding copyright ownership.  The ASF licenses this file
   * to you 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 org.apache.weex.wson;
  
  
  import android.support.v4.util.LruCache;
  
  import com.alibaba.fastjson.JSON;
  import com.alibaba.fastjson.JSONArray;
  import com.alibaba.fastjson.JSONObject;
  import com.alibaba.fastjson.annotation.JSONField;
  import org.apache.weex.utils.WXLogUtils;
  
  import java.lang.reflect.Array;
  import java.lang.reflect.Field;
  import java.lang.reflect.Method;
  import java.lang.reflect.Modifier;
  import java.math.BigDecimal;
  import java.math.BigInteger;
  import java.nio.ByteOrder;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Calendar;
  import java.util.Collection;
  import java.util.Date;
  import java.util.List;
  import java.util.Map;
  import java.util.Set;
  
  /**
   * fast binary json format for parse map and serialize map
   * Created by efurture on 2017/8/16.
   */
  public class Wson {
  
      /**
       * skip map null values
       * */
      public static final boolean WriteMapNullValue = false;
      /**
       * wson data type
       * */
      private static final byte NULL_TYPE = '0';
  
      private static final byte STRING_TYPE = 's';
  
      private static final byte BOOLEAN_TYPE_TRUE = 't';
  
      private static final byte BOOLEAN_TYPE_FALSE = 'f';
  
      private static final byte NUMBER_INT_TYPE = 'i';
  
      private static final byte NUMBER_LONG_TYPE = 'l';
  
      private static final byte NUMBER_BIG_INTEGER_TYPE = 'g';
  
      private static final byte NUMBER_BIG_DECIMAL_TYPE = 'e';
  
      private static final byte NUMBER_DOUBLE_TYPE = 'd';
  
      private static final byte NUMBER_FLOAT_TYPE = 'F';
  
      private static final byte ARRAY_TYPE = '[';
  
      private static final byte MAP_TYPE = '{';
  
      /**
       * StringUTF-16, byte order with native byte order
       * */
      private static final boolean IS_NATIVE_LITTLE_ENDIAN = (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN);
  
  
      /**
       * parse wson data  to object, please use WXJsonUtils.parseWson
       * @param  data  byte array
       * */
      public static final Object parse(byte[] data){
          if(data == null){
              return  null;
          }
          try{
              Parser parser =  new Parser(data);
              Object object = parser.parse();
              parser.close();
              return object;
          }catch (Exception e){
              WXLogUtils.e("parseWson", e);
              return  null;
          }
      }
  
  
      /**
       * serialize object to wson data, please use WXJsonUtils.toWsonOrJsonWXJSObject
       * */
      public static final byte[] toWson(Object object){
          if(object == null){
              return  null;
          }
          Builder builder = new Builder();
          byte[]  bts  = builder.toWson(object);
          builder.close();
          return bts;
      }
  
  
      /**
       * wson data parser
       * */
      private static final class Parser {
  
          private int position = 0;
          private byte[] buffer;
          private char[]  charsBuffer;
  
          private Parser(byte[] buffer) {
              this.buffer = buffer;
              charsBuffer = localCharsBufferCache.get();
              if(charsBuffer != null){
                  localCharsBufferCache.set(null);
              }else{
                  charsBuffer = new char[512];
              }
          }
  
  
          private   final Object parse(){
              return  readObject();
          }
  
          private final void close(){
              position = 0;
              buffer = null;
              if(charsBuffer != null){
                  localCharsBufferCache.set(charsBuffer);
              }
              charsBuffer = null;
          }
  
          private final Object readObject(){
              byte type  = readType();
              switch (type){
                  case STRING_TYPE:
                      return readUTF16String();
                  case NUMBER_INT_TYPE :
                      return  readVarInt();
                  case NUMBER_FLOAT_TYPE :
                      return  readFloat();
                  case MAP_TYPE:
                      return readMap();
                  case ARRAY_TYPE:
                      return readArray();
                  case NUMBER_DOUBLE_TYPE :
                      return readDouble();
                  case NUMBER_LONG_TYPE :
                      return  readLong();
                  case NUMBER_BIG_INTEGER_TYPE :
                      return  new BigInteger(readUTF16String());
                  case NUMBER_BIG_DECIMAL_TYPE :
                      return  new BigDecimal(readUTF16String());
                  case BOOLEAN_TYPE_FALSE:
                      return  Boolean.FALSE;
                  case BOOLEAN_TYPE_TRUE:
                      return  Boolean.TRUE;
                  case NULL_TYPE:
                      return  null;
                  default:
                      throw new RuntimeException("wson unhandled type " + type + " " +
                              position  +  " length " + buffer.length);
              }
          }
  
  
  
          private final Object readMap(){
              int size = readUInt();
              Map<String, Object> object = new JSONObject();;
              for(int i=0; i<size; i++){
                  String key = readMapKeyUTF16();
                  Object value = readObject();
                  object.put(key, value);
              }
              return object;
          }
  
          private final Object readArray(){
              int length = readUInt();
              List<Object> array = new JSONArray(length);
              for(int i=0; i<length; i++){
                  array.add(readObject());
              }
              return  array;
          }
  
          private  final byte readType(){
              byte type = buffer[position];
              position ++;
              return  type;
          }
  
  
          private final String readMapKeyUTF16() {
              int length = readUInt();
              length = length/2;
              if(charsBuffer.length < length){
                  charsBuffer = new char[length];
              }
              int hash = 5381;
              if(IS_NATIVE_LITTLE_ENDIAN){
                  for(int i=0; i<length; i++){
                      char ch = (char) ((buffer[position] & 0xFF) +
                              (buffer[position + 1] << 8));
                      charsBuffer[i] = (ch);
                      hash = ((hash << 5) + hash)  + ch;
                      position+=2;
                  }
              }else{
                  for(int i=0; i<length; i++){
                      char ch = (char) ((buffer[position + 1] & 0xFF) +
                              (buffer[position] << 8));
                      charsBuffer[i] = (ch);
                      hash = ((hash << 5) + hash)  + ch;
                      position+=2;
                  }
              }
              int globalIndex = (globalStringBytesCache.length - 1)&hash;
              String cache = globalStringBytesCache[globalIndex];
              if(cache != null
                      && cache.length() == length){
                  boolean isStringEqual  = true;
                  for(int i=0; i<length; i++){
                      if(charsBuffer[i] != cache.charAt(i)){
                          isStringEqual = false;
                          break;
                      }
                  }
                  if(isStringEqual) {
                      return cache;
                  }
              }
              cache = new String(charsBuffer, 0, length);
              if(length < 64) {
                  globalStringBytesCache[globalIndex] = cache;
              }
              return  cache;
          }
  
          private final String readUTF16String(){
              int length = readUInt()/2;
              if(charsBuffer.length < length){
                  charsBuffer = new char[length];
              }
              if(IS_NATIVE_LITTLE_ENDIAN){
                  for(int i=0; i<length; i++){
                      char ch = (char) ((buffer[position] & 0xFF) +
                              (buffer[position + 1] << 8));
                      charsBuffer[i] = (ch);
                      position+=2;
                  }
              }else{
                  for(int i=0; i<length; i++){
                      char ch = (char) ((buffer[position + 1] & 0xFF) +
                              (buffer[position] << 8));
                      charsBuffer[i] = (ch);
                      position+=2;
                  }
              }
              return  new String(charsBuffer, 0, length);
          }
  
  
  
  
  
          private   final int readVarInt(){
              int raw = readUInt();
              // This undoes the trick in putVarInt()
              int num = (((raw << 31) >> 31) ^ raw) >> 1;
              // This extra step lets us deal with the largest signed values by treating
              // negative results from read unsigned methods as like unsigned values.
              // Must re-flip the top bit if the original read value had it set.
              return num ^ (raw & (1 << 31));
          }
  
          private final  int readUInt(){
              int value = 0;
              int i = 0;
              int b;
              while (((b = buffer[position]) & 0x80) != 0) {
                  value |= (b & 0x7F) << i;
                  i += 7;
                  position+=1;
                  if (i > 35) {
                      throw new IllegalArgumentException("Variable length quantity is too long");
                  }
              }
              position+=1;
              return value | (b << i);
          }
  
          private final long readLong(){
              long number = (((buffer[position + 7] & 0xFFL)      ) +
                      ((buffer[position + 6] & 0xFFL) <<  8) +
                      ((buffer[position + 5] & 0xFFL) << 16) +
                      ((buffer[position + 4] & 0xFFL) << 24) +
                      ((buffer[position + 3] & 0xFFL) << 32) +
                      ((buffer[position + 2] & 0xFFL) << 40) +
                      ((buffer[position + 1] & 0xFFL) << 48) +
                      (((long) buffer[position])      << 56));
              position += 8;
              return  number;
          }
  
          private  final Object readDouble(){
              double number = Double.longBitsToDouble(readLong());
              if(number > Integer.MAX_VALUE){
                  long numberLong = (long) number;
                  double doubleLong = (numberLong);
                  if(number - doubleLong < Double.MIN_NORMAL){
                      return numberLong;
                  }
              }
              return  number;
          }
  
          private Object readFloat() {
              int number = (((buffer[position + 3] & 0xFF)      ) +
                      ((buffer[position + 2] & 0xFF) <<  8) +
                      ((buffer[position + 1] & 0xFF) << 16) +
                      ((buffer[position  ] & 0xFF) << 24));
              position +=4;
              return  Float.intBitsToFloat(number);
          }
      }
  
      /**
       * wson builder
       * */
      private static final class Builder {
  
          private byte[] buffer;
          private int position;
          private ArrayList refs;
          private final static ThreadLocal<byte[]> bufLocal = new ThreadLocal<byte[]>();
          private final static ThreadLocal<ArrayList> refsLocal = new ThreadLocal<ArrayList>();
  
  
  
          private Builder(){
              buffer =  bufLocal.get();
              if(buffer != null) {
                  bufLocal.set(null);
              }else{
                  buffer = new byte[1024];
              }
              refs = refsLocal.get();
              if(refs != null){
                  refsLocal.set(null);
              }else{
                  refs = new ArrayList<>(16);
              }
          }
  
  
          private final byte[] toWson(Object object){
              writeObject(object);
              byte[] bts = new byte[position];
              System.arraycopy(buffer, 0, bts, 0, position);
              return  bts;
          }
  
          private final void close(){
              if(buffer.length <= 1024*16){
                  bufLocal.set(buffer);
              }
              if(refs.isEmpty()){
                  refsLocal.set(refs);
              }else{
                  refs.clear();
              }
              refs = null;
              buffer = null;
              position = 0;
          }
  
          private final void writeObject(Object object) {
              if(object instanceof  CharSequence){
                  ensureCapacity(2);
                  writeByte(STRING_TYPE);
                  writeUTF16String((CharSequence) object);
                  return;
              }else if (object instanceof Map){
                  if(refs.contains(object)){
                      ensureCapacity(2);
                      writeByte(NULL_TYPE);
                      return;
                  }
                  refs.add(object);
                  Map map = (Map) object;
                  writeMap(map);
                  refs.remove(refs.size()-1);
                  return;
              }else if (object instanceof List){
                  if(refs.contains(object)){
                      ensureCapacity(2);
                      writeByte(NULL_TYPE);
                      return;
                  }
                  refs.add(object);
                  ensureCapacity(8);
                  List list = (List) object;
                  writeByte(ARRAY_TYPE);
                  writeUInt(list.size());
                  for(Object value : list){
                      writeObject(value);
                  }
                  refs.remove(refs.size()-1);
                  return;
              }else if (object instanceof Number){
                  Number number = (Number) object;
                  writeNumber(number);
                  return;
              }else if (object instanceof  Boolean){
                  ensureCapacity(2);
                  Boolean value  = (Boolean) object;
                  if(value){
                      writeByte(BOOLEAN_TYPE_TRUE);
                  }else{
                      writeByte(BOOLEAN_TYPE_FALSE);
                  }
                  return;
              }else if(object == null){
                  ensureCapacity(2);
                  writeByte(NULL_TYPE);
                  return;
              }else if (object.getClass().isArray()){
                  if(refs.contains(object)){
                      ensureCapacity(2);
                      writeByte(NULL_TYPE);
                      return;
                  }
                  refs.add(object);
                  ensureCapacity(8);
                  int length = Array.getLength(object);
                  writeByte(ARRAY_TYPE);
                  writeUInt(length);
                  for(int i=0; i<length; i++){
                      Object value = Array.get(object, i);
                      writeObject(value);
                  }
                  refs.remove(refs.size()-1);
                  return;
              }else  if(object instanceof  Date){
                  ensureCapacity(10);
                  double date = ((Date)object).getTime();
                  writeByte(NUMBER_DOUBLE_TYPE);
                  writeDouble(date);
              }else  if(object instanceof  Calendar){
                  ensureCapacity(10);
                  double date = ((Calendar)object).getTime().getTime();
                  writeByte(NUMBER_DOUBLE_TYPE);
                  writeDouble(date);
              }else  if(object instanceof  Collection){
                  if(refs.contains(object)){
                      ensureCapacity(2);
                      writeByte(NULL_TYPE);
                      return;
                  }
                  refs.add(object);
                  ensureCapacity(8);
                  Collection list = (Collection) object;
                  writeByte(ARRAY_TYPE);
                  writeUInt(list.size());
                  for(Object value : list){
                      writeObject(value);
                  }
                  refs.remove(refs.size()-1);
              }else{
                  if(refs.contains(object)){
                      ensureCapacity(2);
                      writeByte(NULL_TYPE);
                  }else {
                      refs.add(object);
                      if(object.getClass().isEnum()){
                          writeObject(JSON.toJSONString(object));
                      }else{
                          writeAdapterObject(object);
                      }
                      refs.remove(refs.size()-1);
                  }
                  return;
              }
          }
  
          private final void writeNumber(Number number) {
              ensureCapacity(12);
              if(number instanceof  Integer){
                  writeByte(NUMBER_INT_TYPE);
                  writeVarInt(number.intValue());
                  return;
              }
  
              if(number instanceof Float){
                  writeByte(NUMBER_FLOAT_TYPE);
                  writeFloat(number.floatValue());
                  return;
              }
              if(number instanceof  Double){
                  writeByte(NUMBER_DOUBLE_TYPE);
                  writeDouble(number.doubleValue());
                  return;
              }
  
              if(number instanceof  Long){
                  writeByte(NUMBER_LONG_TYPE);
                  writeLong(number.longValue());
                  return;
              }
  
              if(number instanceof  Short
                      || number instanceof  Byte){
                  writeByte(NUMBER_INT_TYPE);
                  writeVarInt(number.intValue());
                  return;
              }
  
              if(number instanceof BigInteger){
                  writeByte(NUMBER_BIG_INTEGER_TYPE);
                  writeUTF16String(number.toString());
                  return;
              }
  
              if(number instanceof BigDecimal){
                  String value = number.toString();
                  double doubleValue = number.doubleValue();
                  if(value.equals(Double.toString(doubleValue))){
                      writeByte(NUMBER_DOUBLE_TYPE);
                      writeDouble(doubleValue);
                  }else {
                      writeByte(NUMBER_BIG_DECIMAL_TYPE);
                      writeUTF16String(value);
                  }
                  return;
              }
              writeByte(STRING_TYPE);
              writeUTF16String(number.toString());
  
          }
  
          private final  void writeMap(Map map) {
              if(WriteMapNullValue){
                  ensureCapacity(8);
                  writeByte(MAP_TYPE);
                  writeUInt(map.size());
                  Set<Map.Entry<Object,Object>>  entries = map.entrySet();
                  for(Map.Entry<Object,Object> entry : entries){
                      writeMapKeyUTF16(entry.getKey().toString());
                      writeObject(entry.getValue());
                  }
              }else{
                  Set<Map.Entry<Object,Object>>  entries = map.entrySet();
                  int nullValueSize = 0;
                  for(Map.Entry<Object,Object> entry : entries){
                      if(entry.getValue() == null){
                          nullValueSize++;
                      }
                  }
  
                  ensureCapacity(8);
                  writeByte(MAP_TYPE);
                  writeUInt(map.size()-nullValueSize);
                  for(Map.Entry<Object,Object> entry : entries){
                      if(entry.getValue() == null){
                          continue;
                      }
                      writeMapKeyUTF16(entry.getKey().toString());
                      writeObject(entry.getValue());
                  }
              }
          }
  
  
          private final void writeByte(byte type){
              buffer[position] = type;
              position++;
          }
  
          private final void writeAdapterObject(Object object){
              if(specialClass.get(object.getClass().getName()) != null){
                  writeObject(JSON.toJSON(object));
                  return;
              }
              try{
                  writeMap(toMap(object));
              }catch (Exception e){
                  specialClass.put(object.getClass().getName(), true);
                  writeObject(JSON.toJSON(object));
              }
          }
  
          private  final Map  toMap(Object object){
              Map map = new JSONObject();
              try {
                  Class<?> targetClass = object.getClass();
                  String key = targetClass.getName();
                  List<Method> methods = getBeanMethod(key, targetClass);
                  for (Method method : methods) {
                      String methodName = method.getName();
                      if (methodName.startsWith(METHOD_PREFIX_GET)) {
                          Object value = method.invoke(object);
                          if(value != null){
                              StringBuilder builder = new StringBuilder(method.getName().substring(3));
                              builder.setCharAt(0, Character.toLowerCase(builder.charAt(0)));
                              map.put(builder.toString(), (Object) value);
                          }
                      }else if(methodName.startsWith(METHOD_PREFIX_IS)){
                          Object value = method.invoke(object);
                          if(value != null){
                              StringBuilder builder = new StringBuilder(method.getName().substring(2));
                              builder.setCharAt(0, Character.toLowerCase(builder.charAt(0)));
                              map.put(builder.toString(), value);
                          }
                      }
                  }
                  List<Field> fields = getBeanFields(key, targetClass);
                  for(Field field : fields){
                      String fieldName = field.getName();
                      if(map.containsKey(fieldName)){
                          continue;
                      }
                      Object value  = field.get(object);
                      if(value == null){
                          continue;
                      }
                      map.put(fieldName, value);
                  }
              }catch (Exception e){
                  if(e instanceof  RuntimeException){
                      throw  (RuntimeException)e;
                  }else{
                      throw  new RuntimeException(e);
                  }
              }
              return  map;
          }
  
          private  final void writeMapKeyUTF16(String value){
              writeUTF16String(value);
          }
  
  
  
  
          /**
           * writeString UTF-16
           * */
          private  final void writeUTF16String(CharSequence value){
              int length = value.length();
              ensureCapacity(length*2 + 8);
              writeUInt(length*2);
              if(IS_NATIVE_LITTLE_ENDIAN){
                  for(int i=0; i<length; i++){
                      char ch = value.charAt(i);
                      buffer[position] = (byte) (ch);
                      buffer[position+1] = (byte) (ch >>> 8);
                      position+=2;
                  }
              }else{
                  for(int i=0; i<length; i++){
                      char ch = value.charAt(i);
                      buffer[position + 1] = (byte) (ch      );
                      buffer[position] = (byte) (ch >>> 8);
                      position+=2;
                  }
              }
          }
  
  
          private final void writeDouble(double value){
              writeLong(Double.doubleToLongBits(value));
          }
  
          private final void writeFloat(float value){
              int val = Float.floatToIntBits(value);
              buffer[position + 3] = (byte) (val       );
              buffer[position + 2] = (byte) (val >>>  8);
              buffer[position + 1] = (byte) (val >>> 16);
              buffer[position ] = (byte) (val >>> 24);
              position += 4;
          }
  
          private final void writeLong(long val){
              buffer[position + 7] = (byte) (val       );
              buffer[position + 6] = (byte) (val >>>  8);
              buffer[position + 5] = (byte) (val >>> 16);
              buffer[position + 4] = (byte) (val >>> 24);
              buffer[position + 3] = (byte) (val >>> 32);
              buffer[position + 2] = (byte) (val >>> 40);
              buffer[position + 1] = (byte) (val >>> 48);
              buffer[position    ] = (byte) (val >>> 56);
              position += 8;
          }
  
          private final void writeVarInt(int value){
              writeUInt((value << 1) ^ (value >> 31));
          }
  
          private final void  writeUInt(int value){
              while ((value & 0xFFFFFF80) != 0) {
                  buffer[position] = (byte)((value & 0x7F) | 0x80);
                  position++;
                  value >>>= 7;
              }
              buffer[position] = (byte)(value & 0x7F);
              position++;
          }
  
  
          private final void ensureCapacity(int minCapacity) {
              minCapacity += position;
              // overflow-conscious code
              if (minCapacity - buffer.length > 0){
                  int oldCapacity = buffer.length;
                  int newCapacity = oldCapacity << 1;
                  if(newCapacity < 1024*16){
                      newCapacity = 1024*16;
                  }
                  if (newCapacity - minCapacity < 0) {
                      newCapacity = minCapacity;
                  }
                  buffer = Arrays.copyOf(buffer, newCapacity);
              }
          }
      }
  
  
      /**
       * cache json property key, most of them all same
       * */
      private static final int GLOBAL_STRING_CACHE_SIZE = 2*1024;
      private static final ThreadLocal<char[]> localCharsBufferCache = new ThreadLocal<>();
      private static final String[] globalStringBytesCache = new String[GLOBAL_STRING_CACHE_SIZE];
  
  
  
  
      /**
       * lru cache, to map helper
       * */
      private static final String METHOD_PREFIX_GET = "get";
      private static final String METHOD_PREFIX_IS = "is";
      private static LruCache<String, List<Method>> methodsCache = new LruCache<>(128);
      private static LruCache<String, List<Field>> fieldsCache = new LruCache<>(128);
      private static LruCache<String, Boolean> specialClass = new LruCache<>(16);
  
  
      private static final List<Method> getBeanMethod(String key, Class targetClass){
          List<Method> methods = methodsCache.get(key);
          if(methods == null){
              methods = new ArrayList<>();
              Method[]  allMethods = targetClass.getMethods();
              for(Method method : allMethods){
                  if(method.getDeclaringClass() == Object.class){
                      continue;
                  }
                  if( (method.getModifiers() & Modifier.STATIC) != 0){
                      continue;
                  }
                  String methodName = method.getName();
                  if(methodName.startsWith(METHOD_PREFIX_GET)
                          || methodName.startsWith(METHOD_PREFIX_IS)) {
                      if(method.getAnnotation(JSONField.class) != null){
                          throw new UnsupportedOperationException("getBeanMethod JSONField Annotation Not Handled, Use toJSON");
                      }
                      methods.add(method);
                  }
              }
              methodsCache.put(key, methods);
          }
          return methods;
      }
  
  
  
      private static  final List<Field> getBeanFields(String key, Class targetClass){
          List<Field> fieldList = fieldsCache.get(key);
          if(fieldList == null) {
              Field[] fields = targetClass.getFields();
              fieldList = new ArrayList<>(fields.length);
              for(Field field : fields){
                  if((field.getModifiers() & Modifier.STATIC) != 0){
                      continue;
                  }
                  if(field.getAnnotation(JSONField.class) != null){
                      throw new UnsupportedOperationException("getBeanMethod JSONField Annotation Not Handled, Use toJSON");
                  }
                  fieldList.add(field);
              }
              fieldsCache.put(key, fieldList);
          }
          return  fieldList;
      }
  
  }
  