Java tutorial
/* * * * Copyright 2012-2015 Viant. * * 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.sm.store.hessian; import com.caucho.hessian.io.Hessian2Constants; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import java.util.HashMap; import java.util.IdentityHashMap; import java.util.Map; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class HessianWriter implements Hessian2Constants { private static final Log logger = LogFactory.getLog(HessianWriter.class); public static final int SIZE = 4091; Map<String, String> nameMap; //byte array output volatile byte[] buffer; //buffer index position volatile int offset; //concurrent lock Lock lock = new ReentrantLock(); // map of types HashMap<String, Integer> typeRefs; IdentityHashMap<Object, Integer> _refs; public HessianWriter(Map nameMap) { this.nameMap = nameMap; this.buffer = new byte[SIZE]; offset = 0; } public String getMappingName(String name) { String str = nameMap.get(name); if (str == null) { logger.warn(name + " not in the map"); return name; } else return str; } public Map<String, String> getNameMap() { return nameMap; } public HashMap<String, Integer> getTypeRefs() { return typeRefs; } public int getOffset() { return offset; } public byte[] getBuffer() { return buffer; } public void writeBoolean(boolean value) { if (value) write((byte) 'T'); else write((byte) 'F'); } public void writeInt(int value) { //max 5 bytes checkOverFlow(5); int offset = this.offset; if (INT_DIRECT_MIN <= value && value <= INT_DIRECT_MAX) buffer[offset++] = (byte) (value + BC_INT_ZERO); else if (INT_BYTE_MIN <= value && value <= INT_BYTE_MAX) { buffer[offset++] = (byte) (BC_INT_BYTE_ZERO + (value >> 8)); buffer[offset++] = (byte) (value); } else if (INT_SHORT_MIN <= value && value <= INT_SHORT_MAX) { buffer[offset++] = (byte) (BC_INT_SHORT_ZERO + (value >> 16)); buffer[offset++] = (byte) (value >> 8); buffer[offset++] = (byte) (value); } else { buffer[offset++] = (byte) ('I'); buffer[offset++] = (byte) (value >> 24); buffer[offset++] = (byte) (value >> 16); buffer[offset++] = (byte) (value >> 8); buffer[offset++] = (byte) (value); } this.offset = offset; } public void checkOverFlow(int len) { if (offset + len >= buffer.length) { if (offset + len >= buffer.length) { logger.info("expand length " + buffer.length + " to " + (2 * buffer.length)); byte[] dest = new byte[buffer.length * 2]; System.arraycopy(buffer, 0, dest, 0, buffer.length); buffer = dest; } else logger.info("pass " + buffer.length); } } public void writeString(String value) { int len = 5; //check overflow first checkOverFlow(len); int offset = this.offset; if (value == null) { buffer[offset++] = (byte) 'N'; this.offset = offset; } else { int length = value.length(); int strOffset = 0; while (length > 0x8000) { int sublen = 0x8000; //check overflow checkOverFlow(len + sublen); offset = this.offset; // chunk can't end in high surrogate char tail = value.charAt(strOffset + sublen - 1); if (0xd800 <= tail && tail <= 0xdbff) sublen--; buffer[offset + 0] = (byte) BC_STRING_CHUNK; buffer[offset + 1] = (byte) (sublen >> 8); buffer[offset + 2] = (byte) (sublen); this.offset = offset + 3; printString(value, strOffset, sublen); length -= sublen; strOffset += sublen; } //check overflow checkOverFlow(len + length); offset = this.offset; if (length <= STRING_DIRECT_MAX) { buffer[offset++] = (byte) (BC_STRING_DIRECT + length); } else if (length <= STRING_SHORT_MAX) { buffer[offset++] = (byte) (BC_STRING_SHORT + (length >> 8)); buffer[offset++] = (byte) (length); } else { buffer[offset++] = (byte) ('S'); buffer[offset++] = (byte) (length >> 8); buffer[offset++] = (byte) (length); } this.offset = offset; printString(value, strOffset, length); } } public byte[] getBytes() { if (offset < buffer.length) { byte[] dest = new byte[offset]; System.arraycopy(buffer, 0, dest, 0, offset); return dest; } else { if (offset > buffer.length) throw new RuntimeException("offset " + offset + " > " + buffer.length); return buffer; } } /** * Prints a string to the stream, encoded as UTF-8 * * @param v the string to print. */ public void printString(String v, int strOffset, int length) { int offset = this.offset; //byte [] buffer = _buffer; for (int i = 0; i < length; i++) { char ch = v.charAt(i + strOffset); if (ch < 0x80) buffer[offset++] = (byte) (ch); else if (ch < 0x800) { buffer[offset++] = (byte) (0xc0 + ((ch >> 6) & 0x1f)); buffer[offset++] = (byte) (0x80 + (ch & 0x3f)); } else { buffer[offset++] = (byte) (0xe0 + ((ch >> 12) & 0xf)); buffer[offset++] = (byte) (0x80 + ((ch >> 6) & 0x3f)); buffer[offset++] = (byte) (0x80 + (ch & 0x3f)); } } this.offset = offset; } /** * Writes a byte array to the stream. * The array will be written with the following syntax: * * <code><pre> * B b16 b18 bytes * </pre></code> * * If the value is null, it will be written as * * <code><pre> * N * </pre></code> * * param value the string value to write. */ public void writeBytes(byte[] data) { if (data == null) { //buffer[offset++] = 'N'; write((byte) 'N'); } else writeBytes(data, 0, data.length); } /** * Writes a byte array to the stream. * The array will be written with the following syntax: * * <code><pre> * B b16 b18 bytes * </pre></code> * * If the value is null, it will be written as * * <code><pre> * N * </pre></code> * * param value the string value to write. */ public void writeBytes(byte[] data, int offset, int length) { if (data == null) { //buffer[offset++] = (byte) 'N'; write((byte) 'N'); } else { while (SIZE - 3 < length) { checkOverFlow(3 + length); int sublen = SIZE - 3; buffer[this.offset++] = (byte) BC_BINARY_CHUNK; buffer[this.offset++] = (byte) (sublen >> 8); buffer[this.offset++] = (byte) sublen; System.arraycopy(data, offset, buffer, this.offset, sublen); this.offset += sublen; length -= sublen; offset += sublen; } if (length <= BINARY_DIRECT_MAX) { buffer[this.offset++] = (byte) (BC_BINARY_DIRECT + length); } else if (length <= BINARY_SHORT_MAX) { buffer[this.offset++] = (byte) (BC_BINARY_SHORT + (length >> 8)); buffer[this.offset++] = (byte) (length); } else { buffer[this.offset++] = (byte) 'B'; buffer[this.offset++] = (byte) (length >> 8); buffer[this.offset++] = (byte) (length); } System.arraycopy(data, offset, buffer, this.offset, length); this.offset += length; } } /** * Writes a double value to the stream. The double will be written * with the following syntax: * * <code><pre> * D b64 b56 b48 b40 b32 b24 b16 b8 * </pre></code> * * @param value the double value to write. */ public void writeDouble(double value) { checkOverFlow(10); int offset = this.offset; int intValue = (int) value; if (intValue == value) { if (intValue == 0) { buffer[offset++] = (byte) BC_DOUBLE_ZERO; this.offset = offset; return; } else if (intValue == 1) { buffer[offset++] = (byte) BC_DOUBLE_ONE; this.offset = offset; return; } else if (-0x80 <= intValue && intValue < 0x80) { buffer[offset++] = (byte) BC_DOUBLE_BYTE; buffer[offset++] = (byte) intValue; this.offset = offset; return; } else if (-0x8000 <= intValue && intValue < 0x8000) { buffer[offset + 0] = (byte) BC_DOUBLE_SHORT; buffer[offset + 1] = (byte) (intValue >> 8); buffer[offset + 2] = (byte) intValue; this.offset = offset + 3; return; } } int mills = (int) (value * 1000); if (0.001 * mills == value) { buffer[offset + 0] = (byte) (BC_DOUBLE_MILL); buffer[offset + 1] = (byte) (mills >> 24); buffer[offset + 2] = (byte) (mills >> 16); buffer[offset + 3] = (byte) (mills >> 8); buffer[offset + 4] = (byte) (mills); this.offset = offset + 5; return; } long bits = Double.doubleToLongBits(value); buffer[offset + 0] = (byte) 'D'; buffer[offset + 1] = (byte) (bits >> 56); buffer[offset + 2] = (byte) (bits >> 48); buffer[offset + 3] = (byte) (bits >> 40); buffer[offset + 4] = (byte) (bits >> 32); buffer[offset + 5] = (byte) (bits >> 24); buffer[offset + 6] = (byte) (bits >> 16); buffer[offset + 7] = (byte) (bits >> 8); buffer[offset + 8] = (byte) (bits); this.offset = offset + 9; } /** * Writes the list header to the stream. List writers will call * <code>writeListBegin</code> followed by the list contents and then * call <code>writeListEnd</code>. * * <code><pre> * list ::= V type value* Z * ::= v type int value* * </pre></code> * * @return true for variable lists, false for fixed lists */ public boolean writeListBegin(int length, String type) { if (length < 0) { if (type != null) { //buffer[offset++] = (byte) BC_LIST_VARIABLE; write((byte) BC_LIST_VARIABLE); writeType(type); } else write((byte) BC_LIST_VARIABLE_UNTYPED); //buffer[offset++] = (byte) BC_LIST_VARIABLE_UNTYPED; return true; } else if (length <= LIST_DIRECT_MAX) { if (type != null) { //buffer[offset++] = (byte) (BC_LIST_DIRECT + length); write((byte) (BC_LIST_DIRECT + length)); writeType(type); } else { //buffer[offset++] = (byte) (BC_LIST_DIRECT_UNTYPED + length); write((byte) (BC_LIST_DIRECT_UNTYPED + length)); } return false; } else { if (type != null) { //buffer[offset++] = (byte) BC_LIST_FIXED; write((byte) BC_LIST_FIXED); writeType(type); } else { //buffer[offset++] = (byte) BC_LIST_FIXED_UNTYPED; write((byte) BC_LIST_FIXED_UNTYPED); } writeInt(length); return false; } } /** * Writes the tail of the list to the stream for a variable-length list. */ public void writeListEnd() { //buffer[offset++] = (byte) BC_END; write((byte) BC_END); } /** * <code><pre> * type ::= string * ::= int * </code></pre> */ private void writeType(String type) { int len = type.length(); if (len == 0) { throw new IllegalArgumentException("empty type is not allowed"); } if (typeRefs == null) typeRefs = new HashMap<String, Integer>(); Integer typeRefV = typeRefs.get(type); if (typeRefV != null) { int typeRef = typeRefV.intValue(); writeInt(typeRef); } else { typeRefs.put(type, Integer.valueOf(typeRefs.size())); writeString(type); } } public void writeObject(Object object) { if (object == null) { writeNull(); return; } // writeDefinition20(out); // writeObjectBegin(cl.getName()); // writeInstance(obj, out); } private void writeDefinition20(ClassDef classDef) { write(classDef.getName().getBytes()); writeInt(classDef.fields.size()); for (FieldDef each : classDef.fields) { //writeString(each.getName()); write(each.getName().getBytes()); } } public boolean addRef(Object object) { // return false if there is no IdentifyMap if (_refs == null) return false; int ref; if (_refs.containsKey(object)) ref = _refs.get(object); else ref = -1; // int newRef = _refs.size(); // int ref = _refs.put(object, newRef, false); // if (ref != newRef) { if (ref > 0) { writeRef(ref); return true; } else { return false; } } protected void writeRef(int value) { write((byte) BC_REF); writeInt(value); } /** * Writes the map header to the stream. Map writers will call * <code>writeMapBegin</code> followed by the map contents and then * call <code>writeMapEnd</code>. * * <code><pre> * map ::= M type (<value> <value>)* Z * ::= H (<value> <value>)* Z * </pre></code> */ public void writeMapBegin(String type) { if (type != null) { //buffer[offset++] = BC_MAP; write((byte) BC_MAP); writeType(type); } else write((byte) BC_MAP_UNTYPED); //buffer[offset++] = BC_MAP_UNTYPED; } /** * Writes the tail of the map to the stream. */ public void writeMapEnd() { //buffer[offset++] = (byte) BC_END; write((byte) BC_END); } public void writeNull() { write((byte) 'N'); } public void write(byte b) { checkOverFlow(1); buffer[offset++] = b; } public void write(byte[] bytes) { checkOverFlow(bytes.length); for (int i = 0; i < bytes.length; i++) { buffer[offset++] = bytes[i]; } } }