/*
* 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.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import java.util.Hashtable;
import java.util.Vector;
import com.vodafone360.people.utils.LogUtils;
import com.vodafone360.people.utils.ResetableBufferedInputStream;
/**
* Input 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>
* MicroHessianInput does not depend on any classes other than in J2ME, so it
* can be extracted independently into a smaller package.
* <p>
* MicroHessianInput is unbuffered, so any client needs to provide its own
* buffering.
*
* <pre>
* InputStream is = ...; // from http connection
* MicroHessianInput in = new MicroHessianInput(is);
* String value;
* in.startReply(); // read reply header
* value = in.readString(); // read string value
* in.completeReply(); // read reply footer
* </pre>
*/
public class MicroHessianInput {
protected DataInputStream is;
/**
* Using a special BufferedInputstream, which is constructed once and then reused.
* Saves a lot of GC time.
*/
protected ResetableBufferedInputStream rbis;
/** Using fast but not thread safe StringBuilder for constructing strings */
private StringBuilder mStringBuilder = new StringBuilder(255);
/**
* Creates a new Hessian input stream, initialized with an underlying input
* stream.
*
* @param is the underlying input stream.
*/
public MicroHessianInput(InputStream is) {
init(is);
}
/**
* Creates an uninitialized Hessian input stream.
*/
public MicroHessianInput() {
}
/**
* Initialize the hessian stream with the underlying input stream.
*/
public void init(InputStream is) {
//use the reusable resetablebufferedinputstream here
if (rbis==null){
// create it only once
rbis=new ResetableBufferedInputStream(is,2048);
}else{
// then reuse it
rbis.reset(is);
}
this.is = new DataInputStream(rbis);
}
/**
* Starts reading the reply
* <p>
* A successful completion will have a single value:
*
* <pre>
* r x01 x00
* </pre>
*/
public void startReply() throws IOException {
int tag = is.read();
if (tag != 'r')
throw protocolException("expected hessian reply");
// remove some bits from the input stream
is.skip(2);
}
/**
* Completes reading the call
* <p>
* A successful completion will have a single value:
*
* <pre>
* z
* </pre>
*/
public void completeReply() throws IOException {
int tag = is.read();
if (tag != 'z')
throw protocolException("expected end of reply");
}
/**
* Reads a boolean
*
* <pre>
* T
* F
* </pre>
*/
public boolean readBoolean() throws IOException {
int tag = is.read();
switch (tag) {
case 'T':
return true;
case 'F':
return false;
default:
throw expect("boolean", tag);
}
}
/**
* Reads an integer
*
* <pre>
* I b32 b24 b16 b8
* </pre>
*/
public int readInt() throws IOException {
int tag = is.read();
return readInt(tag);
}
public int readInt(int tag) throws IOException {
if (tag != 'I')
throw expect("integer", tag);
return is.readInt();
}
/**
* Reads a long
*
* <pre>
* L b64 b56 b48 b40 b32 b24 b16 b8
* </pre>
*/
public long readLong() throws IOException {
int tag = is.read();
return readLong(tag);
}
private long readLong(int tag) throws IOException {
if (tag != 'L')
throw protocolException("expected long");
return is.readLong();
}
/**
* Reads a date.
*
* <pre>
* T b64 b56 b48 b40 b32 b24 b16 b8
* </pre>
*/
public long readUTCDate() throws IOException {
int tag = is.read();
if (tag != 'd')
throw protocolException("expected date");
return is.readLong();
}
/**
* Reads a byte array
*
* @return byte[] array extracted from Hessian stream, NULL if 'N' specified
* in data.
* @throws IOException.
*/
public byte[] readBytes() throws IOException {
int tag = is.read();
return readBytes(tag);
}
private byte[] readBytes(int tag) throws IOException {
if (tag == 'N')
return null;
if (tag != 'B')
throw expect("bytes", tag);
int b16 = is.read();
int b8 = is.read();
int len = (b16 << 8) + b8;
byte[] bytes = new byte[len];
is.read(bytes);
return bytes;
}
/**
* Reads an arbitrary object the input stream.
*/
public Object readObject(Class<?> expectedClass) throws IOException {
int tag = is.read();
switch (tag) {
case 'N':
return null;
case 'T':
return true;
case 'F':
return false;
case 'I': {
return readInt(tag);
}
case 'L': {
return readLong(tag);
}
case 'd': {
return new Date(is.readLong());
}
case 'S':
case 'X': {
int b16 = is.read();
int b8 = is.read();
int len = (b16 << 8) + b8;
return readStringImpl(len);
}
case 'B': {
return readBytes(tag);
}
default:
throw new IOException("unknown code:" + (char)tag);
}
}
public Vector<Object> readVector() throws IOException {
int tag = is.read();
return readVector(tag);
}
private Vector<Object> readVector(int tag) throws IOException {
if (tag == 'N')
return null;
if (tag != 'V')
throw expect("vector", tag);
Vector<Object> v = new Vector<Object>();
Object o = decodeTag();
if (o instanceof End)
return v;
if (o instanceof Type)
o = decodeTag();
if (o instanceof End)
return v;
int len = 0;
if (o instanceof Integer) {
len = ((Integer)o);
o = decodeTag();
}
for (int i = 0; i < len; i++) {
v.addElement(o);
o = decodeTag();
}
return v;
}
public Fault readFault() throws IOException {
decodeTag();
int tag = is.read();
if (tag == 'S') {
return new Fault(readString(tag));
}
return null;
}
public Object decodeTag() throws IOException {
int tag = is.read();
// HessianUtils.printTagValue(tag);
return decodeType(tag);
}
public Object decodeType(int tag) throws IOException {
// LogUtils.logD("HessianDecoder.decodeType() tag["+tag+"]");
switch (tag) {
case 't': // tag
is.skip(2);
Type type = new Type();
return type;
case 'l': // length
int i = 0;
i += (is.read() << 24);
i += (is.read() << 16);
i += (is.read() << 8);
i += is.read();
Integer len = i;
return len;
case 'z': // end
End end = new End();
return end;
case 'N': // null
return null;
case 'r':
// reply startReply should have retrieved this?
return null;
case 'M':
return readHashMap(tag);
case 'V': // array/Vector
return readVector(tag);
case 'T': // boolean true
return true;
case 'F': // boolean false
return false;
case 'I': // integer
return readInt(tag);
case 'L': // read long
return readLong(tag);
case 'd': // UTC date
return null;
case 'S': // String
return readString(tag);
case 'B': // read byte array
return readBytes(tag);
case 'f':
return readFault();
default:
LogUtils.logE("HessianDecoder.decodeType() Unknown type");
return null;
}
}
/**
* Reads a string
*
* <pre>
* S b16 b8 string value
* </pre>
*/
public String readString() throws IOException {
int tag = is.read();
return readString(tag);
}
private String readString(int tag) throws IOException {
if (tag == 'N')
return null;
if (tag != 'S')
throw expect("string", tag);
int b16 = is.read();
int b8 = is.read();
int len = (b16 << 8) + b8;
return readStringImpl(len);
}
/**
* Reads a string from the underlying stream.
*/
private String readStringImpl(int length) throws IOException {
// reset the StringBuilder. Recycling is better than making always a
// new one.
mStringBuilder.setLength(0);
for (int i = 0; i < length; i++) {
int ch = is.read();
if (ch < 0x80)
mStringBuilder.append((char)ch);
else if ((ch & 0xe0) == 0xc0) {
int ch1 = is.read();
int v = ((ch & 0x1f) << 6) + (ch1 & 0x3f);
mStringBuilder.append((char)v);
} else if ((ch & 0xf0) == 0xe0) {
int ch1 = is.read();
int ch2 = is.read();
int v = ((ch & 0x0f) << 12) + ((ch1 & 0x3f) << 6) + (ch2 & 0x3f);
mStringBuilder.append((char)v);
} else if ((ch & 0xff) >= 0xf0 && (ch & 0xff) <= 0xf4) { // UTF-4
final byte[] b = new byte[4];
b[0] = (byte)ch;
b[1] = (byte)is.read();
b[2] = (byte)is.read();
b[3] = (byte)is.read();
mStringBuilder.append(new String(b, "utf-8"));
i++;
} else
throw new IOException("bad utf-8 encoding");
}
return mStringBuilder.substring(0, mStringBuilder.length());
}
public Hashtable<String, Object> readHashMap() throws IOException {
// read map type
int tag = is.read();
return readHashMap(tag);
}
public Hashtable<String, Object> readHashMap(int tag) throws IOException {
// read map type
if (tag == 'N')
return null;
if (tag != 'M')
throw expect("map", tag);
Hashtable<String, Object> ht = new Hashtable<String, Object>();
Object obj = decodeTag();
if (obj instanceof Type) {
// get following object
obj = decodeTag();
}
Object obj1 = null;
while (obj != null && !(obj instanceof End)) // 'z' = list-end
{
obj1 = decodeTag();
ht.put(obj.toString(), obj1);
obj = decodeTag();
}
return ht;
}
protected IOException expect(String expect, int ch) {
if (ch < 0)
return protocolException("expected " + expect + " at end of file");
else
return protocolException("expected " + expect + " at " + (char)ch);
}
protected IOException protocolException(String message) {
return new IOException(message);
}
/**
* Place-holder class for End tag 'z'
*/
private static class End {
}
/**
* Place-holder class for Type tag 't'
*/
private static class Type {
}
/**
* Class holding error string returned during Hessian decoding
*/
public static class Fault {
private String mErrString = null;
private Fault(String eString) {
mErrString = eString;
}
public String errString() {
return mErrString;
}
}
}