/*
* Copyright (c) 2001-2006 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 "Hessian", "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.hessian.micro;
import java.io.*;
import java.util.*;
/**
* Output stream for Hessian requests, compatible with microedition Java. It
* only uses classes and types available to J2ME. In particular, it does not
* have any support for the <double> type.
* <p>
* MicroHessianOutput does not depend on any classes other than in J2ME, so it
* can be extracted independently into a smaller package.
* <p>
* MicroHessianOutput is unbuffered, so any client needs to provide its own
* buffering.
*
* <pre>
* OutputStream os = ...; // from http connection
* MicroHessianOutput out = new MicroHessianOutput(os);
* String value;
* out.startCall("hello"); // start hello call
* out.writeString("arg1"); // write a string argument
* out.completeCall(); // complete the call
* </pre>
*/
public class MicroHessianOutput {
protected OutputStream os;
/** Max Chunk size = 64K. */
public final static int MAX_CHUNK = 65535;
/** The mask to be used to get the lower byte in an int */
private final static int LOW_BYTE_MASK = 0xff;
/**
* Creates a new Hessian output stream, initialized with an underlying
* output stream.
*
* @param os the underlying output stream.
*/
public MicroHessianOutput(OutputStream os) {
init(os);
}
/**
* Creates an uninitialized Hessian output stream.
*/
public MicroHessianOutput() {
}
public void init(OutputStream os) {
this.os = os;
}
/**
* Writes the method call: <code><pre>
* c major minor
* m b16 b8 method-namek
* </pre></code>
*
* @param method the method name to call.
*/
public void startCall(String method) throws IOException {
os.write('c');
os.write(0);
os.write(1);
os.write('m');
int len = method.length();
os.write(len >> 8);
os.write(len);
printString(method, 0, len);
}
/**
* Writes the method call: <code><pre>
* z
* </pre></code>
*/
public void completeCall() throws IOException {
os.write('z');
}
/**
* 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 (value)
os.write('T');
else
os.write('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 {
os.write('I');
os.write(value >> 24);
os.write(value >> 16);
os.write(value >> 8);
os.write(value);
}
/**
* 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 {
os.write('L');
os.write((byte)(value >> 56));
os.write((byte)(value >> 48));
os.write((byte)(value >> 40));
os.write((byte)(value >> 32));
os.write((byte)(value >> 24));
os.write((byte)(value >> 16));
os.write((byte)(value >> 8));
os.write((byte)(value));
}
/**
* Writes a date to the stream. <code><pre>
* T b64 b56 b48 b40 b32 b24 b16 b8
* </pre></code>
*
* @param time the date in milliseconds from the epoch in UTC
*/
public void writeUTCDate(long time) throws IOException {
os.write('d');
os.write((byte)(time >> 56));
os.write((byte)(time >> 48));
os.write((byte)(time >> 40));
os.write((byte)(time >> 32));
os.write((byte)(time >> 24));
os.write((byte)(time >> 16));
os.write((byte)(time >> 8));
os.write((byte)(time));
}
/**
* 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 {
os.write('N');
}
/**
* 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 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.
* @throws IOException if the stream fails.
*/
public void writeString(final String value) throws IOException {
if (value == null) {
os.write('N');
return;
}
final int length = value.length();
final int numChunks = length / MAX_CHUNK;
int i = 0;
int offset = 0;
for ( ; i < numChunks; i++) {
printStringLen(MAX_CHUNK, false);
printString(value, offset, MAX_CHUNK);
offset += MAX_CHUNK;
}
printStringLen(length % MAX_CHUNK, true);
printString(value, offset, (length % MAX_CHUNK));
}
/**
* Prints the string length plus the 'S'/'s' prefix.
*
* @param length the length of the string.
* @param isFinalChunk true for 'S', false for 's'.
* @throws IOException if the stream fails.
*/
protected void printStringLen(final int length, final boolean isFinalChunk) throws IOException {
os.write(isFinalChunk ? 'S' : 's');
os.write(length >> 8 & LOW_BYTE_MASK);
os.write(length & LOW_BYTE_MASK);
}
/**
* 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)
os.write('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 b8 bytes B b16 b8 bytes
* </pre></code> If the value is null, it will be written as <code><pre>
* N
* </pre></code>
*
* @param buffer the string value to write.
* @param offset the first byte of the byte array to write.
* @param length the number of bytes of the byte array to write.
* @throws IOException if the stream fails.
*/
public void writeBytes(final byte[] buffer, final int offset, final int length) throws IOException {
if (buffer == null) {
os.write('N');
return;
}
int i = 0;
final int numChunks = length / MAX_CHUNK;
int chunkOffset = 0;
for ( ; i < numChunks; i++) {
printBytesLen(MAX_CHUNK, false);
os.write(buffer, offset + chunkOffset, MAX_CHUNK);
chunkOffset += MAX_CHUNK;
}
printBytesLen(length % MAX_CHUNK, true);
os.write(buffer, offset + chunkOffset, (length % MAX_CHUNK));
}
/**
* Prints the byte array length plus the 'B'/'b' prefix.
*
* @param length the length of the byte array.
* @param isFinalChunk true for 'B', false for 'b'.
* @throws IOException if the stream fails.
*/
protected void printBytesLen(final int length, final boolean isFinalChunk) throws IOException {
this.os.write(isFinalChunk ? 'B' : 'b');
this.os.write(length >> 8 & LOW_BYTE_MASK);
this.os.write(length & LOW_BYTE_MASK);
}
/**
* Writes a reference. <code><pre>
* R b32 b24 b16 b8
* </pre></code>
*
* @param value the integer value to write.
*/
public void writeRef(int value) throws IOException {
os.write('R');
os.write(value >> 24);
os.write(value >> 16);
os.write(value >> 8);
os.write(value);
}
/**
* Writes a generic object to the output stream.
*/
public void writeObject(Object object) throws IOException {
if (object == null)
writeNull();
else if (object instanceof String)
writeString((String)object);
else if (object instanceof Boolean)
writeBoolean(((Boolean)object).booleanValue());
else if (object instanceof Integer)
writeInt(((Number)object).intValue());
else if (object instanceof Long)
writeLong(((Number)object).longValue());
else if (object instanceof Date)
writeUTCDate(((Date)object).getTime());
else if (object instanceof byte[]) {
byte[] data = (byte[])object;
writeBytes(data, 0, data.length);
} else if (object instanceof Vector<?>) {
Vector<?> vector = (Vector<?>)object;
int size = vector.size();
writeListBegin(size, null);
for (int i = 0; i < size; i++)
writeObject(vector.get(i));
writeListEnd();
} else if (object instanceof Hashtable<?,?>) {
@SuppressWarnings("unchecked")
Hashtable<Object, Object> hashtable = (Hashtable<Object, Object>)object;
writeMapBegin(null);
Enumeration<Object> e = hashtable.keys();
while (e.hasMoreElements()) {
Object key = e.nextElement();
Object value = hashtable.get(key);
writeObject(key);
writeObject(value);
}
writeMapEnd();
} else
writeCustomObject(object);
}
/**
* Applications which override this can do custom serialization.
*
* @param object the object to write.
*/
public void writeCustomObject(Object object) throws IOException {
throw new IOException("unexpected object: " + object);
}
/**
* 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>
* <type>java.util.ArrayList</type>
* <length>3</length>
* <int>1</int>
* <int>2</int>
* <int>3</int>
* </list>
* </pre></code>
*/
public void writeListBegin(int length, String type) throws IOException {
os.write('V');
os.write('t');
printLenString(type);
os.write('l');
os.write(length >> 24);
os.write(length >> 16);
os.write(length >> 8);
os.write(length);
}
/**
* Writes the tail of the list to the stream.
*/
public void writeListEnd() throws IOException {
os.write('z');
}
/**
* 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>
* Mt b16 b8 type (<key> <value>)z
* </pre></code>
*/
public void writeMapBegin(String type) throws IOException {
os.write('M');
os.write('t');
printLenString(type);
}
/**
* Writes the tail of the map to the stream.
*/
public void writeMapEnd() throws IOException {
os.write('z');
}
/**
* Writes a remote object reference to the stream. The type is the type of
* the remote interface. <code><pre>
* 'r' 't' b16 b8 type url
* </pre></code>
*/
public void writeRemote(String type, String url) throws IOException {
os.write('r');
os.write('t');
printLenString(type);
os.write('S');
printLenString(url);
}
/**
* 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 (v == null) {
os.write(0);
os.write(0);
} else {
int len = v.length();
os.write(len >> 8);
os.write(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 offset, int length) throws IOException {
for (int i = 0; i < length; i++) {
char ch = v.charAt(i + offset);
if (ch < 0x80)
os.write(ch);
else if (ch < 0x800) {
os.write(0xc0 + ((ch >> 6) & 0x1f));
os.write(0x80 + (ch & 0x3f));
} else if (ch >= 0xd800 && ch <= 0xdf00) {
final char[] chars = new char[2];
chars[0] = ch;
chars[1] = v.charAt(i + offset + 1);
os.write(String.valueOf(chars).getBytes("utf-8"));
i++;
} else {
os.write(0xe0 + ((ch >> 12) & 0xf));
os.write(0x80 + ((ch >> 6) & 0x3f));
os.write(0x80 + (ch & 0x3f));
}
}
}
}