content: /*
   * 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 &lt;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;
          }
      }
  }
  