blob: b8b89928e3966a7d1d6a2eda147c0855f31b9294 [file] [log] [blame]
/**
* 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 com.taobao.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 com.taobao.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;
}
}