/*
 * Copyright (c) 2001-2004 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.burlap.client;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Calendar;
import java.util.Date;
import java.util.Hashtable;
import java.util.TimeZone;
import java.util.Vector;

Input stream for Burlap 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.

MicroBurlapInput does not depend on any classes other than in J2ME, so it can be extracted independently into a smaller package.

MicroBurlapInput is unbuffered, so any client needs to provide its own buffering.

InputStream is = ...; // from http connection
MicroBurlapInput in = new MicroBurlapInput(is);
String value;
in.startReply();         // read reply header
value = in.readString(); // read string value
in.completeReply();      // read reply footer
/** * Input stream for Burlap requests, compatible with microedition * Java. It only uses classes and types available to J2ME. In * particular, it does not have any support for the &lt;double> type. * * <p>MicroBurlapInput does not depend on any classes other than * in J2ME, so it can be extracted independently into a smaller package. * * <p>MicroBurlapInput is unbuffered, so any client needs to provide * its own buffering. * * <pre> * InputStream is = ...; // from http connection * MicroBurlapInput in = new MicroBurlapInput(is); * String value; * * in.startReply(); // read reply header * value = in.readString(); // read string value * in.completeReply(); // read reply footer * </pre> */
public class MicroBurlapInput { private static int base64Decode[]; private InputStream is; protected int peek; protected boolean peekTag; protected Date date; protected Calendar utcCalendar; private Calendar localCalendar; protected Vector refs; protected String method; protected StringBuffer sbuf = new StringBuffer(); protected StringBuffer entity = new StringBuffer();
Creates a new Burlap input stream, initialized with an underlying input stream.
Params:
  • is – the underlying input stream.
/** * Creates a new Burlap input stream, initialized with an * underlying input stream. * * @param is the underlying input stream. */
public MicroBurlapInput(InputStream is) { init(is); }
Creates an uninitialized Burlap input stream.
/** * Creates an uninitialized Burlap input stream. */
public MicroBurlapInput() { }
Returns a call's method.
/** * Returns a call's method. */
public String getMethod() { return method; }
Initialize the Burlap input stream with a new underlying stream. Applications can use init(InputStream) to reuse MicroBurlapInput to save garbage collection.
/** * Initialize the Burlap input stream with a new underlying stream. * Applications can use <code>init(InputStream)</code> to reuse * MicroBurlapInput to save garbage collection. */
public void init(InputStream is) { this.is = is; this.refs = null; }
Starts reading the call

A successful completion will have a single value:

<burlap:call>
<method>method</method>
/** * Starts reading the call * * <p>A successful completion will have a single value: * * <pre> * &lt;burlap:call> * &lt;method>method&lt;/method> * </pre> */
public void startCall() throws IOException { expectStartTag("burlap:call"); expectStartTag("method"); method = parseString(); expectEndTag("method"); this.refs = null; }
Completes reading the call.
</burlap:call>
/** * Completes reading the call. * * <pre> * &lt;/burlap:call> * </pre> */
public void completeCall() throws IOException { expectEndTag("burlap:call"); }
Reads a reply as an object. If the reply has a fault, throws the exception.
/** * Reads a reply as an object. * If the reply has a fault, throws the exception. */
public Object readReply(Class expectedClass) throws Exception { if (startReply()) { Object value = readObject(expectedClass); completeReply(); return value; } else { Hashtable fault = readFault(); Object detail = fault.get("detail"); if (detail instanceof Exception) throw (Exception) detail; else { String code = (String) fault.get("code"); String message = (String) fault.get("message"); throw new BurlapServiceException(message, code, detail); } } }
Starts reading the reply.

A successful completion will have a single value. An unsuccessful one will have a fault:

<burlap:reply>
Returns:true if success, false for fault.
/** * Starts reading the reply. * * <p>A successful completion will have a single value. An unsuccessful * one will have a fault: * * <pre> * &lt;burlap:reply> * </pre> * * @return true if success, false for fault. */
public boolean startReply() throws IOException { this.refs = null; expectStartTag("burlap:reply"); if (! parseTag()) throw new BurlapProtocolException("expected <value>"); String tag = sbuf.toString(); if (tag.equals("fault")) { peekTag = true; return false; } else { peekTag = true; return true; } }
Completes reading the reply.
</burlap:reply>
/** * Completes reading the reply. * * <pre> * &lt;/burlap:reply> * </pre> */
public void completeReply() throws IOException { expectEndTag("burlap:reply"); }
Reads a boolean value from the input stream.
/** * Reads a boolean value from the input stream. */
public boolean readBoolean() throws IOException { expectStartTag("boolean"); int value = parseInt(); expectEndTag("boolean"); return value != 0; }
Reads an integer value from the input stream.
/** * Reads an integer value from the input stream. */
public int readInt() throws IOException { expectStartTag("int"); int value = parseInt(); expectEndTag("int"); return value; }
Reads a long value from the input stream.
/** * Reads a long value from the input stream. */
public long readLong() throws IOException { expectStartTag("long"); long value = parseLong(); expectEndTag("long"); return value; }
Reads a date value from the input stream.
/** * Reads a date value from the input stream. */
public long readUTCDate() throws IOException { expectStartTag("date"); if (utcCalendar == null) utcCalendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")); long value = parseDate(utcCalendar); expectEndTag("date"); return value; }
Reads a date value from the input stream.
/** * Reads a date value from the input stream. */
public long readLocalDate() throws IOException { expectStartTag("date"); if (localCalendar == null) localCalendar = Calendar.getInstance(); long value = parseDate(localCalendar); expectEndTag("date"); return value; }
Reads a remote value from the input stream.
/** * Reads a remote value from the input stream. */
public BurlapRemote readRemote() throws IOException { expectStartTag("remote"); String type = readType(); String url = readString(); expectEndTag("remote"); return new BurlapRemote(type, url); }
Reads a string value from the input stream.

The two valid possibilities are either a <null> or a <string>. The string value is encoded in utf-8, and understands the basic XML escapes: "&123;", "<", ">", "'", """.

<null></null>
<string>a utf-8 encoded string</string>
/** * Reads a string value from the input stream. * * <p>The two valid possibilities are either a &lt;null> * or a &lt;string>. The string value is encoded in utf-8, and * understands the basic XML escapes: "&123;", "&lt;", "&gt;", * "&apos;", "&quot;". * * <pre> * &lt;null>&lt;/null> * &lt;string>a utf-8 encoded string&lt;/string> * </pre> */
public String readString() throws IOException { if (! parseTag()) throw new BurlapProtocolException("expected <string>"); String tag = sbuf.toString(); if (tag.equals("null")) { expectEndTag("null"); return null; } else if (tag.equals("string")) { sbuf.setLength(0); parseString(sbuf); String value = sbuf.toString(); expectEndTag("string"); return value; } else throw expectBeginTag("string", tag); }
Reads a byte array from the input stream.

The two valid possibilities are either a <null> or a <base64>.

/** * Reads a byte array from the input stream. * * <p>The two valid possibilities are either a &lt;null> * or a &lt;base64>. */
public byte []readBytes() throws IOException { if (! parseTag()) throw new BurlapProtocolException("expected <base64>"); String tag = sbuf.toString(); if (tag.equals("null")) { expectEndTag("null"); return null; } else if (tag.equals("base64")) { sbuf.setLength(0); byte []value = parseBytes(); expectEndTag("base64"); return value; } else throw expectBeginTag("base64", tag); }
Reads an arbitrary object the input stream.
/** * Reads an arbitrary object the input stream. */
public Object readObject(Class expectedClass) throws IOException { if (! parseTag()) throw new BurlapProtocolException("expected <tag>"); String tag = sbuf.toString(); if (tag.equals("null")) { expectEndTag("null"); return null; } else if (tag.equals("boolean")) { int value = parseInt(); expectEndTag("boolean"); return new Boolean(value != 0); } else if (tag.equals("int")) { int value = parseInt(); expectEndTag("int"); return new Integer(value); } else if (tag.equals("long")) { long value = parseLong(); expectEndTag("long"); return new Long(value); } else if (tag.equals("string")) { sbuf.setLength(0); parseString(sbuf); String value = sbuf.toString(); expectEndTag("string"); return value; } else if (tag.equals("xml")) { sbuf.setLength(0); parseString(sbuf); String value = sbuf.toString(); expectEndTag("xml"); return value; } else if (tag.equals("date")) { if (utcCalendar == null) utcCalendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")); long value = parseDate(utcCalendar); expectEndTag("date"); return new Date(value); } else if (tag.equals("map")) { String type = readType(); return readMap(expectedClass, type); } else if (tag.equals("list")) { String type = readType(); int length = readLength(); return readList(expectedClass, type, length); } else if (tag.equals("ref")) { int value = parseInt(); expectEndTag("ref"); return refs.elementAt(value); } else if (tag.equals("remote")) { String type = readType(); String url = readString(); expectEndTag("remote"); return resolveRemote(type, url); } else return readExtensionObject(expectedClass, tag); }
Reads a type value from the input stream.
<type>a utf-8 encoded string</type>
/** * Reads a type value from the input stream. * * <pre> * &lt;type>a utf-8 encoded string&lt;/type> * </pre> */
public String readType() throws IOException { if (! parseTag()) throw new BurlapProtocolException("expected <type>"); String tag = sbuf.toString(); if (! tag.equals("type")) throw new BurlapProtocolException("expected <type>"); sbuf.setLength(0); parseString(sbuf); String value = sbuf.toString(); expectEndTag("type"); return value; }
Reads a length value from the input stream. If the length isn't specified, returns -1.
<length>integer</length>
/** * Reads a length value from the input stream. If the length isn't * specified, returns -1. * * <pre> * &lt;length>integer&lt;/length> * </pre> */
public int readLength() throws IOException { expectStartTag("length"); int ch = skipWhitespace(); peek = ch; if (ch == '<') { expectEndTag("length"); return -1; } int value = parseInt(); expectEndTag("length"); return value; }
Resolves a remote object.
/** * Resolves a remote object. */
public Object resolveRemote(String type, String url) throws IOException { return new BurlapRemote(type, url); }
Reads a fault.
/** * Reads a fault. */
public Hashtable readFault() throws IOException { expectStartTag("fault"); Hashtable map = new Hashtable(); while (parseTag()) { peekTag = true; Object key = readObject(null); Object value = readObject(null); if (key != null && value != null) map.put(key, value); } if (! sbuf.toString().equals("fault")) throw new BurlapProtocolException("expected </fault>"); return map; }
Reads an object from the input stream.
Params:
  • expectedClass – the calling routine's expected class
  • type – the type from the stream
/** * Reads an object from the input stream. * * @param expectedClass the calling routine's expected class * @param type the type from the stream */
public Object readMap(Class expectedClass, String type) throws IOException { Hashtable map = new Hashtable(); if (refs == null) refs = new Vector(); refs.addElement(map); while (parseTag()) { peekTag = true; Object key = readObject(null); Object value = readObject(null); map.put(key, value); } if (! sbuf.toString().equals("map")) throw new BurlapProtocolException("expected </map>"); return map; }
Reads object unknown to MicroBurlapInput.
/** * Reads object unknown to MicroBurlapInput. */
protected Object readExtensionObject(Class expectedClass, String tag) throws IOException { throw new BurlapProtocolException("unknown object tag <" + tag + ">"); }
Reads a list object from the input stream.
Params:
  • expectedClass – the calling routine's expected class
  • type – the type from the stream
  • length – the expected length, -1 for unspecified length
/** * Reads a list object from the input stream. * * @param expectedClass the calling routine's expected class * @param type the type from the stream * @param length the expected length, -1 for unspecified length */
public Object readList(Class expectedClass, String type, int length) throws IOException { Vector list = new Vector(); if (refs == null) refs = new Vector(); refs.addElement(list); while (parseTag()) { peekTag = true; Object value = readObject(null); list.addElement(value); } if (! sbuf.toString().equals("list")) throw new BurlapProtocolException("expected </list>"); return list; }
Parses an integer value from the stream.
/** * Parses an integer value from the stream. */
protected int parseInt() throws IOException { int sign = 1; int value = 0; int ch = skipWhitespace(); if (ch == '+') ch = read(); else if (ch == '-') { sign = -1; ch = read(); } for (; ch >= '0' && ch <= '9'; ch = read()) value = 10 * value + ch - '0'; peek = ch; return sign * value; }
Parses a long value from the stream.
/** * Parses a long value from the stream. */
protected long parseLong() throws IOException { long sign = 1; long value = 0; int ch = skipWhitespace(); if (ch == '+') ch = read(); else if (ch == '-') { sign = -1; ch = read(); } for (; ch >= '0' && ch <= '9'; ch = read()) { value = 10 * value + ch - '0'; } peek = ch; return sign * value; }
Parses a date value from the stream.
/** * Parses a date value from the stream. */
protected long parseDate(Calendar calendar) throws IOException { int ch = skipWhitespace(); int year = 0; for (int i = 0; i < 4; i++) { if (ch >= '0' && ch <= '9') year = 10 * year + ch - '0'; else throw expectedChar("year", ch); ch = read(); } int month = 0; for (int i = 0; i < 2; i++) { if (ch >= '0' && ch <= '9') month = 10 * month + ch - '0'; else throw expectedChar("month", ch); ch = read(); } int day = 0; for (int i = 0; i < 2; i++) { if (ch >= '0' && ch <= '9') day = 10 * day + ch - '0'; else throw expectedChar("day", ch); ch = read(); } if (ch != 'T') throw expectedChar("`T'", ch); ch = read(); int hour = 0; for (int i = 0; i < 2; i++) { if (ch >= '0' && ch <= '9') hour = 10 * hour + ch - '0'; else throw expectedChar("hour", ch); ch = read(); } int minute = 0; for (int i = 0; i < 2; i++) { if (ch >= '0' && ch <= '9') minute = 10 * minute + ch - '0'; else throw expectedChar("minute", ch); ch = read(); } int second = 0; for (int i = 0; i < 2; i++) { if (ch >= '0' && ch <= '9') second = 10 * second + ch - '0'; else throw expectedChar("second", ch); ch = read(); } for (; ch > 0 && ch != '<'; ch = read()) { } peek = ch; calendar.set(Calendar.YEAR, year); calendar.set(Calendar.MONTH, month - 1); calendar.set(Calendar.DAY_OF_MONTH, day); calendar.set(Calendar.HOUR_OF_DAY, hour); calendar.set(Calendar.MINUTE, minute); calendar.set(Calendar.SECOND, second); calendar.set(Calendar.MILLISECOND, 0); return calendar.getTime().getTime(); }
Parses a string value from the stream. string buffer is used for the result.
/** * Parses a string value from the stream. * string buffer is used for the result. */
protected String parseString() throws IOException { StringBuffer sbuf = new StringBuffer(); return parseString(sbuf).toString(); }
Parses a string value from the stream. The burlap object's string buffer is used for the result.
/** * Parses a string value from the stream. The burlap object's * string buffer is used for the result. */
protected StringBuffer parseString(StringBuffer sbuf) throws IOException { int ch = read(); for (; ch >= 0 && ch != '<'; ch = read()) { if (ch == '&') { ch = read(); if (ch == '#') { ch = read(); if (ch >= '0' && ch <= '9') { int v = 0; for (; ch >= '0' && ch <= '9'; ch = read()) { v = 10 * v + ch - '0'; } sbuf.append((char) v); } } else { StringBuffer entityBuffer = new StringBuffer(); for (; ch >= 'a' && ch <= 'z'; ch = read()) entityBuffer.append((char) ch); String entity = entityBuffer.toString(); if (entity.equals("amp")) sbuf.append('&'); else if (entity.equals("apos")) sbuf.append('\''); else if (entity.equals("quot")) sbuf.append('"'); else if (entity.equals("lt")) sbuf.append('<'); else if (entity.equals("gt")) sbuf.append('>'); else throw new BurlapProtocolException("unknown XML entity &" + entity + "; at `" + (char) ch + "'"); } if (ch != ';') throw expectedChar("';'", ch); } else if (ch < 0x80) sbuf.append((char) ch); else if ((ch & 0xe0) == 0xc0) { int ch1 = read(); int v = ((ch & 0x1f) << 6) + (ch1 & 0x3f); sbuf.append((char) v); } else if ((ch & 0xf0) == 0xe0) { int ch1 = read(); int ch2 = read(); int v = ((ch & 0x0f) << 12) + ((ch1 & 0x3f) << 6) + (ch2 & 0x3f); sbuf.append((char) v); } else throw new BurlapProtocolException("bad utf-8 encoding"); } peek = ch; return sbuf; }
Parses a byte array.
/** * Parses a byte array. */
protected byte []parseBytes() throws IOException { ByteArrayOutputStream bos = new ByteArrayOutputStream(); parseBytes(bos); return bos.toByteArray(); }
Parses a byte array.
/** * Parses a byte array. */
protected ByteArrayOutputStream parseBytes(ByteArrayOutputStream bos) throws IOException { int ch; for (ch = read(); ch >= 0 && ch != '<'; ch = read()) { int b1 = ch; int b2 = read(); int b3 = read(); int b4 = read(); if (b4 != '=') { int chunk = ((base64Decode[b1] << 18) + (base64Decode[b2] << 12) + (base64Decode[b3] << 6) + (base64Decode[b4])); bos.write(chunk >> 16); bos.write(chunk >> 8); bos.write(chunk); } else if (b3 != '=') { int chunk = ((base64Decode[b1] << 12) + (base64Decode[b2] << 6) + (base64Decode[b3])); bos.write(chunk >> 8); bos.write(chunk); } else { int chunk = ((base64Decode[b1] << 6) + (base64Decode[b2])); bos.write(chunk); } } if (ch == '<') peek = ch; return bos; } protected void expectStartTag(String tag) throws IOException { if (! parseTag()) throw new BurlapProtocolException("expected <" + tag + ">"); if (! sbuf.toString().equals(tag)) throw new BurlapProtocolException("expected <" + tag + "> at <" + sbuf + ">"); } protected void expectEndTag(String tag) throws IOException { if (parseTag()) throw new BurlapProtocolException("expected </" + tag + ">"); if (! sbuf.toString().equals(tag)) throw new BurlapProtocolException("expected </" + tag + "> at </" + sbuf + ">"); }
Parses a tag. Returns true if it's a start tag.
/** * Parses a tag. Returns true if it's a start tag. */
protected boolean parseTag() throws IOException { if (peekTag) { peekTag = false; return true; } int ch = skipWhitespace(); boolean isStartTag = true; if (ch != '<') throw expectedChar("'<'", ch); ch = read(); if (ch == '/') { isStartTag = false; ch = is.read(); } if (! isTagChar(ch)) throw expectedChar("tag", ch); sbuf.setLength(0); for (; isTagChar(ch); ch = read()) sbuf.append((char) ch); if (ch != '>') throw expectedChar("'>'", ch); return isStartTag; } protected IOException expectedChar(String expect, int actualChar) { return new BurlapProtocolException("expected " + expect + " at " + (char) actualChar + "'"); } protected IOException expectBeginTag(String expect, String tag) { return new BurlapProtocolException("expected <" + expect + "> at <" + tag + ">"); } private boolean isTagChar(int ch) { return (ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z' || ch >= '0' && ch <= '9' || ch == ':' || ch == '-'); } protected int skipWhitespace() throws IOException { int ch = read(); for (; ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r'; ch = read()) { } return ch; } protected boolean isWhitespace(int ch) throws IOException { return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r'; } protected int read() throws IOException { if (peek > 0) { int value = peek; peek = 0; return value; } return is.read(); } static { base64Decode = new int[256]; for (int i = 'A'; i <= 'Z'; i++) base64Decode[i] = i - 'A'; for (int i = 'a'; i <= 'z'; i++) base64Decode[i] = i - 'a' + 26; for (int i = '0'; i <= '9'; i++) base64Decode[i] = i - '0' + 52; base64Decode['+'] = 62; base64Decode['/'] = 63; } }