content: /*
   * Copyright (c) 2001-2008 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.*;
  import java.lang.reflect.Field;
  import java.util.ArrayList;
  import java.util.Date;
  import java.util.HashMap;
  import java.util.logging.Level;
  import java.util.logging.Logger;
  
  /**
   * 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 Hessian2Input
    extends AbstractHessianInput
    implements Hessian2Constants
  {
    private static final Logger log
      = Logger.getLogger(Hessian2Input.class.getName());
    
    private static final int END_OF_DATA = -2;
  
    private static Field _detailMessageField;
  
    private static final int SIZE = 1024;
    private static final int GAP = 16;
    
    // standard, unmodified factory for deserializing objects
    protected SerializerFactory _defaultSerializerFactory;
    // factory for deserializing objects in the input stream
    protected SerializerFactory _serializerFactory;
  
    private static boolean _isCloseStreamOnClose;
    
    protected ArrayList<Object> _refs
      = new ArrayList<Object>();
    protected ArrayList<ObjectDefinition> _classDefs
      = new ArrayList<ObjectDefinition>();
    protected ArrayList<String> _types
      = new ArrayList<String>();
    
    // the underlying input stream
    private InputStream _is;
    private final byte []_buffer = new byte[SIZE];
    
    // a peek character
    private int _offset;
    private int _length;
  
    // the method for a call
    private String _method;
    private Throwable _replyFault;
  
    private StringBuilder _sbuf = new StringBuilder();
    
    // true if this is the last chunk
    private boolean _isLastChunk;
    // the chunk length
    private int _chunkLength;
    
    private HessianDebugInputStream _dIs;
    
    public Hessian2Input()
    {
      if (log.isLoggable(Level.FINEST)) {
        _dIs = new HessianDebugInputStream(log, Level.FINEST);
      }
    }
    
    /**
     * Creates a new Hessian input stream, initialized with an
     * underlying input stream.
     *
     * @param is the underlying input stream.
     */
    public Hessian2Input(InputStream is)
    {
      this();
      
      init(is);
    }
  
    /**
     * Sets the serializer factory.
     */
    public void setSerializerFactory(SerializerFactory factory)
    {
      _serializerFactory = factory;
    }
  
    /**
     * Gets the serializer factory.
     */
    public SerializerFactory getSerializerFactory()
    {
      // the default serializer factory cannot be modified by external
      // callers
      if (_serializerFactory == _defaultSerializerFactory) {
        _serializerFactory = new SerializerFactory();
      }
      
      return _serializerFactory;
    }
  
    /**
     * Gets the serializer factory.
     */
    protected final SerializerFactory findSerializerFactory()
    {
      SerializerFactory factory = _serializerFactory;
  
      if (factory == null) {
        factory = SerializerFactory.createDefault();
        _defaultSerializerFactory = factory;
        _serializerFactory = factory;
      }
  
      return factory;
    }
    
    public void allow(String pattern)
    {
      ClassFactory factory = getSerializerFactory().getClassFactory();
      
      factory.allow(pattern);
    }
  
    public void setCloseStreamOnClose(boolean isClose)
    {
      _isCloseStreamOnClose = isClose;
    }
  
    public boolean isCloseStreamOnClose()
    {
      return _isCloseStreamOnClose;
    }
  
    /**
     * Returns the calls method
     */
    public String getMethod()
    {
      return _method;
    }
  
    /**
     * Returns any reply fault.
     */
    public Throwable getReplyFault()
    {
      return _replyFault;
    }
  
    @Override
    public void init(InputStream is)
    {
      if (_dIs != null) {
        _dIs.initPacket(is);
        is = _dIs;
      }
      
      _is = is;
      
      reset();
    }
  
    public void initPacket(InputStream is)
    {
      if (_dIs != null) {
        _dIs.initPacket(is);
        is = _dIs;
      }
      
      _is = is;
      
      resetReferences();
    }
  
    /**
     * 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));
  
      return 0;
    }
  
    /**
     * Starts reading the envelope
     *
     * <pre>
     * E major minor
     * </pre>
     */
    public int readEnvelope()
      throws IOException
    {
      int tag = read();
      int version = 0;
  
      if (tag == 'H') {
        int major = read();
        int minor = read();
  
        version = (major << 16) + minor;
  
        tag = read();
      }
      
      if (tag != 'E')
        throw error("expected hessian Envelope ('E') at " + codeName(tag));
  
      return version;
    }
  
    /**
     * Completes reading the envelope
     *
     * <p>A successful completion will have a single value:
     *
     * <pre>
     * Z
     * </pre>
     */
    public void completeEnvelope()
      throws IOException
    {
      int tag = read();
      
      if (tag != 'Z')
        error("expected end of envelope at " + codeName(tag));
    }
  
    /**
     * Starts reading the call
     *
     * <p>A successful completion will have a single value:
     *
     * <pre>
     * string
     * </pre>
     */
    public String readMethod()
      throws IOException
    {
      _method = readString();
  
      return _method;
    }
  
    /**
     * Returns the number of method arguments
     *
     * <pre>
     * int
     * </pre>
     */
    @Override
    public int readMethodArgLength()
      throws IOException
    {
      return readInt();
    }
  
    /**
     * 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();
  
      readMethod();
    }
  
    public Object []readArguments()
      throws IOException
    {
      int len = readInt();
  
      Object []args = new Object[len];
  
      for (int i = 0; i < len; i++)
        args[i] = readObject();
  
      return args;
    }
  
    /**
     * Completes reading the call
     *
     * <p>A successful completion will have a single value:
     *
     * <pre>
     * </pre>
     */
    public void completeCall()
      throws IOException
    {
    }
  
    /**
     * Reads a reply as an object.
     * If the reply has a fault, throws the exception.
     */
    @Override
    public Object readReply(Class expectedClass)
      throws Throwable
    {
      int tag = read();
  
      if (tag == 'R')
        return readObject(expectedClass);
      else if (tag == 'F') {
        HashMap map = (HashMap) readObject(HashMap.class);
  
        throw prepareFault(map);
      }
      else {
        StringBuilder sb = new StringBuilder();
        sb.append((char) tag);
        
        try {
          int ch;
  
          while ((ch = read()) >= 0) {
            sb.append((char) ch);
          }
        } catch (IOException e) {
          log.log(Level.FINE, e.toString(), e);
        }
        
        throw error("expected hessian reply at " + codeName(tag) + "\n"
                    + sb);
      }
    }
  
    /**
     * Starts reading the reply
     *
     * <p>A successful completion will have a single value:
     *
     * <pre>
     * r
     * </pre>
     */
    public void startReply()
      throws Throwable
    {
      // XXX: for variable length (?)
      
      readReply(Object.class);
    }
  
    /**
     * Prepares the fault.
     */
    private Throwable prepareFault(HashMap fault)
      throws IOException
    {
      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
    {
    }
  
    /**
     * 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
    {
      return null;
    }
  
    /**
     * Starts reading a packet
     *
     * <pre>
     * p major minor
     * </pre>
     */
    public int startMessage()
      throws IOException
    {
      int tag = read();
  
      if (tag == 'p') {
      } else if (tag == 'P') {
      } else
        throw error("expected Hessian message ('p') at " + codeName(tag));
  
      int major = read();
      int minor = read();
  
      return (major << 16) + minor;
    }
  
    /**
     * Completes reading the message
     *
     * <p>A successful completion will have a single value:
     *
     * <pre>
     * z
     * </pre>
     */
    public void completeMessage()
      throws IOException
    {
      int tag = read();
      
      if (tag != 'Z')
        error("expected end of message at " + codeName(tag));
    }
  
    /**
     * 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 = _offset < _length ? (_buffer[_offset++] & 0xff) : read();
  
      switch (tag) {
      case 'T': return true;
      case 'F': return false;
  
        // direct integer
      case 0x80: case 0x81: case 0x82: case 0x83:
      case 0x84: case 0x85: case 0x86: case 0x87:
      case 0x88: case 0x89: case 0x8a: case 0x8b:
      case 0x8c: case 0x8d: case 0x8e: case 0x8f:
        
      case 0x90: case 0x91: case 0x92: case 0x93:
      case 0x94: case 0x95: case 0x96: case 0x97:
      case 0x98: case 0x99: case 0x9a: case 0x9b:
      case 0x9c: case 0x9d: case 0x9e: case 0x9f:
        
      case 0xa0: case 0xa1: case 0xa2: case 0xa3:
      case 0xa4: case 0xa5: case 0xa6: case 0xa7:
      case 0xa8: case 0xa9: case 0xaa: case 0xab:
      case 0xac: case 0xad: case 0xae: case 0xaf:
  
      case 0xb0: case 0xb1: case 0xb2: case 0xb3:
      case 0xb4: case 0xb5: case 0xb6: case 0xb7:
      case 0xb8: case 0xb9: case 0xba: case 0xbb:
      case 0xbc: case 0xbd: case 0xbe: case 0xbf:
        return tag != BC_INT_ZERO;
  
        // INT_BYTE = 0
      case 0xc8: 
        return read() != 0;
        
        // INT_BYTE != 0
      case 0xc0: case 0xc1: case 0xc2: case 0xc3:
      case 0xc4: case 0xc5: case 0xc6: case 0xc7:
      case 0xc9: case 0xca: case 0xcb:
      case 0xcc: case 0xcd: case 0xce: case 0xcf:
        read();
        return true;
  
        // INT_SHORT = 0
      case 0xd4: 
        return (256 * read() + read()) != 0;
        
        // INT_SHORT != 0
      case 0xd0: case 0xd1: case 0xd2: case 0xd3:
      case 0xd5: case 0xd6: case 0xd7:
        read();
        read();
        return true;
        
      case 'I': return
          parseInt() != 0;
        
      case 0xd8: case 0xd9: case 0xda: case 0xdb:
      case 0xdc: case 0xdd: case 0xde: case 0xdf:
        
      case 0xe0: case 0xe1: case 0xe2: case 0xe3:
      case 0xe4: case 0xe5: case 0xe6: case 0xe7:
      case 0xe8: case 0xe9: case 0xea: case 0xeb:
      case 0xec: case 0xed: case 0xee: case 0xef:
        return tag != BC_LONG_ZERO;
  
        // LONG_BYTE = 0
      case 0xf8: 
        return read() != 0;
        
        // LONG_BYTE != 0
      case 0xf0: case 0xf1: case 0xf2: case 0xf3:
      case 0xf4: case 0xf5: case 0xf6: case 0xf7:
      case 0xf9: case 0xfa: case 0xfb:
      case 0xfc: case 0xfd: case 0xfe: case 0xff:
        read();
        return true;
  
        // INT_SHORT = 0
      case 0x3c: 
        return (256 * read() + read()) != 0;
        
        // INT_SHORT != 0
      case 0x38: case 0x39: case 0x3a: case 0x3b:
      case 0x3d: case 0x3e: case 0x3f:
        read();
        read();
        return true;
  
      case BC_LONG_INT:
        return (0x1000000L * read()
                + 0x10000L * read()
                + 0x100 * read()
                + read()) != 0;
        
      case 'L':
        return parseLong() != 0;
  
      case BC_DOUBLE_ZERO:
        return false;
        
      case BC_DOUBLE_ONE:
        return true;
        
      case BC_DOUBLE_BYTE:
        return read() != 0;
        
      case BC_DOUBLE_SHORT:
        return (0x100 * read() + read()) != 0;
        
      case BC_DOUBLE_MILL:
        {
          int mills = parseInt();
  
          return mills != 0;
        }
        
      case 'D':
        return parseDouble() != 0.0;
        
      case 'N':
        return false;
        
      default:
        throw expect("boolean", tag);
      }
    }
  
    /**
     * 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 final int readInt()
      throws IOException
    {
      //int tag = _offset < _length ? (_buffer[_offset++] & 0xff) : read();
      int tag = read();
  
      switch (tag) {
      case 'N':
        return 0;
        
      case 'F':
        return 0;
        
      case 'T':
        return 1;
  
        // direct integer
      case 0x80: case 0x81: case 0x82: case 0x83:
      case 0x84: case 0x85: case 0x86: case 0x87:
      case 0x88: case 0x89: case 0x8a: case 0x8b:
      case 0x8c: case 0x8d: case 0x8e: case 0x8f:
        
      case 0x90: case 0x91: case 0x92: case 0x93:
      case 0x94: case 0x95: case 0x96: case 0x97:
      case 0x98: case 0x99: case 0x9a: case 0x9b:
      case 0x9c: case 0x9d: case 0x9e: case 0x9f:
        
      case 0xa0: case 0xa1: case 0xa2: case 0xa3:
      case 0xa4: case 0xa5: case 0xa6: case 0xa7:
      case 0xa8: case 0xa9: case 0xaa: case 0xab:
      case 0xac: case 0xad: case 0xae: case 0xaf:
        
      case 0xb0: case 0xb1: case 0xb2: case 0xb3:
      case 0xb4: case 0xb5: case 0xb6: case 0xb7:
      case 0xb8: case 0xb9: case 0xba: case 0xbb:
      case 0xbc: case 0xbd: case 0xbe: case 0xbf:
        return tag - BC_INT_ZERO;
  
        /* byte int */
      case 0xc0: case 0xc1: case 0xc2: case 0xc3:
      case 0xc4: case 0xc5: case 0xc6: case 0xc7:
      case 0xc8: case 0xc9: case 0xca: case 0xcb:
      case 0xcc: case 0xcd: case 0xce: case 0xcf:
        return ((tag - BC_INT_BYTE_ZERO) << 8) + read();
        
        /* short int */
      case 0xd0: case 0xd1: case 0xd2: case 0xd3:
      case 0xd4: case 0xd5: case 0xd6: case 0xd7:
        return ((tag - BC_INT_SHORT_ZERO) << 16) + 256 * read() + read();
  
      case 'I':
      case BC_LONG_INT:
        return ((read() << 24)
                + (read() << 16)
                + (read() << 8)
                + read());
  
        // direct long
      case 0xd8: case 0xd9: case 0xda: case 0xdb:
      case 0xdc: case 0xdd: case 0xde: case 0xdf:
        
      case 0xe0: case 0xe1: case 0xe2: case 0xe3:
      case 0xe4: case 0xe5: case 0xe6: case 0xe7:
      case 0xe8: case 0xe9: case 0xea: case 0xeb:
      case 0xec: case 0xed: case 0xee: case 0xef:
        return tag - BC_LONG_ZERO;
  
        /* byte long */
      case 0xf0: case 0xf1: case 0xf2: case 0xf3:
      case 0xf4: case 0xf5: case 0xf6: case 0xf7:
      case 0xf8: case 0xf9: case 0xfa: case 0xfb:
      case 0xfc: case 0xfd: case 0xfe: case 0xff:
        return ((tag - BC_LONG_BYTE_ZERO) << 8) + read();
        
        /* short long */
      case 0x38: case 0x39: case 0x3a: case 0x3b:
      case 0x3c: case 0x3d: case 0x3e: case 0x3f:
        return ((tag - BC_LONG_SHORT_ZERO) << 16) + 256 * read() + read();
  
      case 'L':
        return (int) parseLong();
  
      case BC_DOUBLE_ZERO:
        return 0;
  
      case BC_DOUBLE_ONE:
        return 1;
  
        //case LONG_BYTE:
      case BC_DOUBLE_BYTE:
        return (byte) (_offset < _length ? _buffer[_offset++] : read());
  
        //case INT_SHORT:
        //case LONG_SHORT:
      case BC_DOUBLE_SHORT:
        return (short) (256 * read() + read());
  
      case BC_DOUBLE_MILL:
        {
          int mills = parseInt();
  
          return (int) (0.001 * mills);
        }
  
      case 'D':
        return (int) parseDouble();
        
      default:
        throw expect("integer", 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 'N':
        return 0;
        
      case 'F':
        return 0;
        
      case 'T':
        return 1;
  
        // direct integer
      case 0x80: case 0x81: case 0x82: case 0x83:
      case 0x84: case 0x85: case 0x86: case 0x87:
      case 0x88: case 0x89: case 0x8a: case 0x8b:
      case 0x8c: case 0x8d: case 0x8e: case 0x8f:
        
      case 0x90: case 0x91: case 0x92: case 0x93:
      case 0x94: case 0x95: case 0x96: case 0x97:
      case 0x98: case 0x99: case 0x9a: case 0x9b:
      case 0x9c: case 0x9d: case 0x9e: case 0x9f:
        
      case 0xa0: case 0xa1: case 0xa2: case 0xa3:
      case 0xa4: case 0xa5: case 0xa6: case 0xa7:
      case 0xa8: case 0xa9: case 0xaa: case 0xab:
      case 0xac: case 0xad: case 0xae: case 0xaf:
        
      case 0xb0: case 0xb1: case 0xb2: case 0xb3:
      case 0xb4: case 0xb5: case 0xb6: case 0xb7:
      case 0xb8: case 0xb9: case 0xba: case 0xbb:
      case 0xbc: case 0xbd: case 0xbe: case 0xbf:
        return tag - BC_INT_ZERO;
  
        /* byte int */
      case 0xc0: case 0xc1: case 0xc2: case 0xc3:
      case 0xc4: case 0xc5: case 0xc6: case 0xc7:
      case 0xc8: case 0xc9: case 0xca: case 0xcb:
      case 0xcc: case 0xcd: case 0xce: case 0xcf:
        return ((tag - BC_INT_BYTE_ZERO) << 8) + read();
        
        /* short int */
      case 0xd0: case 0xd1: case 0xd2: case 0xd3:
      case 0xd4: case 0xd5: case 0xd6: case 0xd7:
        return ((tag - BC_INT_SHORT_ZERO) << 16) + 256 * read() + read();
  
        //case LONG_BYTE:
      case BC_DOUBLE_BYTE:
        return (byte) (_offset < _length ? _buffer[_offset++] : read());
  
        //case INT_SHORT:
        //case LONG_SHORT:
      case BC_DOUBLE_SHORT:
        return (short) (256 * read() + read());
  
      case 'I':
      case BC_LONG_INT:
        return parseInt();
  
        // direct long
      case 0xd8: case 0xd9: case 0xda: case 0xdb:
      case 0xdc: case 0xdd: case 0xde: case 0xdf:
        
      case 0xe0: case 0xe1: case 0xe2: case 0xe3:
      case 0xe4: case 0xe5: case 0xe6: case 0xe7:
      case 0xe8: case 0xe9: case 0xea: case 0xeb:
      case 0xec: case 0xed: case 0xee: case 0xef:
        return tag - BC_LONG_ZERO;
  
        /* byte long */
      case 0xf0: case 0xf1: case 0xf2: case 0xf3:
      case 0xf4: case 0xf5: case 0xf6: case 0xf7:
      case 0xf8: case 0xf9: case 0xfa: case 0xfb:
      case 0xfc: case 0xfd: case 0xfe: case 0xff:
        return ((tag - BC_LONG_BYTE_ZERO) << 8) + read();
        
        /* short long */
      case 0x38: case 0x39: case 0x3a: case 0x3b:
      case 0x3c: case 0x3d: case 0x3e: case 0x3f:
        return ((tag - BC_LONG_SHORT_ZERO) << 16) + 256 * read() + read();
  
      case 'L':
        return parseLong();
  
      case BC_DOUBLE_ZERO:
        return 0;
  
      case BC_DOUBLE_ONE:
        return 1;
  
      case BC_DOUBLE_MILL:
        {
          int mills = parseInt();
  
          return (long) (0.001 * mills);
        }
  
      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 'N':
        return 0;
        
      case 'F':
        return 0;
        
      case 'T':
        return 1;
  
        // direct integer
      case 0x80: case 0x81: case 0x82: case 0x83:
      case 0x84: case 0x85: case 0x86: case 0x87:
      case 0x88: case 0x89: case 0x8a: case 0x8b:
      case 0x8c: case 0x8d: case 0x8e: case 0x8f:
        
      case 0x90: case 0x91: case 0x92: case 0x93:
      case 0x94: case 0x95: case 0x96: case 0x97:
      case 0x98: case 0x99: case 0x9a: case 0x9b:
      case 0x9c: case 0x9d: case 0x9e: case 0x9f:
        
      case 0xa0: case 0xa1: case 0xa2: case 0xa3:
      case 0xa4: case 0xa5: case 0xa6: case 0xa7:
      case 0xa8: case 0xa9: case 0xaa: case 0xab:
      case 0xac: case 0xad: case 0xae: case 0xaf:
        
      case 0xb0: case 0xb1: case 0xb2: case 0xb3:
      case 0xb4: case 0xb5: case 0xb6: case 0xb7:
      case 0xb8: case 0xb9: case 0xba: case 0xbb:
      case 0xbc: case 0xbd: case 0xbe: case 0xbf:
        return tag - 0x90;
  
        /* byte int */
      case 0xc0: case 0xc1: case 0xc2: case 0xc3:
      case 0xc4: case 0xc5: case 0xc6: case 0xc7:
      case 0xc8: case 0xc9: case 0xca: case 0xcb:
      case 0xcc: case 0xcd: case 0xce: case 0xcf:
        return ((tag - BC_INT_BYTE_ZERO) << 8) + read();
        
        /* short int */
      case 0xd0: case 0xd1: case 0xd2: case 0xd3:
      case 0xd4: case 0xd5: case 0xd6: case 0xd7:
        return ((tag - BC_INT_SHORT_ZERO) << 16) + 256 * read() + read();
  
      case 'I':
      case BC_LONG_INT:
        return parseInt();
  
        // direct long
      case 0xd8: case 0xd9: case 0xda: case 0xdb:
      case 0xdc: case 0xdd: case 0xde: case 0xdf:
        
      case 0xe0: case 0xe1: case 0xe2: case 0xe3:
      case 0xe4: case 0xe5: case 0xe6: case 0xe7:
      case 0xe8: case 0xe9: case 0xea: case 0xeb:
      case 0xec: case 0xed: case 0xee: case 0xef:
        return tag - BC_LONG_ZERO;
  
        /* byte long */
      case 0xf0: case 0xf1: case 0xf2: case 0xf3:
      case 0xf4: case 0xf5: case 0xf6: case 0xf7:
      case 0xf8: case 0xf9: case 0xfa: case 0xfb:
      case 0xfc: case 0xfd: case 0xfe: case 0xff:
        return ((tag - BC_LONG_BYTE_ZERO) << 8) + read();
        
        /* short long */
      case 0x38: case 0x39: case 0x3a: case 0x3b:
      case 0x3c: case 0x3d: case 0x3e: case 0x3f:
        return ((tag - BC_LONG_SHORT_ZERO) << 16) + 256 * read() + read();
  
      case 'L':
        return (double) parseLong();
  
      case BC_DOUBLE_ZERO:
        return 0;
  
      case BC_DOUBLE_ONE:
        return 1;
  
      case BC_DOUBLE_BYTE:
        return (byte) (_offset < _length ? _buffer[_offset++] : read());
  
      case BC_DOUBLE_SHORT:
        return (short) (256 * read() + read());
  
      case BC_DOUBLE_MILL:
        {
          int mills = parseInt();
  
          return 0.001 * mills;
        }
        
      case 'D':
        return parseDouble();
        
      default:
        throw expect("double", 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 == BC_DATE) {
        return parseLong();
      }
      else if (tag == BC_DATE_MINUTE) {
        return parseInt() * 60000L;
      }
      else
        throw expect("date", tag);
    }
  
    /**
     * 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 BC_STRING_CHUNK:
        _isLastChunk = tag == 'S';
        _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 expect("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 BC_STRING_CHUNK:
          _isLastChunk = tag == 'S';
          _chunkLength = (read() << 8) + read();
          break;
  
        case 0x00: case 0x01: case 0x02: case 0x03:
        case 0x04: case 0x05: case 0x06: case 0x07:
        case 0x08: case 0x09: case 0x0a: case 0x0b:
        case 0x0c: case 0x0d: case 0x0e: case 0x0f:
  
        case 0x10: case 0x11: case 0x12: case 0x13:
        case 0x14: case 0x15: case 0x16: case 0x17:
        case 0x18: case 0x19: case 0x1a: case 0x1b:
        case 0x1c: case 0x1d: case 0x1e: case 0x1f:
          _isLastChunk = true;
          _chunkLength = tag - 0x00;
          break;
  
        case 0x30: case 0x31: case 0x32: case 0x33:
          _isLastChunk = true;
          _chunkLength = (tag - 0x30) * 256 + read();
          break;
  
        default:
          throw expect("string", 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 BC_STRING_CHUNK:
            _isLastChunk = tag == 'S';
            _chunkLength = (read() << 8) + read();
            break;
  
          case 0x00: case 0x01: case 0x02: case 0x03:
          case 0x04: case 0x05: case 0x06: case 0x07:
          case 0x08: case 0x09: case 0x0a: case 0x0b:
          case 0x0c: case 0x0d: case 0x0e: case 0x0f:
  
          case 0x10: case 0x11: case 0x12: case 0x13:
          case 0x14: case 0x15: case 0x16: case 0x17:
          case 0x18: case 0x19: case 0x1a: case 0x1b:
          case 0x1c: case 0x1d: case 0x1e: case 0x1f:
            _isLastChunk = true;
            _chunkLength = tag - 0x00;
            break;
  
          case 0x30: case 0x31: case 0x32: case 0x33:
            _isLastChunk = true;
            _chunkLength = (tag - 0x30) * 256 + read();
            break;
        
          default:
            throw expect("string", 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 'T':
        return "true";
      case 'F':
        return "false";
  
        // direct integer
      case 0x80: case 0x81: case 0x82: case 0x83:
      case 0x84: case 0x85: case 0x86: case 0x87:
      case 0x88: case 0x89: case 0x8a: case 0x8b:
      case 0x8c: case 0x8d: case 0x8e: case 0x8f:
        
      case 0x90: case 0x91: case 0x92: case 0x93:
      case 0x94: case 0x95: case 0x96: case 0x97:
      case 0x98: case 0x99: case 0x9a: case 0x9b:
      case 0x9c: case 0x9d: case 0x9e: case 0x9f:
        
      case 0xa0: case 0xa1: case 0xa2: case 0xa3:
      case 0xa4: case 0xa5: case 0xa6: case 0xa7:
      case 0xa8: case 0xa9: case 0xaa: case 0xab:
      case 0xac: case 0xad: case 0xae: case 0xaf:
        
      case 0xb0: case 0xb1: case 0xb2: case 0xb3:
      case 0xb4: case 0xb5: case 0xb6: case 0xb7:
      case 0xb8: case 0xb9: case 0xba: case 0xbb:
      case 0xbc: case 0xbd: case 0xbe: case 0xbf:
        return String.valueOf((tag - 0x90));
  
        /* byte int */
      case 0xc0: case 0xc1: case 0xc2: case 0xc3:
      case 0xc4: case 0xc5: case 0xc6: case 0xc7:
      case 0xc8: case 0xc9: case 0xca: case 0xcb:
      case 0xcc: case 0xcd: case 0xce: case 0xcf:
        return String.valueOf(((tag - BC_INT_BYTE_ZERO) << 8) + read());
        
        /* short int */
      case 0xd0: case 0xd1: case 0xd2: case 0xd3:
      case 0xd4: case 0xd5: case 0xd6: case 0xd7:
        return String.valueOf(((tag - BC_INT_SHORT_ZERO) << 16)
                              + 256 * read() + read());
  
      case 'I':
      case BC_LONG_INT:
        return String.valueOf(parseInt());
  
        // direct long
      case 0xd8: case 0xd9: case 0xda: case 0xdb:
      case 0xdc: case 0xdd: case 0xde: case 0xdf:
        
      case 0xe0: case 0xe1: case 0xe2: case 0xe3:
      case 0xe4: case 0xe5: case 0xe6: case 0xe7:
      case 0xe8: case 0xe9: case 0xea: case 0xeb:
      case 0xec: case 0xed: case 0xee: case 0xef:
        return String.valueOf(tag - BC_LONG_ZERO);
  
        /* byte long */
      case 0xf0: case 0xf1: case 0xf2: case 0xf3:
      case 0xf4: case 0xf5: case 0xf6: case 0xf7:
      case 0xf8: case 0xf9: case 0xfa: case 0xfb:
      case 0xfc: case 0xfd: case 0xfe: case 0xff:
        return String.valueOf(((tag - BC_LONG_BYTE_ZERO) << 8) + read());
        
        /* short long */
      case 0x38: case 0x39: case 0x3a: case 0x3b:
      case 0x3c: case 0x3d: case 0x3e: case 0x3f:
        return String.valueOf(((tag - BC_LONG_SHORT_ZERO) << 16)
                              + 256 * read() + read());
  
      case 'L':
        return String.valueOf(parseLong());
  
      case BC_DOUBLE_ZERO:
        return "0.0";
  
      case BC_DOUBLE_ONE:
        return "1.0";
  
      case BC_DOUBLE_BYTE:
        return String.valueOf((byte) (_offset < _length
                                      ? _buffer[_offset++]
                                      : read()));
  
      case BC_DOUBLE_SHORT:
        return String.valueOf(((short) (256 * read() + read())));
  
      case BC_DOUBLE_MILL:
        {
          int mills = parseInt();
  
          return String.valueOf(0.001 * mills);
        }
        
      case 'D':
        return String.valueOf(parseDouble());
  
      case 'S':
      case BC_STRING_CHUNK:
        _isLastChunk = tag == 'S';
        _chunkLength = (read() << 8) + read();
  
        _sbuf.setLength(0);
        int ch;
  
        while ((ch = parseChar()) >= 0)
          _sbuf.append((char) ch);
  
        return _sbuf.toString();
  
        // 0-byte string
      case 0x00: case 0x01: case 0x02: case 0x03:
      case 0x04: case 0x05: case 0x06: case 0x07:
      case 0x08: case 0x09: case 0x0a: case 0x0b:
      case 0x0c: case 0x0d: case 0x0e: case 0x0f:
  
      case 0x10: case 0x11: case 0x12: case 0x13:
      case 0x14: case 0x15: case 0x16: case 0x17:
      case 0x18: case 0x19: case 0x1a: case 0x1b:
      case 0x1c: case 0x1d: case 0x1e: case 0x1f:
        _isLastChunk = true;
        _chunkLength = tag - 0x00;
  
        _sbuf.setLength(0);
  
        while ((ch = parseChar()) >= 0) {
          _sbuf.append((char) ch);
        }
  
        return _sbuf.toString();
  
      case 0x30: case 0x31: case 0x32: case 0x33:
        _isLastChunk = true;
        _chunkLength = (tag - 0x30) * 256 + read();
  
        _sbuf.setLength(0);
  
        while ((ch = parseChar()) >= 0)
          _sbuf.append((char) ch);
  
        return _sbuf.toString();
  
      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 BC_BINARY:
      case BC_BINARY_CHUNK:
        _isLastChunk = tag == BC_BINARY;
        _chunkLength = (read() << 8) + read();
  
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
  
        int data;
        while ((data = parseByte()) >= 0)
          bos.write(data);
  
        return bos.toByteArray();
  
      case 0x20: case 0x21: case 0x22: case 0x23:
      case 0x24: case 0x25: case 0x26: case 0x27:
      case 0x28: case 0x29: case 0x2a: case 0x2b:
      case 0x2c: case 0x2d: case 0x2e: case 0x2f:
        {
          _isLastChunk = true;
          _chunkLength = tag - 0x20;
  
          byte []buffer = new byte[_chunkLength];
  
          int offset = 0;
          while (offset < _chunkLength) {
            int sublen = read(buffer, 0, _chunkLength - offset);
  
            if (sublen <= 0)
              break;
  
            offset += sublen;
          }
  
          return buffer;
        }
        
      case 0x34: case 0x35: case 0x36: case 0x37:
        {
          _isLastChunk = true;
          _chunkLength = (tag - 0x34) * 256 + read();
  
          byte []buffer = new byte[_chunkLength];
  
          int offset = 0;
          while (offset < _chunkLength) {
            int sublen = read(buffer, 0, _chunkLength - offset);
  
            if (sublen <= 0)
              break;
  
            offset += sublen;
          }
  
          return buffer;
        }
        
      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 BC_BINARY_CHUNK:
        {
          _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;
        }
  
      case 0x20: case 0x21: case 0x22: case 0x23:
      case 0x24: case 0x25: case 0x26: case 0x27:
      case 0x28: case 0x29: case 0x2a: case 0x2b:
      case 0x2c: case 0x2d: case 0x2e: case 0x2f:
        {
          _isLastChunk = true;
          _chunkLength = tag - 0x20;
  
          int value = parseByte();
  
          // special code so successive read byte won't
          // be read as a single object.
          if (_chunkLength == 0)
            _chunkLength = END_OF_DATA;
  
          return value;
        }
        
      case 0x34: case 0x35: case 0x36: case 0x37:
        {
          _isLastChunk = true;
          _chunkLength = (tag - 0x34) * 256 + read();
  
          int value = parseByte();
  
          // special code so successive read byte won't
          // be read as a single object.
          if (_chunkLength == 0)
            _chunkLength = END_OF_DATA;
  
          return value;
        }
        
      default:
        throw expect("binary", 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 BC_BINARY_CHUNK:
          _isLastChunk = tag == 'B';
          _chunkLength = (read() << 8) + read();
          break;
  
      case 0x20: case 0x21: case 0x22: case 0x23:
      case 0x24: case 0x25: case 0x26: case 0x27:
      case 0x28: case 0x29: case 0x2a: case 0x2b:
      case 0x2c: case 0x2d: case 0x2e: case 0x2f:
        {
          _isLastChunk = true;
          _chunkLength = tag - 0x20;
          break;
        }
        
      case 0x34: case 0x35: case 0x36: case 0x37:
        {
          _isLastChunk = true;
          _chunkLength = (tag - 0x34) * 256 + read();
          break;
        }
        
        default:
          throw expect("binary", 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 BC_BINARY_CHUNK:
            _isLastChunk = tag == 'B';
            _chunkLength = (read() << 8) + read();
            break;
        
          default:
            throw expect("binary", 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()) {
        _offset--;
        
        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 = _offset < _length ? (_buffer[_offset++] & 0xff) : read();
  
      switch (tag) {
      case 'N':
        return null;
  
      case 'H':
        {
          Deserializer reader = findSerializerFactory().getDeserializer(cl);
  
          return reader.readMap(this);
        }
  
      case 'M':
        {
          String type = readType();
  
          // hessian/3bb3
          if ("".equals(type)) {
            Deserializer reader;
            reader = findSerializerFactory().getDeserializer(cl);
  
            return reader.readMap(this);
          }
          else {
            Deserializer reader;
            reader = findSerializerFactory().getObjectDeserializer(type, cl);
  
            return reader.readMap(this);
          }
        }
  
      case 'C':
        {
          readObjectDefinition(cl);
  
          return readObject(cl);
        }
  
      case 0x60: case 0x61: case 0x62: case 0x63:
      case 0x64: case 0x65: case 0x66: case 0x67:
      case 0x68: case 0x69: case 0x6a: case 0x6b:
      case 0x6c: case 0x6d: case 0x6e: case 0x6f:
        {
          int ref = tag - 0x60;
          int size = _classDefs.size();
  
          if (ref < 0 || size <= ref)
            throw new HessianProtocolException("'" + ref + "' is an unknown class definition");
  
          ObjectDefinition def = _classDefs.get(ref);
  
          return readObjectInstance(cl, def);
        }
  
      case 'O':
        {
          int ref = readInt();
          int size = _classDefs.size();
  
          if (ref < 0 || size <= ref)
            throw new HessianProtocolException("'" + ref + "' is an unknown class definition");
  
          ObjectDefinition def = _classDefs.get(ref);
  
          return readObjectInstance(cl, def);
        }
  
      case BC_LIST_VARIABLE:
        {
          String type = readType();
        
          Deserializer reader;
          reader = findSerializerFactory().getListDeserializer(type, cl);
  
          Object v = reader.readList(this, -1);
  
          return v;
        }
  
      case BC_LIST_FIXED:
        {
          String type = readType();
          int length = readInt();
        
          Deserializer reader;
          reader = findSerializerFactory().getListDeserializer(type, cl);
  
          Object v = reader.readLengthList(this, length);
  
          return v;
        }
  
      case 0x70: case 0x71: case 0x72: case 0x73:
      case 0x74: case 0x75: case 0x76: case 0x77:
        {
          int length = tag - 0x70;
  
          String type = readType();
        
          Deserializer reader;
          reader = findSerializerFactory().getListDeserializer(type, cl);
  
          Object v = reader.readLengthList(this, length);
  
          return v;
        }
  
      case BC_LIST_VARIABLE_UNTYPED:
        {
          Deserializer reader;
          reader = findSerializerFactory().getListDeserializer(null, cl);
  
          Object v = reader.readList(this, -1);
  
          return v;
        }
  
      case BC_LIST_FIXED_UNTYPED:
        {
          int length = readInt();
        
          Deserializer reader;
          reader = findSerializerFactory().getListDeserializer(null, cl);
  
          Object v = reader.readLengthList(this, length);
  
          return v;
        }
  
      case 0x78: case 0x79: case 0x7a: case 0x7b:
      case 0x7c: case 0x7d: case 0x7e: case 0x7f:
        {
          int length = tag - 0x78;
        
          Deserializer reader;
          reader = findSerializerFactory().getListDeserializer(null, cl);
  
          Object v = reader.readLengthList(this, length);
  
          return v;
        }
  
      case BC_REF:
        {
          int ref = readInt();
  
          return _refs.get(ref);
        }
      }
  
      if (tag >= 0)
        _offset--;
  
      // hessian/3b2i vs hessian/3406
      // return readObject();
      Object value = findSerializerFactory().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 = _offset < _length ? (_buffer[_offset++] & 0xff) : read();
  
      switch (tag) {
      case 'N':
        return null;
        
      case 'T':
        return Boolean.valueOf(true);
        
      case 'F':
        return Boolean.valueOf(false);
  
        // direct integer
      case 0x80: case 0x81: case 0x82: case 0x83:
      case 0x84: case 0x85: case 0x86: case 0x87:
      case 0x88: case 0x89: case 0x8a: case 0x8b:
      case 0x8c: case 0x8d: case 0x8e: case 0x8f:
        
      case 0x90: case 0x91: case 0x92: case 0x93:
      case 0x94: case 0x95: case 0x96: case 0x97:
      case 0x98: case 0x99: case 0x9a: case 0x9b:
      case 0x9c: case 0x9d: case 0x9e: case 0x9f:
        
      case 0xa0: case 0xa1: case 0xa2: case 0xa3:
      case 0xa4: case 0xa5: case 0xa6: case 0xa7:
      case 0xa8: case 0xa9: case 0xaa: case 0xab:
      case 0xac: case 0xad: case 0xae: case 0xaf:
        
      case 0xb0: case 0xb1: case 0xb2: case 0xb3:
      case 0xb4: case 0xb5: case 0xb6: case 0xb7:
      case 0xb8: case 0xb9: case 0xba: case 0xbb:
      case 0xbc: case 0xbd: case 0xbe: case 0xbf:
        return Integer.valueOf(tag - BC_INT_ZERO);
  
        /* byte int */
      case 0xc0: case 0xc1: case 0xc2: case 0xc3:
      case 0xc4: case 0xc5: case 0xc6: case 0xc7:
      case 0xc8: case 0xc9: case 0xca: case 0xcb:
      case 0xcc: case 0xcd: case 0xce: case 0xcf:
        return Integer.valueOf(((tag - BC_INT_BYTE_ZERO) << 8) + read());
        
        /* short int */
      case 0xd0: case 0xd1: case 0xd2: case 0xd3:
      case 0xd4: case 0xd5: case 0xd6: case 0xd7:
        return Integer.valueOf(((tag - BC_INT_SHORT_ZERO) << 16)
                               + 256 * read() + read());
        
      case 'I':
        return Integer.valueOf(parseInt());
  
        // direct long
      case 0xd8: case 0xd9: case 0xda: case 0xdb:
      case 0xdc: case 0xdd: case 0xde: case 0xdf:
        
      case 0xe0: case 0xe1: case 0xe2: case 0xe3:
      case 0xe4: case 0xe5: case 0xe6: case 0xe7:
      case 0xe8: case 0xe9: case 0xea: case 0xeb:
      case 0xec: case 0xed: case 0xee: case 0xef:
        return Long.valueOf(tag - BC_LONG_ZERO);
  
        /* byte long */
      case 0xf0: case 0xf1: case 0xf2: case 0xf3:
      case 0xf4: case 0xf5: case 0xf6: case 0xf7:
      case 0xf8: case 0xf9: case 0xfa: case 0xfb:
      case 0xfc: case 0xfd: case 0xfe: case 0xff:
        return Long.valueOf(((tag - BC_LONG_BYTE_ZERO) << 8) + read());
        
        /* short long */
      case 0x38: case 0x39: case 0x3a: case 0x3b:
      case 0x3c: case 0x3d: case 0x3e: case 0x3f:
        return Long.valueOf(((tag - BC_LONG_SHORT_ZERO) << 16) + 256 * read() + read());
        
      case BC_LONG_INT:
        return Long.valueOf(parseInt());
      
      case 'L':
        return Long.valueOf(parseLong());
  
      case BC_DOUBLE_ZERO:
        return Double.valueOf(0);
  
      case BC_DOUBLE_ONE:
        return Double.valueOf(1);
  
      case BC_DOUBLE_BYTE:
        return Double.valueOf((byte) read());
  
      case BC_DOUBLE_SHORT:
        return Double.valueOf((short) (256 * read() + read()));
        
      case BC_DOUBLE_MILL:
        {
          int mills = parseInt();
  
          return Double.valueOf(0.001 * mills);
        }
  
      case 'D':
        return Double.valueOf(parseDouble());
      
      case BC_DATE:
        return new Date(parseLong());
      
      case BC_DATE_MINUTE:
        return new Date(parseInt() * 60000L);
  
      case BC_STRING_CHUNK:
      case 'S':
        {
          _isLastChunk = tag == 'S';
          _chunkLength = (read() << 8) + read();
  
          _sbuf.setLength(0);
          
          parseString(_sbuf);
  
          return _sbuf.toString();
        }
  
      case 0x00: case 0x01: case 0x02: case 0x03:
      case 0x04: case 0x05: case 0x06: case 0x07:
      case 0x08: case 0x09: case 0x0a: case 0x0b:
      case 0x0c: case 0x0d: case 0x0e: case 0x0f:
  
      case 0x10: case 0x11: case 0x12: case 0x13:
      case 0x14: case 0x15: case 0x16: case 0x17:
      case 0x18: case 0x19: case 0x1a: case 0x1b:
      case 0x1c: case 0x1d: case 0x1e: case 0x1f:
        {
          _isLastChunk = true;
          _chunkLength = tag - 0x00;
  
          int data;
          _sbuf.setLength(0);
        
          parseString(_sbuf);
  
          return _sbuf.toString();
        }
  
      case 0x30: case 0x31: case 0x32: case 0x33:
        {
          _isLastChunk = true;
          _chunkLength = (tag - 0x30) * 256 + read();
  
          _sbuf.setLength(0);
  
          parseString(_sbuf);
  
          return _sbuf.toString();
        }
  
      case BC_BINARY_CHUNK:
      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 0x20: case 0x21: case 0x22: case 0x23:
      case 0x24: case 0x25: case 0x26: case 0x27:
      case 0x28: case 0x29: case 0x2a: case 0x2b:
      case 0x2c: case 0x2d: case 0x2e: case 0x2f:
        {
          _isLastChunk = true;
          int len = tag - 0x20;
          _chunkLength = 0;
  
          byte []data = new byte[len];
  
          for (int i = 0; i < len; i++)
            data[i] = (byte) read();
  
          return data;
        }
        
      case 0x34: case 0x35: case 0x36: case 0x37:
        {
          _isLastChunk = true;
          int len = (tag - 0x34) * 256 + read();
          _chunkLength = 0;
  
          byte []buffer = new byte[len];
  
          for (int i = 0; i < len; i++) {
            buffer[i] = (byte) read();
          }
  
          return buffer;
        }
  
      case BC_LIST_VARIABLE:
        {
          // variable length list
          String type = readType();
  
          return findSerializerFactory().readList(this, -1, type);
        }
  
      case BC_LIST_VARIABLE_UNTYPED:
        {
          return findSerializerFactory().readList(this, -1, null);
        }
  
      case BC_LIST_FIXED:
        {
          // fixed length lists
          String type = readType();
          int length = readInt();
  
          Deserializer reader;
          reader = findSerializerFactory().getListDeserializer(type, null);
        
          return reader.readLengthList(this, length);
        }
  
      case BC_LIST_FIXED_UNTYPED:
        {
          // fixed length lists
          int length = readInt();
  
          Deserializer reader;
          reader = findSerializerFactory().getListDeserializer(null, null);
        
          return reader.readLengthList(this, length);
        }
  
        // compact fixed list
      case 0x70: case 0x71: case 0x72: case 0x73:
      case 0x74: case 0x75: case 0x76: case 0x77:
        {
          // fixed length lists
          String type = readType();
          int length = tag - 0x70;
  
          Deserializer reader;
          reader = findSerializerFactory().getListDeserializer(type, null);
        
          return reader.readLengthList(this, length);
        }
  
        // compact fixed untyped list
      case 0x78: case 0x79: case 0x7a: case 0x7b:
      case 0x7c: case 0x7d: case 0x7e: case 0x7f:
        {
          // fixed length lists
          int length = tag - 0x78;
  
          Deserializer reader;
          reader = findSerializerFactory().getListDeserializer(null, null);
        
          return reader.readLengthList(this, length);
        }
  
      case 'H':
        {
          return findSerializerFactory().readMap(this, null);
        }
  
      case 'M':
        {
          String type = readType();
  
          return findSerializerFactory().readMap(this, type);
        }
  
      case 'C':
        {
          readObjectDefinition(null);
  
          return readObject();
        }
  
      case 0x60: case 0x61: case 0x62: case 0x63:
      case 0x64: case 0x65: case 0x66: case 0x67:
      case 0x68: case 0x69: case 0x6a: case 0x6b:
      case 0x6c: case 0x6d: case 0x6e: case 0x6f:
        {
          int ref = tag - 0x60;
  
          if (_classDefs.size() <= ref)
            throw error("No classes defined at reference '"
                        + Integer.toHexString(tag) + "'");
  
          ObjectDefinition def = _classDefs.get(ref);
  
          return readObjectInstance(null, def);
        }
  
      case 'O':
        {
          int ref = readInt();
  
          if (_classDefs.size() <= ref)
            throw error("Illegal object reference #" + ref);
  
          ObjectDefinition def = _classDefs.get(ref);
  
          return readObjectInstance(null, def);
        }
  
      case BC_REF:
        {
          int ref = readInt();
  
          return _refs.get(ref);
        }
  
      default:
        if (tag < 0)
          throw new EOFException("readObject: unexpected end of file");
        else
          throw error("readObject: unknown code " + codeName(tag));
      }
    }
  
    /**
     * Reads an object definition:
     *
     * <pre>
     * O string <int> (string)* <value>*
     * </pre>
     */
    private void readObjectDefinition(Class<?> cl)
      throws IOException
    {
      String type = readString();
      int len = readInt();
  
      SerializerFactory factory = findSerializerFactory();
      
      Deserializer reader = factory.getObjectDeserializer(type, null);
      
      Object []fields = reader.createFields(len);
      String []fieldNames = new String[len];
      
      for (int i = 0; i < len; i++) {
        String name = readString();
        
        fields[i] = reader.createField(name);
        fieldNames[i] = name;
      }
      
      ObjectDefinition def
        = new ObjectDefinition(type, reader, fields, fieldNames);
  
      _classDefs.add(def);
    }
  
    private Object readObjectInstance(Class<?> cl,
                                      ObjectDefinition def)
      throws IOException
    {
      String type = def.getType();
      Deserializer reader = def.getReader();
      Object []fields = def.getFields();
  
      SerializerFactory factory = findSerializerFactory();
      
      if (cl != reader.getType() && cl != null) {
        reader = factory.getObjectDeserializer(type, cl);
        
        return reader.readObject(this, def.getFieldNames());
      }
      else {
        return reader.readObject(this, fields);
      }
    }
  
    /**
     * 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
    {
      int value = parseInt();
      
      return _refs.get(value);
    }
  
    /**
     * 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;
  
      if (_offset < _length)
        code = (_buffer[_offset] & 0xff);
      else {
        code = read();
  
        if (code >= 0)
          _offset--;
      }
  
      return (code < 0 || code == 'Z');
    }
  
    /**
     * Reads the end byte.
     */
    public void readEnd()
      throws IOException
    {
      int code = _offset < _length ? (_buffer[_offset++] & 0xff) : read();
  
      if (code == 'Z')
        return;
      else if (code < 0)
        throw error("unexpected end of file");
      else
        throw error("unknown code:" + codeName(code));
    }
  
    /**
     * Reads the end byte.
     */
    public void readMapEnd()
      throws IOException
    {
      int code = _offset < _length ? (_buffer[_offset++] & 0xff) : 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 = _offset < _length ? (_buffer[_offset++] & 0xff) : read();
  
      if (code != 'Z')
        throw error("expected end of list ('Z') at '" + codeName(code) + "'");
    }
  
    /**
     * Adds a list/map reference.
     */
    @Override
    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.
     */
    @Override
    public void resetReferences()
    {
      _refs.clear();
    }
    
    public void reset()
    {
      resetReferences();
      
      _classDefs.clear();
      _types.clear();
    }
  
    public void resetBuffer()
    {
      int offset = _offset;
      _offset = 0;
      
      int length = _length;
      _length = 0;
  
      if (length > 0 && offset != length)
        throw new IllegalStateException("offset=" + offset + " length=" + length);
    }
  
    public Object readStreamingObject()
      throws IOException
    {
      if (_refs != null)
        _refs.clear();
  
      return readObject();
    }
  
    /**
     * 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>
     * type ::= string
     * type ::= int
     * </pre>
     */
    public String readType()
      throws IOException
    {
      int code = _offset < _length ? (_buffer[_offset++] & 0xff) : read();
      _offset--;
  
      switch (code) {
      case 0x00: case 0x01: case 0x02: case 0x03:
      case 0x04: case 0x05: case 0x06: case 0x07:
      case 0x08: case 0x09: case 0x0a: case 0x0b:
      case 0x0c: case 0x0d: case 0x0e: case 0x0f:
  
      case 0x10: case 0x11: case 0x12: case 0x13:
      case 0x14: case 0x15: case 0x16: case 0x17:
      case 0x18: case 0x19: case 0x1a: case 0x1b:
      case 0x1c: case 0x1d: case 0x1e: case 0x1f:
  
      case 0x30: case 0x31: case 0x32: case 0x33:
      case BC_STRING_CHUNK: case 'S':
        {
          String type = readString();
  
          if (_types == null)
            _types = new ArrayList();
  
          _types.add(type);
  
          return type;
        }
  
      default:
        {
          int ref = readInt();
  
          if (_types.size() <= ref)
            throw new IndexOutOfBoundsException("type ref #" + ref + " is greater than the number of valid types (" + _types.size() + ")");
  
          return (String) _types.get(ref);
        }
      }
    }
  
    /**
     * Parses the length for an array
     *
     * <pre>
     * l b32 b24 b16 b8
     * </pre>
     */
    public int readLength()
      throws IOException
    {
      throw new UnsupportedOperationException();
    }
  
    /**
     * Parses a 32-bit integer value from the stream.
     *
     * <pre>
     * b32 b24 b16 b8
     * </pre>
     */
    private int parseInt()
      throws IOException
    {
      int offset = _offset;
      
      if (offset + 3 < _length) {
        byte []buffer = _buffer;
        
        int b32 = buffer[offset + 0] & 0xff;
        int b24 = buffer[offset + 1] & 0xff;
        int b16 = buffer[offset + 2] & 0xff;
        int b8 = buffer[offset + 3] & 0xff;
  
        _offset = offset + 4;
  
        return (b32 << 24) + (b24 << 16) + (b16 << 8) + b8;
      }
      else {
        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 bits = parseLong();
    
      return Double.longBitsToDouble(bits);
    }
  
    org.w3c.dom.Node parseXML()
      throws IOException
    {
      throw new UnsupportedOperationException();
    }
  
    private void parseString(StringBuilder sbuf)
      throws IOException
    {
      while (true) {
        if (_chunkLength <= 0) {
          if (! parseChunkLength())
            return;
        }
        
        int length = _chunkLength;
        _chunkLength = 0;
        
        while (length-- > 0) {
          sbuf.append((char) parseUTF8Char());
        }
      }
    }
  
    /**
     * Reads a character from the underlying stream.
     */
    private int parseChar()
      throws IOException
    {
      while (_chunkLength <= 0) {
        if (! parseChunkLength())
          return -1;
      }
  
      _chunkLength--;
  
      return parseUTF8Char();
    }
    
    private boolean parseChunkLength()
      throws IOException
    {
      if (_isLastChunk)
        return false;
  
      int code = _offset < _length ? (_buffer[_offset++] & 0xff) : read();
  
      switch (code) {
      case BC_STRING_CHUNK:
        _isLastChunk = false;
  
        _chunkLength = (read() << 8) + read();
        break;
        
      case 'S':
        _isLastChunk = true;
  
        _chunkLength = (read() << 8) + read();
        break;
  
      case 0x00: case 0x01: case 0x02: case 0x03:
      case 0x04: case 0x05: case 0x06: case 0x07:
      case 0x08: case 0x09: case 0x0a: case 0x0b:
      case 0x0c: case 0x0d: case 0x0e: case 0x0f:
  
      case 0x10: case 0x11: case 0x12: case 0x13:
      case 0x14: case 0x15: case 0x16: case 0x17:
      case 0x18: case 0x19: case 0x1a: case 0x1b:
      case 0x1c: case 0x1d: case 0x1e: case 0x1f:
        _isLastChunk = true;
        _chunkLength = code - 0x00;
        break;
  
      case 0x30: case 0x31: case 0x32: case 0x33:
        _isLastChunk = true;
        _chunkLength = (code - 0x30) * 256 + read();
        break;
  
      default:
        throw expect("string", code);
      }
  
      return true;
    }
  
    /**
     * Parses a single UTF8 character.
     */
    private int parseUTF8Char()
      throws IOException
    {
      int ch = _offset < _length ? (_buffer[_offset++] & 0xff) : 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 BC_BINARY_CHUNK:
          _isLastChunk = false;
  
          _chunkLength = (read() << 8) + read();
          break;
          
        case 'B':
          _isLastChunk = true;
  
          _chunkLength = (read() << 8) + read();
          break;
  
        case 0x20: case 0x21: case 0x22: case 0x23:
        case 0x24: case 0x25: case 0x26: case 0x27:
        case 0x28: case 0x29: case 0x2a: case 0x2b:
        case 0x2c: case 0x2d: case 0x2e: case 0x2f:
          _isLastChunk = true;
  
          _chunkLength = code - 0x20;
          break;
  
        case 0x34: case 0x35: case 0x36: case 0x37:
          _isLastChunk = true;
          _chunkLength = (code - 0x34) * 256 + 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 BC_BINARY:
      case BC_BINARY_CHUNK:
        _isLastChunk = tag == BC_BINARY;
        _chunkLength = (read() << 8) + read();
        break;
  
      case 0x20: case 0x21: case 0x22: case 0x23:
      case 0x24: case 0x25: case 0x26: case 0x27:
      case 0x28: case 0x29: case 0x2a: case 0x2b:
      case 0x2c: case 0x2d: case 0x2e: case 0x2f:
        _isLastChunk = true;
        _chunkLength = tag - 0x20;
        break;
  
      case 0x34: case 0x35: case 0x36: case 0x37:
        _isLastChunk = true;
        _chunkLength = (tag - 0x34) * 256 + read();
        break;
        
      default:
        throw expect("binary", tag);
      }
      
      return new ReadInputStream();
    }
    
    /**
     * 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 BC_BINARY_CHUNK:
            _isLastChunk = false;
  
            _chunkLength = (read() << 8) + read();
            break;
          
          case BC_BINARY:
            _isLastChunk = true;
  
            _chunkLength = (read() << 8) + read();
            break;
  
          case 0x20: case 0x21: case 0x22: case 0x23:
          case 0x24: case 0x25: case 0x26: case 0x27:
          case 0x28: case 0x29: case 0x2a: case 0x2b:
          case 0x2c: case 0x2d: case 0x2e: case 0x2f:
            _isLastChunk = true;
            _chunkLength = code - 0x20;
            break;
  
          case 0x34: case 0x35: case 0x36: case 0x37:
            _isLastChunk = true;
            _chunkLength = (code - 0x34) * 256 + read();
            break;
  
          default:
            throw expect("byte[]", code);
          }
        }
  
        int sublen = _chunkLength;
        if (length < sublen)
          sublen = length;
  
        if (_length <= _offset && ! readBuffer())
          return -1;
        
        if (_length - _offset < sublen)
          sublen = _length - _offset;
  
        System.arraycopy(_buffer, _offset, buffer, offset, sublen);
  
        _offset += sublen;
        
        offset += sublen;
        readLength += sublen;
        length -= sublen;
        _chunkLength -= sublen;
      }
  
      return readLength;
    }
  
    /**
     * Normally, shouldn't be called externally, but needed for QA, e.g.
     * ejb/3b01.
     */
    public final int read()
      throws IOException
    {
      if (_length <= _offset && ! readBuffer())
        return -1;
  
      return _buffer[_offset++] & 0xff;
    }
  
    protected void unread()
    {
      if (_offset <= 0)
        throw new IllegalStateException();
      
      _offset--;
    }
  
    private final boolean readBuffer()
      throws IOException
    {
      byte []buffer = _buffer;
      int offset = _offset;
      int length = _length;
      
      if (offset < length) {
        System.arraycopy(buffer, offset, buffer, 0, length - offset);
        offset = length - offset;
      }
      else
        offset = 0;
      
      int len = _is.read(buffer, offset, SIZE - offset);
  
      if (len <= 0) {
        _length = offset;
        _offset = 0;
        
        return offset > 0;
      }
  
      _length = offset + len;
      _offset = 0;
  
      return true;
    }
  
    public Reader getReader()
    {
      return null;
    }
  
    protected IOException expect(String expect, int ch)
      throws IOException
    {
      if (ch < 0)
        return error("expected " + expect + " at end of file");
      else {
        _offset--;
  
        try {
          int offset = _offset;
          String context
            = buildDebugContext(_buffer, 0, _length, offset);
  
          Object obj = readObject();
  
          if (obj != null) {
            return error("expected " + expect
                         + " at 0x" + Integer.toHexString(ch & 0xff)
                         + " " + obj.getClass().getName() + " (" + obj + ")"
                         + "\n  " + context + "");
          }
          else
            return error("expected " + expect
                         + " at 0x" + Integer.toHexString(ch & 0xff) + " null");
        } catch (Exception e) {
          log.log(Level.FINE, e.toString(), e);
  
          return error("expected " + expect
                       + " at 0x" + Integer.toHexString(ch & 0xff));
        }
      }
    }
    
    private String buildDebugContext(byte []buffer, int offset, int length,
                                     int errorOffset)
    {
      StringBuilder sb = new StringBuilder();
      
      sb.append("[");
      for (int i = 0; i < errorOffset; i++) {
        int ch = buffer[offset + i];
        addDebugChar(sb, ch);
      }
      sb.append("] ");
      addDebugChar(sb, buffer[offset + errorOffset]);
      sb.append(" [");
      for (int i = errorOffset + 1; i < length; i++) {
        int ch = buffer[offset + i];
        addDebugChar(sb, ch);
      }
      sb.append("]");
      
      return sb.toString();
    }
    
    private void addDebugChar(StringBuilder sb, int ch)
    {    
      if (ch >= 0x20 && ch < 0x7f) {
        sb.append((char) ch);
      }
      else if (ch == '\n')
        sb.append((char) ch);
      else
        sb.append(String.format("\\x%02x", ch & 0xff));    
    }
  
    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);
    }
    
    public void free()
    {
      reset();
    }
  
    @Override
    public void close()
      throws IOException
    {
      InputStream is = _is;
      _is = null;
  
      if (_isCloseStreamOnClose && is != null)
        is.close();
    }
    
    class ReadInputStream extends InputStream {
      boolean _isClosed = false;
  
      public int read()
        throws IOException
      {
        if (_isClosed)
          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)
          return -1;
  
        int len = Hessian2Input.this.read(buffer, offset, length);
        if (len < 0)
          _isClosed = true;
  
        return len;
      }
  
      public void close()
        throws IOException
      {
        while (read() >= 0) {
        }
      }
    };
  
    final static class ObjectDefinition {
      private final String _type;
      private final Deserializer _reader;
      private final Object []_fields;
      private final String []_fieldNames;
  
      ObjectDefinition(String type,
                       Deserializer reader,
                       Object []fields,
                       String []fieldNames)
      {
        _type = type;
        _reader = reader;
        _fields = fields;
        _fieldNames = fieldNames;
      }
  
      String getType()
      {
        return _type;
      }
  
      Deserializer getReader()
      {
        return _reader;
      }
  
      Object []getFields()
      {
        return _fields;
      }
      
      String []getFieldNames()
      {
        return _fieldNames;
      }
    }
  
    static {
      try {
        _detailMessageField = Throwable.class.getDeclaredField("detailMessage");
        _detailMessageField.setAccessible(true);
      } catch (Throwable e) {
      }
    }
  }
  