/*
* Copyright (c) 2001-2008 Caucho Technology, Inc. All rights reserved.
*
* The Apache Software License, Version 1.1
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution, if
* any, must include the following acknowlegement:
* "This product includes software developed by the
* Caucho Technology (http://www.caucho.com/)."
* Alternately, this acknowlegement may appear in the software itself,
* if and wherever such third-party acknowlegements normally appear.
*
* 4. The names "Burlap", "Resin", and "Caucho" must not be used to
* endorse or promote products derived from this software without prior
* written permission. For written permission, please contact
* info@caucho.com.
*
* 5. Products derived from this software may not be called "Resin"
* nor may "Resin" appear in their names without prior written
* permission of Caucho Technology.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL CAUCHO TECHNOLOGY OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
* IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* @author Scott Ferguson
*/
package com.caucho.hessian4.io;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import org.jboss.netty.channel.ChannelFuture;
import com.caucho.hessian4.util.IdentityIntMap;
/**
* Output stream for Hessian 2 requests.
*
* <p>Since HessianOutput does not depend on any classes other than
* in the JDK, it can be extracted independently into a smaller package.
*
* <p>HessianOutput is unbuffered, so any client needs to provide
* its own buffering.
*
* <pre>
* OutputStream os = ...; // from http connection
* Hessian2Output out = new Hessian2Output(os);
* String value;
*
* out.startCall("hello", 1); // start hello call
* out.writeString("arg1"); // write a string argument
* out.completeCall(); // complete the call
* </pre>
*/
public class Hessian2Output
extends AbstractHessianOutput
implements Hessian2Constants
{
public final static int SIZE = 1024*1024;
// the output stream/
protected OutputStream _os;
// map of references
private final IdentityIntMap _refs
= new IdentityIntMap(256);
private boolean _isCloseStreamOnClose;
// map of classes
private final IdentityIntMap _classRefs
= new IdentityIntMap(256);
// map of types
private HashMap<String,Integer> _typeRefs;
private final byte []_buffer = new byte[SIZE];
private int _offset;
private boolean _isPacket;
/**
* Creates a new Hessian output stream, initialized with an
* underlying output stream.
*
* @param os the underlying output stream.
*/
public Hessian2Output(OutputStream os)
{
init(os);
}
public void init(OutputStream os)
{
_os = os;
reset();
}
public void setCloseStreamOnClose(boolean isClose)
{
_isCloseStreamOnClose = isClose;
}
public boolean isCloseStreamOnClose()
{
return _isCloseStreamOnClose;
}
/**
* Writes a complete method call.
*/
@Override
public void call(String method, Object []args)
throws IOException
{
writeVersion();
int length = args != null ? args.length : 0;
startCall(method, length);
for (int i = 0; i < length; i++)
writeObject(args[i]);
completeCall();
flush();
}
/**
* Starts the method call. Clients would use <code>startCall</code>
* instead of <code>call</code> if they wanted finer control over
* writing the arguments, or needed to write headers.
*
* <code><pre>
* C
* string # method name
* int # arg count
* </pre></code>
*
* @param method the method name to call.
*/
public void startCall(String method, int length)
throws IOException
{
int offset = _offset;
if (SIZE < offset + 32) {
flushBuffer();
offset = _offset;
}
byte []buffer = _buffer;
buffer[_offset++] = (byte) 'C';
writeString(method);
writeInt(length);
}
/**
* Writes the call tag. This would be followed by the
* method and the arguments
*
* <code><pre>
* C
* </pre></code>
*
* @param method the method name to call.
*/
public void startCall()
throws IOException
{
flushIfFull();
_buffer[_offset++] = (byte) 'C';
}
/**
* Starts an envelope.
*
* <code><pre>
* E major minor
* m b16 b8 method-name
* </pre></code>
*
* @param method the method name to call.
*/
public void startEnvelope(String method)
throws IOException
{
int offset = _offset;
if (SIZE < offset + 32) {
flushBuffer();
offset = _offset;
}
_buffer[_offset++] = (byte) 'E';
writeString(method);
}
/**
* Completes an envelope.
*
* <p>A successful completion will have a single value:
*
* <pre>
* Z
* </pre>
*/
public void completeEnvelope()
throws IOException
{
flushIfFull();
_buffer[_offset++] = (byte) 'Z';
}
/**
* Writes the method tag.
*
* <code><pre>
* string
* </pre></code>
*
* @param method the method name to call.
*/
public void writeMethod(String method)
throws IOException
{
writeString(method);
}
/**
* Completes.
*
* <code><pre>
* z
* </pre></code>
*/
public void completeCall()
throws IOException
{
/*
flushIfFull();
_buffer[_offset++] = (byte) 'Z';
*/
}
/**
* Starts the reply
*
* <p>A successful completion will have a single value:
*
* <pre>
* R
* </pre>
*/
public void startReply()
throws IOException
{
writeVersion();
flushIfFull();
_buffer[_offset++] = (byte) 'R';
}
public void writeVersion()
throws IOException
{
flushIfFull();
_buffer[_offset++] = (byte) 'H';
_buffer[_offset++] = (byte) 2;
_buffer[_offset++] = (byte) 0;
}
/**
* Completes reading the reply
*
* <p>A successful completion will have a single value:
*
* <pre>
* z
* </pre>
*/
public void completeReply()
throws IOException
{
}
/**
* Starts a packet
*
* <p>A message contains several objects encapsulated by a length</p>
*
* <pre>
* p x02 x00
* </pre>
*/
public void startMessage()
throws IOException
{
flushIfFull();
_buffer[_offset++] = (byte) 'p';
_buffer[_offset++] = (byte) 2;
_buffer[_offset++] = (byte) 0;
}
/**
* Completes reading the message
*
* <p>A successful completion will have a single value:
*
* <pre>
* z
* </pre>
*/
public void completeMessage()
throws IOException
{
flushIfFull();
_buffer[_offset++] = (byte) 'z';
}
/**
* Writes a fault. The fault will be written
* as a descriptive string followed by an object:
*
* <code><pre>
* F map
* </pre></code>
*
* <code><pre>
* F H
* \x04code
* \x10the fault code
*
* \x07message
* \x11the fault message
*
* \x06detail
* M\xnnjavax.ejb.FinderException
* ...
* Z
* Z
* </pre></code>
*
* @param code the fault code, a three digit
*/
public void writeFault(String code, String message, Object detail)
throws IOException
{
flushIfFull();
writeVersion();
_buffer[_offset++] = (byte) 'F';
_buffer[_offset++] = (byte) 'H';
_refs.put(new Object(), _refs.size(), false);
writeString("code");
writeString(code);
writeString("message");
writeString(message);
if (detail != null) {
writeString("detail");
writeObject(detail);
}
flushIfFull();
_buffer[_offset++] = (byte) 'Z';
}
/**
* Writes any object to the output stream.
*/
public void writeObject(Object object)
throws IOException
{
if (object == null) {
writeNull();
return;
}
Serializer serializer
= findSerializerFactory().getObjectSerializer(object.getClass());
serializer.writeObject(object, this);
}
/**
* 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)
throws IOException
{
flushIfFull();
if (length < 0) {
if (type != null) {
_buffer[_offset++] = (byte) BC_LIST_VARIABLE;
writeType(type);
}
else
_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);
writeType(type);
}
else {
_buffer[_offset++] = (byte) (BC_LIST_DIRECT_UNTYPED + length);
}
return false;
}
else {
if (type != null) {
_buffer[_offset++] = (byte) BC_LIST_FIXED;
writeType(type);
}
else {
_buffer[_offset++] = (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()
throws IOException
{
flushIfFull();
_buffer[_offset++] = (byte) BC_END;
}
/**
* 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)
throws IOException
{
if (SIZE < _offset + 32)
flushBuffer();
if (type != null) {
_buffer[_offset++] = BC_MAP;
writeType(type);
}
else
_buffer[_offset++] = BC_MAP_UNTYPED;
}
/**
* Writes the tail of the map to the stream.
*/
public void writeMapEnd()
throws IOException
{
if (SIZE < _offset + 32)
flushBuffer();
_buffer[_offset++] = (byte) BC_END;
}
/**
* Writes the object definition
*
* <code><pre>
* C <string> <int> <string>*
* </pre></code>
*/
@Override
public int writeObjectBegin(String type)
throws IOException
{
int newRef = _classRefs.size();
int ref = _classRefs.put(type, newRef, false);
if (newRef != ref) {
if (SIZE < _offset + 32)
flushBuffer();
if (ref <= OBJECT_DIRECT_MAX) {
_buffer[_offset++] = (byte) (BC_OBJECT_DIRECT + ref);
}
else {
_buffer[_offset++] = (byte) 'O';
writeInt(ref);
}
return ref;
}
else {
if (SIZE < _offset + 32)
flushBuffer();
_buffer[_offset++] = (byte) 'C';
writeString(type);
return -1;
}
}
/**
* Writes the tail of the class definition to the stream.
*/
public void writeClassFieldLength(int len)
throws IOException
{
writeInt(len);
}
/**
* Writes the tail of the object definition to the stream.
*/
public void writeObjectEnd()
throws IOException
{
}
/**
* <code><pre>
* type ::= string
* ::= int
* </code></pre>
*/
private void writeType(String type)
throws IOException
{
flushIfFull();
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 = (Integer) _typeRefs.get(type);
if (typeRefV != null) {
int typeRef = typeRefV.intValue();
writeInt(typeRef);
}
else {
_typeRefs.put(type, Integer.valueOf(_typeRefs.size()));
writeString(type);
}
}
/**
* Writes a boolean value to the stream. The boolean will be written
* with the following syntax:
*
* <code><pre>
* T
* F
* </pre></code>
*
* @param value the boolean value to write.
*/
public void writeBoolean(boolean value)
throws IOException
{
if (SIZE < _offset + 16)
flushBuffer();
if (value)
_buffer[_offset++] = (byte) 'T';
else
_buffer[_offset++] = (byte) 'F';
}
/**
* Writes an integer value to the stream. The integer will be written
* with the following syntax:
*
* <code><pre>
* I b32 b24 b16 b8
* </pre></code>
*
* @param value the integer value to write.
*/
public void writeInt(int value)
throws IOException
{
int offset = _offset;
byte []buffer = _buffer;
if (SIZE <= offset + 16) {
flushBuffer();
offset = _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);
}
_offset = offset;
}
/**
* Writes a long value to the stream. The long will be written
* with the following syntax:
*
* <code><pre>
* L b64 b56 b48 b40 b32 b24 b16 b8
* </pre></code>
*
* @param value the long value to write.
*/
public void writeLong(long value)
throws IOException
{
int offset = _offset;
byte []buffer = _buffer;
if (SIZE <= offset + 16) {
flushBuffer();
offset = _offset;
}
if (LONG_DIRECT_MIN <= value && value <= LONG_DIRECT_MAX) {
buffer[offset++] = (byte) (value + BC_LONG_ZERO);
}
else if (LONG_BYTE_MIN <= value && value <= LONG_BYTE_MAX) {
buffer[offset++] = (byte) (BC_LONG_BYTE_ZERO + (value >> 8));
buffer[offset++] = (byte) (value);
}
else if (LONG_SHORT_MIN <= value && value <= LONG_SHORT_MAX) {
buffer[offset++] = (byte) (BC_LONG_SHORT_ZERO + (value >> 16));
buffer[offset++] = (byte) (value >> 8);
buffer[offset++] = (byte) (value);
}
else if (-0x80000000L <= value && value <= 0x7fffffffL) {
buffer[offset + 0] = (byte) BC_LONG_INT;
buffer[offset + 1] = (byte) (value >> 24);
buffer[offset + 2] = (byte) (value >> 16);
buffer[offset + 3] = (byte) (value >> 8);
buffer[offset + 4] = (byte) (value);
offset += 5;
}
else {
buffer[offset + 0] = (byte) 'L';
buffer[offset + 1] = (byte) (value >> 56);
buffer[offset + 2] = (byte) (value >> 48);
buffer[offset + 3] = (byte) (value >> 40);
buffer[offset + 4] = (byte) (value >> 32);
buffer[offset + 5] = (byte) (value >> 24);
buffer[offset + 6] = (byte) (value >> 16);
buffer[offset + 7] = (byte) (value >> 8);
buffer[offset + 8] = (byte) (value);
offset += 9;
}
_offset = offset;
}
/**
* 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)
throws IOException
{
int offset = _offset;
byte []buffer = _buffer;
if (SIZE <= offset + 16) {
flushBuffer();
offset = _offset;
}
int intValue = (int) value;
if (intValue == value) {
if (intValue == 0) {
buffer[offset++] = (byte) BC_DOUBLE_ZERO;
_offset = offset;
return;
}
else if (intValue == 1) {
buffer[offset++] = (byte) BC_DOUBLE_ONE;
_offset = offset;
return;
}
else if (-0x80 <= intValue && intValue < 0x80) {
buffer[offset++] = (byte) BC_DOUBLE_BYTE;
buffer[offset++] = (byte) intValue;
_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;
_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);
_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);
_offset = offset + 9;
}
/**
* Writes a date to the stream.
*
* <code><pre>
* date ::= d b7 b6 b5 b4 b3 b2 b1 b0
* ::= x65 b3 b2 b1 b0
* </pre></code>
*
* @param time the date in milliseconds from the epoch in UTC
*/
public void writeUTCDate(long time)
throws IOException
{
if (SIZE < _offset + 32)
flushBuffer();
int offset = _offset;
byte []buffer = _buffer;
if (time % 60000L == 0) {
// compact date ::= x65 b3 b2 b1 b0
long minutes = time / 60000L;
if ((minutes >> 31) == 0 || (minutes >> 31) == -1) {
buffer[offset++] = (byte) BC_DATE_MINUTE;
buffer[offset++] = ((byte) (minutes >> 24));
buffer[offset++] = ((byte) (minutes >> 16));
buffer[offset++] = ((byte) (minutes >> 8));
buffer[offset++] = ((byte) (minutes >> 0));
_offset = offset;
return;
}
}
buffer[offset++] = (byte) BC_DATE;
buffer[offset++] = ((byte) (time >> 56));
buffer[offset++] = ((byte) (time >> 48));
buffer[offset++] = ((byte) (time >> 40));
buffer[offset++] = ((byte) (time >> 32));
buffer[offset++] = ((byte) (time >> 24));
buffer[offset++] = ((byte) (time >> 16));
buffer[offset++] = ((byte) (time >> 8));
buffer[offset++] = ((byte) (time));
_offset = offset;
}
/**
* Writes a null value to the stream.
* The null will be written with the following syntax
*
* <code><pre>
* N
* </pre></code>
*
* @param value the string value to write.
*/
public void writeNull()
throws IOException
{
int offset = _offset;
byte []buffer = _buffer;
if (SIZE <= offset + 16) {
flushBuffer();
offset = _offset;
}
buffer[offset++] = 'N';
_offset = offset;
}
/**
* Writes a string value to the stream using UTF-8 encoding.
* The string will be written with the following syntax:
*
* <code><pre>
* S b16 b8 string-value
* </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 writeString(String value)
throws IOException
{
int offset = _offset;
byte []buffer = _buffer;
if (SIZE <= offset + 16) {
flushBuffer();
offset = _offset;
}
if (value == null) {
buffer[offset++] = (byte) 'N';
_offset = offset;
}
else {
int length = value.length();
int strOffset = 0;
while (length > 0x8000) {
int sublen = 0x8000;
offset = _offset;
if (SIZE <= offset + 16) {
flushBuffer();
offset = _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);
_offset = offset + 3;
printString(value, strOffset, sublen);
length -= sublen;
strOffset += sublen;
}
offset = _offset;
if (SIZE <= offset + 16) {
flushBuffer();
offset = _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);
}
_offset = offset;
printString(value, strOffset, length);
}
}
/**
* Writes a string value to the stream using UTF-8 encoding.
* The string will be written with the following syntax:
*
* <code><pre>
* S b16 b8 string-value
* </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 writeString(char []buffer, int offset, int length)
throws IOException
{
if (buffer == null) {
if (SIZE < _offset + 16)
flushBuffer();
_buffer[_offset++] = (byte) ('N');
}
else {
while (length > 0x8000) {
int sublen = 0x8000;
if (SIZE < _offset + 16)
flushBuffer();
// chunk can't end in high surrogate
char tail = buffer[offset + sublen - 1];
if (0xd800 <= tail && tail <= 0xdbff)
sublen--;
_buffer[_offset++] = (byte) BC_STRING_CHUNK;
_buffer[_offset++] = (byte) (sublen >> 8);
_buffer[_offset++] = (byte) (sublen);
printString(buffer, offset, sublen);
length -= sublen;
offset += sublen;
}
if (SIZE < _offset + 16)
flushBuffer();
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);
}
printString(buffer, offset, 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 []buffer)
throws IOException
{
if (buffer == null) {
if (SIZE < _offset + 16)
flushBuffer();
_buffer[_offset++] = 'N';
}
else
writeBytes(buffer, 0, buffer.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 []buffer, int offset, int length)
throws IOException
{
if (buffer == null) {
if (SIZE < _offset + 16)
flushBuffer();
_buffer[_offset++] = (byte) 'N';
}
else {
while (SIZE - _offset - 3 < length) {
int sublen = SIZE - _offset - 3;
if (sublen < 16) {
flushBuffer();
sublen = SIZE - _offset - 3;
if (length < sublen)
sublen = length;
}
_buffer[_offset++] = (byte) BC_BINARY_CHUNK;
_buffer[_offset++] = (byte) (sublen >> 8);
_buffer[_offset++] = (byte) sublen;
System.arraycopy(buffer, offset, _buffer, _offset, sublen);
_offset += sublen;
length -= sublen;
offset += sublen;
flushBuffer();
}
if (SIZE < _offset + 16)
flushBuffer();
if (length <= BINARY_DIRECT_MAX) {
_buffer[_offset++] = (byte) (BC_BINARY_DIRECT + length);
}
else if (length <= BINARY_SHORT_MAX) {
_buffer[_offset++] = (byte) (BC_BINARY_SHORT + (length >> 8));
_buffer[_offset++] = (byte) (length);
}
else {
_buffer[_offset++] = (byte) 'B';
_buffer[_offset++] = (byte) (length >> 8);
_buffer[_offset++] = (byte) (length);
}
System.arraycopy(buffer, offset, _buffer, _offset, length);
_offset += length;
}
}
/**
* Writes a byte buffer to the stream.
*
* <code><pre>
* </pre></code>
*/
public void writeByteBufferStart()
throws IOException
{
}
/**
* Writes a byte buffer to the stream.
*
* <code><pre>
* b b16 b18 bytes
* </pre></code>
*/
public void writeByteBufferPart(byte []buffer, int offset, int length)
throws IOException
{
while (length > 0) {
flushIfFull();
int sublen = _buffer.length - _offset;
if (length < sublen)
sublen = length;
_buffer[_offset++] = BC_BINARY_CHUNK;
_buffer[_offset++] = (byte) (sublen >> 8);
_buffer[_offset++] = (byte) sublen;
System.arraycopy(buffer, offset, _buffer, _offset, sublen);
_offset += sublen;
length -= sublen;
offset += sublen;
}
}
/**
* Writes a byte buffer to the stream.
*
* <code><pre>
* b b16 b18 bytes
* </pre></code>
*/
public void writeByteBufferEnd(byte []buffer, int offset, int length)
throws IOException
{
writeBytes(buffer, offset, length);
}
/**
* Returns an output stream to write binary data.
*/
public OutputStream getBytesOutputStream()
throws IOException
{
return new BytesOutputStream();
}
/**
* Writes a full output stream.
*/
@Override
public void writeByteStream(InputStream is)
throws IOException
{
while (true) {
int len = SIZE - _offset - 3;
if (len < 16) {
flushBuffer();
len = SIZE - _offset - 3;
}
len = is.read(_buffer, _offset + 3, len);
if (len <= 0) {
_buffer[_offset++] = BC_BINARY_DIRECT;
return;
}
_buffer[_offset + 0] = (byte) BC_BINARY_CHUNK;
_buffer[_offset + 1] = (byte) (len >> 8);
_buffer[_offset + 2] = (byte) (len);
_offset += len + 3;
}
}
/**
* Writes a reference.
*
* <code><pre>
* x51 <int>
* </pre></code>
*
* @param value the integer value to write.
*/
@Override
protected void writeRef(int value)
throws IOException
{
if (SIZE < _offset + 16)
flushBuffer();
_buffer[_offset++] = (byte) BC_REF;
writeInt(value);
}
/**
* If the object has already been written, just write its ref.
*
* @return true if we're writing a ref.
*/
public boolean addRef(Object object)
throws IOException
{
int newRef = _refs.size();
int ref = _refs.put(object, newRef, false);
if (ref != newRef) {
writeRef(ref);
return true;
}
else {
return false;
}
}
/**
* Removes a reference.
*/
/*
private boolean removeRef(Object obj)
throws IOException
{
if (_refs != null) {
_refs.put(obj, -1);
return true;
}
else
return false;
}
*/
/**
* Replaces a reference from one object to another.
*/
public boolean replaceRef(Object oldRef, Object newRef)
throws IOException
{
int value = _refs.get(oldRef);
if (value >= 0) {
_refs.put(newRef, value, true);
return true;
}
else
return false;
}
/**
* Starts the streaming message
*
* <p>A streaming message starts with 'P'</p>
*
* <pre>
* P x02 x00
* </pre>
*/
public void writeStreamingObject(Object obj)
throws IOException
{
startPacket();
writeObject(obj);
endPacket();
}
/**
* Starts a streaming packet
*
* <p>A streaming contains a set of chunks, ending with a zero chunk.
* Each chunk is a length followed by data where the length is
* encoded by (b1xxxxxxxx)* b0xxxxxxxx</p>
*/
public void startPacket()
throws IOException
{
if (_refs != null)
_refs.clear();
flushBuffer();
_isPacket = true;
_offset = 3;
_buffer[0] = (byte) 0x55;
_buffer[1] = (byte) 0x55;
_buffer[2] = (byte) 0x55;
}
public void endPacket()
throws IOException
{
int offset = _offset;
OutputStream os = _os;
if (os == null) {
_offset = 0;
return;
}
int len = offset - 3;
_buffer[0] = (byte) (0x80);
_buffer[1] = (byte) (0x80 + ((len >> 7) & 0x7f));
_buffer[2] = (byte) (len & 0x7f);
// end chunk
_buffer[offset++] = (byte) 0x80;
_buffer[offset++] = (byte) 0x00;
_isPacket = false;
_offset = 0;
if (os != null) {
if (len == 0) {
os.write(_buffer, 1, 2);
}
else if (len < 0x80) {
os.write(_buffer, 1, offset - 1);
}
else {
os.write(_buffer, 0, offset);
}
}
}
/**
* Prints a string to the stream, encoded as UTF-8 with preceeding length
*
* @param v the string to print.
*/
public void printLenString(String v)
throws IOException
{
if (SIZE < _offset + 16)
flushBuffer();
if (v == null) {
_buffer[_offset++] = (byte) (0);
_buffer[_offset++] = (byte) (0);
}
else {
int len = v.length();
_buffer[_offset++] = (byte) (len >> 8);
_buffer[_offset++] = (byte) (len);
printString(v, 0, len);
}
}
/**
* Prints a string to the stream, encoded as UTF-8
*
* @param v the string to print.
*/
public void printString(String v)
throws IOException
{
printString(v, 0, v.length());
}
/**
* 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)
throws IOException
{
int offset = _offset;
byte []buffer = _buffer;
for (int i = 0; i < length; i++) {
if (SIZE <= offset + 16) {
_offset = offset;
flushBuffer();
offset = _offset;
}
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));
}
}
_offset = offset;
}
/**
* Prints a string to the stream, encoded as UTF-8
*
* @param v the string to print.
*/
public void printString(char []v, int strOffset, int length)
throws IOException
{
int offset = _offset;
byte []buffer = _buffer;
for (int i = 0; i < length; i++) {
if (SIZE <= offset + 16) {
_offset = offset;
flushBuffer();
offset = _offset;
}
char ch = v[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));
}
}
_offset = offset;
}
private final void flushIfFull()
throws IOException
{
int offset = _offset;
if (SIZE < offset + 32) {
flushBuffer();
}
}
public final void flush()
throws IOException
{
flushBuffer();
_os.flush();
}
public final void flush(ChannelFuture future)
throws IOException
{
flushBuffer();
((FlushableOutput)_os).flush(future);
}
public final void flushBuffer()
throws IOException
{
if (_offset == 0)
return;
int offset = _offset;
OutputStream os = _os;
if (! _isPacket && offset > 0) {
_offset = 0;
if (os != null)
os.write(_buffer, 0, offset);
}
else if (_isPacket && offset > 3) {
int len = offset - 3;
_buffer[0] = (byte) 0x80;
_buffer[1] = (byte) (0x80 + ((len >> 7) & 0x7f));
_buffer[2] = (byte) (len & 0x7f);
_offset = 3;
if (os != null)
os.write(_buffer, 0, offset);
_buffer[0] = (byte) 0x56;
_buffer[1] = (byte) 0x56;
_buffer[2] = (byte) 0x56;
}
}
public void close()
throws IOException
{
// hessian/3a8c
flush();
OutputStream os = _os;
_os = null;
if (os != null) {
if (_isCloseStreamOnClose)
os.close();
}
}
public void free()
{
reset();
_os = null;
_isCloseStreamOnClose = false;
}
/**
* Resets the references for streaming.
*/
@Override
public void resetReferences()
{
if (_refs != null)
_refs.clear();
}
/**
* Resets all counters and references
*/
public void reset()
{
if (_refs != null)
_refs.clear();
_classRefs.clear();
_typeRefs = null;
_offset = 0;
_isPacket = false;
if (_os instanceof FlushableOutput)
((FlushableOutput)_os).reset();
}
class BytesOutputStream extends OutputStream {
private int _startOffset;
BytesOutputStream()
throws IOException
{
if (SIZE < _offset + 16) {
Hessian2Output.this.flushBuffer();
}
_startOffset = _offset;
_offset += 3; // skip 'b' xNN xNN
}
@Override
public void write(int ch)
throws IOException
{
if (SIZE <= _offset) {
int length = (_offset - _startOffset) - 3;
_buffer[_startOffset] = (byte) BC_BINARY_CHUNK;
_buffer[_startOffset + 1] = (byte) (length >> 8);
_buffer[_startOffset + 2] = (byte) (length);
Hessian2Output.this.flushBuffer();
_startOffset = _offset;
_offset += 3;
}
_buffer[_offset++] = (byte) ch;
}
@Override
public void write(byte []buffer, int offset, int length)
throws IOException
{
while (length > 0) {
int sublen = SIZE - _offset;
if (length < sublen)
sublen = length;
if (sublen > 0) {
System.arraycopy(buffer, offset, _buffer, _offset, sublen);
_offset += sublen;
}
length -= sublen;
offset += sublen;
if (SIZE <= _offset) {
int chunkLength = (_offset - _startOffset) - 3;
_buffer[_startOffset] = (byte) BC_BINARY_CHUNK;
_buffer[_startOffset + 1] = (byte) (chunkLength >> 8);
_buffer[_startOffset + 2] = (byte) (chunkLength);
Hessian2Output.this.flushBuffer();
_startOffset = _offset;
_offset += 3;
}
}
}
@Override
public void close()
throws IOException
{
int startOffset = _startOffset;
_startOffset = -1;
if (startOffset < 0)
return;
int length = (_offset - startOffset) - 3;
_buffer[startOffset] = (byte) 'B';
_buffer[startOffset + 1] = (byte) (length >> 8);
_buffer[startOffset + 2] = (byte) (length);
Hessian2Output.this.flushBuffer();
}
}
}