content: /*
   * 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 "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.io;
  
  import java.io.ByteArrayOutputStream;
  import java.io.IOException;
  import java.io.InputStream;
  import java.io.Reader;
  import java.lang.reflect.Field;
  import java.util.ArrayList;
  import java.util.Date;
  import java.util.HashMap;
  
  /**
   * Input stream for Hessian requests.
   *
   * <p>HessianInput is unbuffered, so any client needs to provide
   * its own buffering.
   *
   * <pre>
   * InputStream is = ...; // from http connection
   * HessianInput in = new HessianInput(is);
   * String value;
   *
   * in.startReply();         // read reply header
   * value = in.readString(); // read string value
   * in.completeReply();      // read reply footer
   * </pre>
   */
  public class HessianInput extends AbstractHessianInput {
    private static int END_OF_DATA = -2;
  
    private static Field _detailMessageField;
    
    // factory for deserializing objects in the input stream
    protected SerializerFactory _serializerFactory;
    
    protected ArrayList _refs;
    
    // the underlying input stream
    private InputStream _is;
    // a peek character
    protected int _peek = -1;
    
    // the method for a call
    private String _method;
  
    private Reader _chunkReader;
    private InputStream _chunkInputStream;
  
    private Throwable _replyFault;
  
    private StringBuffer _sbuf = new StringBuffer();
    
    // true if this is the last chunk
    private boolean _isLastChunk;
    // the chunk length
    private int _chunkLength;
  
    /**
     * Creates an uninitialized Hessian input stream.
     */
    public HessianInput()
    {
    }
    
    /**
     * Creates a new Hessian input stream, initialized with an
     * underlying input stream.
     *
     * @param is the underlying input stream.
     */
    public HessianInput(InputStream is)
    {
      init(is);
    }
  
    /**
     * Sets the serializer factory.
     */
    public void setSerializerFactory(SerializerFactory factory)
    {
      _serializerFactory = factory;
    }
  
    /**
     * Gets the serializer factory.
     */
    public SerializerFactory getSerializerFactory()
    {
      return _serializerFactory;
    }
  
    /**
     * Initialize the hessian stream with the underlying input stream.
     */
    public void init(InputStream is)
    {
      _is = is;
      _method = null;
      _isLastChunk = true;
      _chunkLength = 0;
      _peek = -1;
      _refs = null;
      _replyFault = null;
  
      if (_serializerFactory == null)
        _serializerFactory = new SerializerFactory();
    }
  
    /**
     * Returns the calls method
     */
    public String getMethod()
    {
      return _method;
    }
  
    /**
     * Returns any reply fault.
     */
    public Throwable getReplyFault()
    {
      return _replyFault;
    }
  
    /**
     * Starts reading the call
     *
     * <pre>
     * c major minor
     * </pre>
     */
    public int readCall()
      throws IOException
    {
      int tag = read();
      
      if (tag != 'c')
        throw error("expected hessian call ('c') at " + codeName(tag));
  
      int major = read();
      int minor = read();
  
      return (major << 16) + minor;
    }
  
    /**
     * For backward compatibility with HessianSkeleton
     */
    public void skipOptionalCall()
      throws IOException
    {
      int tag = read();
  
      if (tag == 'c') {
        read();
        read();
      }
      else
        _peek = tag;
    }
  
    /**
     * Starts reading the call
     *
     * <p>A successful completion will have a single value:
     *
     * <pre>
     * m b16 b8 method
     * </pre>
     */
    public String readMethod()
      throws IOException
    {
      int tag = read();
      
      if (tag != 'm')
        throw error("expected hessian method ('m') at " + codeName(tag));
      int d1 = read();
      int d2 = read();
  
      _isLastChunk = true;
      _chunkLength = d1 * 256 + d2;
      _sbuf.setLength(0);
      int ch;
      while ((ch = parseChar()) >= 0)
        _sbuf.append((char) ch);
      
      _method = _sbuf.toString();
  
      return _method;
    }
  
    /**
     * Starts reading the call, including the headers.
     *
     * <p>The call expects the following protocol data
     *
     * <pre>
     * c major minor
     * m b16 b8 method
     * </pre>
     */
    public void startCall()
      throws IOException
    {
      readCall();
  
      while (readHeader() != null) {
        readObject();
      }
  
      readMethod();
    }
  
    /**
     * Completes reading the call
     *
     * <p>A successful completion will have a single value:
     *
     * <pre>
     * z
     * </pre>
     */
    public void completeCall()
      throws IOException
    {
      int tag = read();
  
      if (tag == 'z') {
      }
      else
        throw error("expected end of call ('z') at " + codeName(tag) + ".  Check method arguments and ensure method overloading is enabled if necessary");
    }
  
    /**
     * Reads a reply as an object.
     * If the reply has a fault, throws the exception.
     */
    public Object readReply(Class expectedClass)
      throws Throwable
    {
      int tag = read();
      
      if (tag != 'r')
        error("expected hessian reply at " + codeName(tag));
  
      int major = read();
      int minor = read();
  
      tag = read();
      if (tag == 'f')
        throw prepareFault();
      else {
        _peek = tag;
      
        Object value = readObject(expectedClass);
  
        completeValueReply();
  
        return value;
      }
    }
  
    /**
     * Starts reading the reply
     *
     * <p>A successful completion will have a single value:
     *
     * <pre>
     * r
     * </pre>
     */
    public void startReply()
      throws Throwable
    {
      int tag = read();
      
      if (tag != 'r')
        error("expected hessian reply at " + codeName(tag));
  
      int major = read();
      int minor = read();
  
      startReplyBody();
    }
  
    public void startReplyBody()
      throws Throwable
    {
      int tag = read();
      
      if (tag == 'f')
        throw prepareFault();
      else
        _peek = tag;
    }
  
    /**
     * Prepares the fault.
     */
    private Throwable prepareFault()
      throws IOException
    {
      HashMap fault = readFault();
  
      Object detail = fault.get("detail");
      String message = (String) fault.get("message");
  
      if (detail instanceof Throwable) {
        _replyFault = (Throwable) detail;
        
        if (message != null && _detailMessageField != null) {
          try {
            _detailMessageField.set(_replyFault, message);
          } catch (Throwable e) {
          }
        }
  
        return _replyFault;
      }
  
      else {
        String code = (String) fault.get("code");
          
        _replyFault = new HessianServiceException(message, code, detail);
  
        return _replyFault;
      }
    }
  
    /**
     * Completes reading the call
     *
     * <p>A successful completion will have a single value:
     *
     * <pre>
     * z
     * </pre>
     */
    public void completeReply()
      throws IOException
    {
      int tag = read();
      
      if (tag != 'z')
        error("expected end of reply at " + codeName(tag));
    }
  
    /**
     * Completes reading the call
     *
     * <p>A successful completion will have a single value:
     *
     * <pre>
     * z
     * </pre>
     */
    public void completeValueReply()
      throws IOException
    {
      int tag = read();
      
      if (tag != 'z')
        error("expected end of reply at " + codeName(tag));
    }
  
    /**
     * Reads a header, returning null if there are no headers.
     *
     * <pre>
     * H b16 b8 value
     * </pre>
     */
    public String readHeader()
      throws IOException
    {
      int tag = read();
  
      if (tag == 'H') {
        _isLastChunk = true;
        _chunkLength = (read() << 8) + read();
  
        _sbuf.setLength(0);
        int ch;
        while ((ch = parseChar()) >= 0)
          _sbuf.append((char) ch);
  
        return _sbuf.toString();
      }
  
      _peek = tag;
  
      return null;
    }
  
    /**
     * Reads a null
     *
     * <pre>
     * N
     * </pre>
     */
    public void readNull()
      throws IOException
    {
      int tag = read();
  
      switch (tag) {
      case 'N': return;
        
      default:
        throw expect("null", tag);
      }
    }
  
    /**
     * Reads a boolean
     *
     * <pre>
     * T
     * F
     * </pre>
     */
    public boolean readBoolean()
      throws IOException
    {
      int tag = read();
  
      switch (tag) {
      case 'T': return true;
      case 'F': return false;
      case 'I': return parseInt() == 0;
      case 'L': return parseLong() == 0;
      case 'D': return parseDouble() == 0.0;
      case 'N': return false;
        
      default:
        throw expect("boolean", tag);
      }
    }
  
    /**
     * Reads a byte
     *
     * <pre>
     * I b32 b24 b16 b8
     * </pre>
     */
    /*
    public byte readByte()
      throws IOException
    {
      return (byte) readInt();
    }
    */
  
    /**
     * Reads a short
     *
     * <pre>
     * I b32 b24 b16 b8
     * </pre>
     */
    public short readShort()
      throws IOException
    {
      return (short) readInt();
    }
  
    /**
     * Reads an integer
     *
     * <pre>
     * I b32 b24 b16 b8
     * </pre>
     */
    public int readInt()
      throws IOException
    {
      int tag = read();
  
      switch (tag) {
      case 'T': return 1;
      case 'F': return 0;
      case 'I': return parseInt();
      case 'L': return (int) parseLong();
      case 'D': return (int) parseDouble();
        
      default:
        throw expect("int", tag);
      }
    }
  
    /**
     * Reads a long
     *
     * <pre>
     * L b64 b56 b48 b40 b32 b24 b16 b8
     * </pre>
     */
    public long readLong()
      throws IOException
    {
      int tag = read();
  
      switch (tag) {
      case 'T': return 1;
      case 'F': return 0;
      case 'I': return parseInt();
      case 'L': return parseLong();
      case 'D': return (long) parseDouble();
        
      default:
        throw expect("long", tag);
      }
    }
  
    /**
     * Reads a float
     *
     * <pre>
     * D b64 b56 b48 b40 b32 b24 b16 b8
     * </pre>
     */
    public float readFloat()
      throws IOException
    {
      return (float) readDouble();
    }
  
    /**
     * Reads a double
     *
     * <pre>
     * D b64 b56 b48 b40 b32 b24 b16 b8
     * </pre>
     */
    public double readDouble()
      throws IOException
    {
      int tag = read();
  
      switch (tag) {
      case 'T': return 1;
      case 'F': return 0;
      case 'I': return parseInt();
      case 'L': return (double) parseLong();
      case 'D': return parseDouble();
        
      default:
        throw expect("long", tag);
      }
    }
  
    /**
     * Reads a date.
     *
     * <pre>
     * T b64 b56 b48 b40 b32 b24 b16 b8
     * </pre>
     */
    public long readUTCDate()
      throws IOException
    {
      int tag = read();
  
      if (tag != 'd')
        throw error("expected date at " + codeName(tag));
  
      long b64 = read();
      long b56 = read();
      long b48 = read();
      long b40 = read();
      long b32 = read();
      long b24 = read();
      long b16 = read();
      long b8 = read();
  
      return ((b64 << 56) +
              (b56 << 48) +
              (b48 << 40) +
              (b40 << 32) +
              (b32 << 24) +
              (b24 << 16) +
              (b16 << 8) +
              b8);
    }
  
    /**
     * Reads a byte from the stream.
     */
    public int readChar()
      throws IOException
    {
      if (_chunkLength > 0) {
        _chunkLength--;
        if (_chunkLength == 0 && _isLastChunk)
          _chunkLength = END_OF_DATA;
  
        int ch = parseUTF8Char();
        return ch;
      }
      else if (_chunkLength == END_OF_DATA) {
        _chunkLength = 0;
        return -1;
      }
      
      int tag = read();
  
      switch (tag) {
      case 'N':
        return -1;
  
      case 'S':
      case 's':
      case 'X':
      case 'x':
        _isLastChunk = tag == 'S' || tag == 'X';
        _chunkLength = (read() << 8) + read();
  
        _chunkLength--;
        int value = parseUTF8Char();
  
        // special code so successive read byte won't
        // be read as a single object.
        if (_chunkLength == 0 && _isLastChunk)
          _chunkLength = END_OF_DATA;
  
        return value;
        
      default:
        throw new IOException("expected 'S' at " + (char) tag);
      }
    }
  
    /**
     * Reads a byte array from the stream.
     */
    public int readString(char []buffer, int offset, int length)
      throws IOException
    {
      int readLength = 0;
  
      if (_chunkLength == END_OF_DATA) {
        _chunkLength = 0;
        return -1;
      }
      else if (_chunkLength == 0) {
        int tag = read();
  
        switch (tag) {
        case 'N':
          return -1;
        
        case 'S':
        case 's':
        case 'X':
        case 'x':
          _isLastChunk = tag == 'S' || tag == 'X';
          _chunkLength = (read() << 8) + read();
          break;
  
        default:
          throw new IOException("expected 'S' at " + (char) tag);
        }
      }
  
      while (length > 0) {
        if (_chunkLength > 0) {
          buffer[offset++] = (char) parseUTF8Char();
          _chunkLength--;
          length--;
          readLength++;
        }
        else if (_isLastChunk) {
          if (readLength == 0)
            return -1;
          else {
            _chunkLength = END_OF_DATA;
            return readLength;
          }
        }
        else {
          int tag = read();
  
          switch (tag) {
          case 'S':
          case 's':
          case 'X':
          case 'x':
            _isLastChunk = tag == 'S' || tag == 'X';
            _chunkLength = (read() << 8) + read();
            break;
        
          default:
            throw new IOException("expected 'S' at " + (char) tag);
          }
        }
      }
      
      if (readLength == 0)
        return -1;
      else if (_chunkLength > 0 || ! _isLastChunk)
        return readLength;
      else {
        _chunkLength = END_OF_DATA;
        return readLength;
      }
    }
  
    /**
     * Reads a string
     *
     * <pre>
     * S b16 b8 string value
     * </pre>
     */
    public String readString()
      throws IOException
    {
      int tag = read();
  
      switch (tag) {
      case 'N':
        return null;
  
      case 'I':
        return String.valueOf(parseInt());
      case 'L':
        return String.valueOf(parseLong());
      case 'D':
        return String.valueOf(parseDouble());
  
      case 'S':
      case 's':
      case 'X':
      case 'x':
        _isLastChunk = tag == 'S' || tag == 'X';
        _chunkLength = (read() << 8) + read();
  
        _sbuf.setLength(0);
        int ch;
  
        while ((ch = parseChar()) >= 0)
          _sbuf.append((char) ch);
  
        return _sbuf.toString();
  
      default:
        throw expect("string", tag);
      }
    }
  
    /**
     * Reads an XML node.
     *
     * <pre>
     * S b16 b8 string value
     * </pre>
     */
    public org.w3c.dom.Node readNode()
      throws IOException
    {
      int tag = read();
  
      switch (tag) {
      case 'N':
        return null;
  
      case 'S':
      case 's':
      case 'X':
      case 'x':
        _isLastChunk = tag == 'S' || tag == 'X';
        _chunkLength = (read() << 8) + read();
  
        throw error("Can't handle string in this context");
  
      default:
        throw expect("string", tag);
      }
    }
  
    /**
     * Reads a byte array
     *
     * <pre>
     * B b16 b8 data value
     * </pre>
     */
    public byte []readBytes()
      throws IOException
    {
      int tag = read();
  
      switch (tag) {
      case 'N':
        return null;
  
      case 'B':
      case 'b':
        _isLastChunk = tag == 'B';
        _chunkLength = (read() << 8) + read();
  
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
  
        int data;
        while ((data = parseByte()) >= 0)
          bos.write(data);
  
        return bos.toByteArray();
        
      default:
        throw expect("bytes", tag);
      }
    }
  
    /**
     * Reads a byte from the stream.
     */
    public int readByte()
      throws IOException
    {
      if (_chunkLength > 0) {
        _chunkLength--;
        if (_chunkLength == 0 && _isLastChunk)
          _chunkLength = END_OF_DATA;
  
        return read();
      }
      else if (_chunkLength == END_OF_DATA) {
        _chunkLength = 0;
        return -1;
      }
      
      int tag = read();
  
      switch (tag) {
      case 'N':
        return -1;
  
      case 'B':
      case 'b':
        _isLastChunk = tag == 'B';
        _chunkLength = (read() << 8) + read();
  
        int value = parseByte();
  
        // special code so successive read byte won't
        // be read as a single object.
        if (_chunkLength == 0 && _isLastChunk)
          _chunkLength = END_OF_DATA;
  
        return value;
        
      default:
        throw new IOException("expected 'B' at " + (char) tag);
      }
    }
  
    /**
     * Reads a byte array from the stream.
     */
    public int readBytes(byte []buffer, int offset, int length)
      throws IOException
    {
      int readLength = 0;
  
      if (_chunkLength == END_OF_DATA) {
        _chunkLength = 0;
        return -1;
      }
      else if (_chunkLength == 0) {
        int tag = read();
  
        switch (tag) {
        case 'N':
          return -1;
        
        case 'B':
        case 'b':
          _isLastChunk = tag == 'B';
          _chunkLength = (read() << 8) + read();
          break;
        
        default:
          throw new IOException("expected 'B' at " + (char) tag);
        }
      }
  
      while (length > 0) {
        if (_chunkLength > 0) {
          buffer[offset++] = (byte) read();
          _chunkLength--;
          length--;
          readLength++;
        }
        else if (_isLastChunk) {
          if (readLength == 0)
            return -1;
          else {
            _chunkLength = END_OF_DATA;
            return readLength;
          }
        }
        else {
          int tag = read();
  
          switch (tag) {
          case 'B':
          case 'b':
            _isLastChunk = tag == 'B';
            _chunkLength = (read() << 8) + read();
            break;
        
          default:
            throw new IOException("expected 'B' at " + (char) tag);
          }
        }
      }
      
      if (readLength == 0)
        return -1;
      else if (_chunkLength > 0 || ! _isLastChunk)
        return readLength;
      else {
        _chunkLength = END_OF_DATA;
        return readLength;
      }
    }
  
    /**
     * Reads a fault.
     */
    private HashMap readFault()
      throws IOException
    {
      HashMap map = new HashMap();
  
      int code = read();
      for (; code > 0 && code != 'z'; code = read()) {
        _peek = code;
        
        Object key = readObject();
        Object value = readObject();
  
        if (key != null && value != null)
          map.put(key, value);
      }
  
      if (code != 'z')
        throw expect("fault", code);
  
      return map;
    }
  
    /**
     * Reads an object from the input stream with an expected type.
     */
    public Object readObject(Class cl)
      throws IOException
    {
      if (cl == null || cl == Object.class)
        return readObject();
      
      int tag = read();
      
      switch (tag) {
      case 'N':
        return null;
  
      case 'M':
      {
        String type = readType();
  
        // hessian/3386
        if ("".equals(type)) {
          Deserializer reader;
          reader = _serializerFactory.getDeserializer(cl);
  
          return reader.readMap(this);
        }
        else {
          Deserializer reader;
          reader = _serializerFactory.getObjectDeserializer(type);
  
          return reader.readMap(this);
        }
      }
  
      case 'V':
      {
        String type = readType();
        int length = readLength();
        
        Deserializer reader;
        reader = _serializerFactory.getObjectDeserializer(type);
        
        if (cl != reader.getType() && cl.isAssignableFrom(reader.getType()))
          return reader.readList(this, length);
  
        reader = _serializerFactory.getDeserializer(cl);
  
        Object v = reader.readList(this, length);
  
        return v;
      }
  
      case 'R':
      {
        int ref = parseInt();
  
        return _refs.get(ref);
      }
  
      case 'r':
      {
        String type = readType();
        String url = readString();
  
        return resolveRemote(type, url);
      }
      }
  
      _peek = tag;
  
      // hessian/332i vs hessian/3406
      //return readObject();
      
      Object value = _serializerFactory.getDeserializer(cl).readObject(this);
  
      return value;
    }
    
    /**
     * Reads an arbitrary object from the input stream when the type
     * is unknown.
     */
    public Object readObject()
      throws IOException
    {
      int tag = read();
  
      switch (tag) {
      case 'N':
        return null;
        
      case 'T':
        return Boolean.valueOf(true);
        
      case 'F':
        return Boolean.valueOf(false);
        
      case 'I':
        return Integer.valueOf(parseInt());
      
      case 'L':
        return Long.valueOf(parseLong());
      
      case 'D':
        return Double.valueOf(parseDouble());
      
      case 'd':
        return new Date(parseLong());
      
      case 'x':
      case 'X': {
        _isLastChunk = tag == 'X';
        _chunkLength = (read() << 8) + read();
  
        return parseXML();
      }
  
      case 's':
      case 'S': {
        _isLastChunk = tag == 'S';
        _chunkLength = (read() << 8) + read();
  
        int data;
        _sbuf.setLength(0);
        
        while ((data = parseChar()) >= 0)
          _sbuf.append((char) data);
  
        return _sbuf.toString();
      }
  
      case 'b':
      case 'B': {
        _isLastChunk = tag == 'B';
        _chunkLength = (read() << 8) + read();
  
        int data;
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        
        while ((data = parseByte()) >= 0)
          bos.write(data);
  
        return bos.toByteArray();
      }
  
      case 'V': {
        String type = readType();
        int length = readLength();
  
        return _serializerFactory.readList(this, length, type);
      }
  
      case 'M': {
        String type = readType();
  
        return _serializerFactory.readMap(this, type);
      }
  
      case 'R': {
        int ref = parseInt();
  
        return _refs.get(ref);
      }
  
      case 'r': {
        String type = readType();
        String url = readString();
  
        return resolveRemote(type, url);
      }
  
      default:
        throw error("unknown code for readObject at " + codeName(tag));
      }
    }
  
    /**
     * Reads a remote object.
     */
    public Object readRemote()
      throws IOException
    {
      String type = readType();
      String url = readString();
  
      return resolveRemote(type, url);
    }
  
    /**
     * Reads a reference.
     */
    public Object readRef()
      throws IOException
    {
      return _refs.get(parseInt());
    }
  
    /**
     * Reads the start of a list.
     */
    public int readListStart()
      throws IOException
    {
      return read();
    }
  
    /**
     * Reads the start of a list.
     */
    public int readMapStart()
      throws IOException
    {
      return read();
    }
  
    /**
     * Returns true if this is the end of a list or a map.
     */
    public boolean isEnd()
      throws IOException
    {
      int code = read();
  
      _peek = code;
  
      return (code < 0 || code == 'z');
    }
  
    /**
     * Reads the end byte.
     */
    public void readEnd()
      throws IOException
    {
      int code = read();
  
      if (code != 'z')
        throw error("unknown code at " + codeName(code));
    }
  
    /**
     * Reads the end byte.
     */
    public void readMapEnd()
      throws IOException
    {
      int code = read();
  
      if (code != 'z')
        throw error("expected end of map ('z') at " + codeName(code));
    }
  
    /**
     * Reads the end byte.
     */
    public void readListEnd()
      throws IOException
    {
      int code = read();
  
      if (code != 'z')
        throw error("expected end of list ('z') at " + codeName(code));
    }
  
    /**
     * Adds a list/map reference.
     */
    public int addRef(Object ref)
    {
      if (_refs == null)
        _refs = new ArrayList();
      
      _refs.add(ref);
  
      return _refs.size() - 1;
    }
  
    /**
     * Adds a list/map reference.
     */
    public void setRef(int i, Object ref)
    {
      _refs.set(i, ref);
    }
  
    /**
     * Resets the references for streaming.
     */
    public void resetReferences()
    {
      if (_refs != null)
        _refs.clear();
    }
  
    /**
     * Resolves a remote object.
     */
    public Object resolveRemote(String type, String url)
      throws IOException
    {
      HessianRemoteResolver resolver = getRemoteResolver();
  
      if (resolver != null)
        return resolver.lookup(type, url);
      else
        return new HessianRemote(type, url);
    }
  
    /**
     * Parses a type from the stream.
     *
     * <pre>
     * t b16 b8
     * </pre>
     */
    public String readType()
      throws IOException
    {
      int code = read();
  
      if (code != 't') {
        _peek = code;
        return "";
      }
  
      _isLastChunk = true;
      _chunkLength = (read() << 8) + read();
  
      _sbuf.setLength(0);
      int ch;
      while ((ch = parseChar()) >= 0)
        _sbuf.append((char) ch);
  
      return _sbuf.toString();
    }
  
    /**
     * Parses the length for an array
     *
     * <pre>
     * l b32 b24 b16 b8
     * </pre>
     */
    public int readLength()
      throws IOException
    {
      int code = read();
  
      if (code != 'l') {
        _peek = code;
        return -1;
      }
  
      return parseInt();
    }
  
    /**
     * Parses a 32-bit integer value from the stream.
     *
     * <pre>
     * b32 b24 b16 b8
     * </pre>
     */
    private int parseInt()
      throws IOException
    {
      int b32 = read();
      int b24 = read();
      int b16 = read();
      int b8 = read();
  
      return (b32 << 24) + (b24 << 16) + (b16 << 8) + b8;
    }
  
    /**
     * Parses a 64-bit long value from the stream.
     *
     * <pre>
     * b64 b56 b48 b40 b32 b24 b16 b8
     * </pre>
     */
    private long parseLong()
      throws IOException
    {
      long b64 = read();
      long b56 = read();
      long b48 = read();
      long b40 = read();
      long b32 = read();
      long b24 = read();
      long b16 = read();
      long b8 = read();
  
      return ((b64 << 56) +
              (b56 << 48) +
              (b48 << 40) +
              (b40 << 32) +
              (b32 << 24) +
              (b24 << 16) +
              (b16 << 8) +
              b8);
    }
    
    /**
     * Parses a 64-bit double value from the stream.
     *
     * <pre>
     * b64 b56 b48 b40 b32 b24 b16 b8
     * </pre>
     */
    private double parseDouble()
      throws IOException
    {
      long b64 = read();
      long b56 = read();
      long b48 = read();
      long b40 = read();
      long b32 = read();
      long b24 = read();
      long b16 = read();
      long b8 = read();
  
      long bits = ((b64 << 56) +
                   (b56 << 48) +
                   (b48 << 40) +
                   (b40 << 32) +
                   (b32 << 24) +
                   (b24 << 16) +
                   (b16 << 8) +
                   b8);
    
      return Double.longBitsToDouble(bits);
    }
  
    org.w3c.dom.Node parseXML()
      throws IOException
    {
      throw new UnsupportedOperationException();
    }
    
    /**
     * Reads a character from the underlying stream.
     */
    private int parseChar()
      throws IOException
    {
      while (_chunkLength <= 0) {
        if (_isLastChunk)
          return -1;
  
        int code = read();
  
        switch (code) {
        case 's':
        case 'x':
          _isLastChunk = false;
  
          _chunkLength = (read() << 8) + read();
          break;
          
        case 'S':
        case 'X':
          _isLastChunk = true;
  
          _chunkLength = (read() << 8) + read();
          break;
  
        default:
          throw expect("string", code);
        }
  
      }
  
      _chunkLength--;
  
      return parseUTF8Char();
    }
  
    /**
     * Parses a single UTF8 character.
     */
    private int parseUTF8Char()
      throws IOException
    {
      int ch = read();
  
      if (ch < 0x80)
        return ch;
      else if ((ch & 0xe0) == 0xc0) {
        int ch1 = read();
        int v = ((ch & 0x1f) << 6) + (ch1 & 0x3f);
  
        return v;
      }
      else if ((ch & 0xf0) == 0xe0) {
        int ch1 = read();
        int ch2 = read();
        int v = ((ch & 0x0f) << 12) + ((ch1 & 0x3f) << 6) + (ch2 & 0x3f);
  
        return v;
      }
      else
        throw error("bad utf-8 encoding at " + codeName(ch));
    }
    
    /**
     * Reads a byte from the underlying stream.
     */
    private int parseByte()
      throws IOException
    {
      while (_chunkLength <= 0) {
        if (_isLastChunk) {
          return -1;
        }
  
        int code = read();
  
        switch (code) {
        case 'b':
          _isLastChunk = false;
  
          _chunkLength = (read() << 8) + read();
          break;
          
        case 'B':
          _isLastChunk = true;
  
          _chunkLength = (read() << 8) + read();
          break;
  
        default:
          throw expect("byte[]", code);
        }
      }
  
      _chunkLength--;
  
      return read();
    }
  
    /**
     * Reads bytes based on an input stream.
     */
    public InputStream readInputStream()
      throws IOException
    {
      int tag = read();
  
      switch (tag) {
      case 'N':
        return null;
  
      case 'B':
      case 'b':
        _isLastChunk = tag == 'B';
        _chunkLength = (read() << 8) + read();
        break;
        
      default:
        throw expect("inputStream", tag);
      }
      
      return new InputStream() {
          boolean _isClosed = false;
  
          public int read()
            throws IOException
          {
            if (_isClosed || _is == null)
              return -1;
  
            int ch = parseByte();
            if (ch < 0)
              _isClosed = true;
  
            return ch;
          }
  
          public int read(byte []buffer, int offset, int length)
            throws IOException
          {
            if (_isClosed || _is == null)
              return -1;
  
            int len = HessianInput.this.read(buffer, offset, length);
            if (len < 0)
              _isClosed = true;
  
            return len;
          }
  
          public void close()
            throws IOException
          {
            while (read() >= 0) {
            }
  
            _isClosed = true;
          }
        };
    }
    
    /**
     * Reads bytes from the underlying stream.
     */
    int read(byte []buffer, int offset, int length)
      throws IOException
    {
      int readLength = 0;
      
      while (length > 0) {
        while (_chunkLength <= 0) {
          if (_isLastChunk)
            return readLength == 0 ? -1 : readLength;
  
          int code = read();
  
          switch (code) {
          case 'b':
            _isLastChunk = false;
  
            _chunkLength = (read() << 8) + read();
            break;
          
          case 'B':
            _isLastChunk = true;
  
            _chunkLength = (read() << 8) + read();
            break;
  
          default:
            throw expect("byte[]", code);
          }
        }
  
        int sublen = _chunkLength;
        if (length < sublen)
          sublen = length;
  
        sublen = _is.read(buffer, offset, sublen);
        offset += sublen;
        readLength += sublen;
        length -= sublen;
        _chunkLength -= sublen;
      }
  
      return readLength;
    }
  
    final int read()
      throws IOException
    {
      if (_peek >= 0) {
        int value = _peek;
        _peek = -1;
        return value;
      }
  
      int ch = _is.read();
        
      return ch;
    }
  
    public void close()
    {
      _is = null;
    }
  
    public Reader getReader()
    {
      return null;
    }
  
    protected IOException expect(String expect, int ch)
    {
      return error("expected " + expect + " at " + codeName(ch));
    }
  
    protected String codeName(int ch)
    {
      if (ch < 0)
        return "end of file";
      else
        return "0x" + Integer.toHexString(ch & 0xff) + " (" + (char) + ch + ")";
    }
    
    protected IOException error(String message)
    {
      if (_method != null)
        return new HessianProtocolException(_method + ": " + message);
      else
        return new HessianProtocolException(message);
    }
  
    static {
      try {
        _detailMessageField = Throwable.class.getDeclaredField("detailMessage");
        _detailMessageField.setAccessible(true);
      } catch (Throwable e) {
      }
    }
  }
  