Java tutorial
/** * 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; } }