package org.jacorb.orb;

/*
 *        JacORB - a free Java ORB
 *
 *   Copyright (C) 1997-2014 Gerald Brose / The JacORB Team.
 *
 *   This library is free software; you can redistribute it and/or
 *   modify it under the terms of the GNU Library General Public
 *   License as published by the Free Software Foundation; either
 *   version 2 of the License, or (at your option) any later version.
 *
 *   This library is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *   Library General Public License for more details.
 *
 *   You should have received a copy of the GNU Library General Public
 *   License along with this library; if not, write to the Free
 *   Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

import java.io.IOException;
import java.io.Serializable;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import org.jacorb.config.Configuration;
import org.jacorb.config.ConfigurationException;
import org.jacorb.orb.giop.GIOPConnection;
import org.jacorb.orb.giop.Messages;
import org.jacorb.orb.typecode.DelegatingTypeCodeReader;
import org.jacorb.orb.typecode.TypeCodeCache;
import org.jacorb.util.ObjectUtil;
import org.jacorb.util.ValueHandler;
import org.omg.CORBA.BAD_PARAM;
import org.omg.CORBA.CompletionStatus;
import org.omg.CORBA.INTERNAL;
import org.omg.CORBA.MARSHAL;
import org.omg.CORBA.NO_IMPLEMENT;
import org.omg.CORBA.TCKind;
import org.omg.CORBA.TypeCodePackage.BadKind;
import org.omg.CORBA.TypeCodePackage.Bounds;
import org.omg.CORBA.portable.IDLEntity;
import org.omg.CORBA.portable.ValueInputStream;
import org.omg.GIOP.MsgType_1_1;
import org.slf4j.Logger;

/**
 * Read CDR encoded data
 *
 * @author Gerald Brose
 */

public class CDRInputStream
    extends org.omg.CORBA_2_3.portable.InputStream
    implements CodeSet.InputBuffer, ValueInputStream
{
    /**
     * <code>encaps_stack</code> is used to saving/restoring
     * encapsulation information.
     */
    private Deque<EncapsInfo> encaps_stack;

    /**
     * This Map is basically a one-entry map pool to be used in
     * read_TypeCode() as the repeatedTCMap.
     */
    private SortedMap repeatedTCMap;

    private Map recursiveTCMap;

    /** indexes to support mark/reset */
    private int marked_pos;
    private int marked_index;
    private int marked_chunk_end_pos;
    private int marked_valueNestingLevel;

    private boolean closed;

    /** configurable properties */
    Logger logger;
    private boolean cometInteropFix;
    private boolean laxBooleanEncoding;
    private boolean nullStringEncoding;

    /* character encoding code sets for char and wchar, default ISO8859_1 */
    private CodeSet codeSet;
    private CodeSet codeSetW;
    protected int giop_minor = 2; // needed to determine size in chars

    /**
     * <code>valueMap</code> maps indices within the buffer
     * (java.lang.Integer) to the values that appear at these indices. Do
     * NOT access this variable directly. It is initialized on demand.
     * Use the method {@link #getValueMap()}
     */
    private Map valueMap;

    /**
     * Index of the current IDL value that is being unmarshalled.
     * This is kept here so that when the value object has been
     * created, the value factory can immediately store it into this
     * stream's valueMap by calling `register_value()'.
     */
    private int currentValueIndex;

    /**
     * <code>repIdMap</code> maps indices within the buffer
     * (java.lang.Integer) to repository ids that appear at these indices.
     * Do NOT access this variable directly. It is initialized on demand.
     * Use the method {@link #getRepIdMap()}
     */
    private Map repIdMap;

    /**
     * <code>codebaseMap</code> maps indices within the buffer
     * (java.lang.Integer) to codebase strings that appear at these indices.
     * Do NOT access this variable directly. It is initialized on demand.
     * Use the method {@link #getCodebaseMap()}
     */
    private Map codebaseMap;

    private boolean littleEndian = false;

    /** indices into the actual buffer */
    protected byte[] buffer = null;
    protected int pos = 0;
    protected int index = 0;

    /** Last value tag read had the chunking bit on */
    private boolean chunkedValue = false;

    /** Nesting level of chunked valuetypes */
    private int valueNestingLevel = 0;

    /** Ending position of the current chunk */
    private int chunk_end_pos = -1;   // -1 means we're not within a chunk

    /**
     * <code>mutator</code> is a pluggable IOR mutator.
     */
    private IORMutator mutator;

    private boolean isMutatorEnabled;

    /**
     * <code>codesetEnabled</code> denotes whether codeset marshalling is enabled.
     */
    private boolean codesetEnabled;

    /**
     * for this stream to be able to return a live object reference, a
     * full ORB (not the Singleton!) must be known. If this stream is
     * used only to demarshal base type data, the Singleton is enough
     */
    private final org.omg.CORBA.ORB orb;

    /**
     * this is the lowest possible value_tag indicating the
     * begin of a valuetype (15.3.4)
     */
    private static final int MAX_BLOCK_SIZE = 0x7fffff00;

    /**
     * fixes RMI/IIOP related interoperability issues with the
     * sun the orb that occured
     * while receiving serialized collections.
     * see <a href="http://lists.spline.inf.fu-berlin.de/mailman/htdig/jacorb-developer/2006-May/008251.html">mailing list</a>
     * for details.
     */
    private boolean sunInteropFix;

    private static final DelegatingTypeCodeReader typeCodeReader = new DelegatingTypeCodeReader();

    private final TypeCodeCache typeCodeCache;

    private int typeCodeNestingLevel = -1;


    private CDRInputStream(org.omg.CORBA.ORB orb)
    {
        super();

        if (orb == null)
        {
            this.orb = org.omg.CORBA.ORBSingleton.init();
        }
        else
        {
            this.orb = orb;
        }

        if (! (this.orb instanceof ORBSingleton))
        {
            throw new BAD_PARAM("don't pass in a non JacORB ORB");
        }

        try
        {
            configure(((ORBSingleton)this.orb).getConfiguration());
        }
        catch( ConfigurationException e )
        {
            throw new INTERNAL("ConfigurationException: " + e);
        }

        typeCodeCache = ((ORBSingleton)this.orb).getTypeCodeCache();
    }


    private CDRInputStream(org.omg.CORBA.ORB orb, byte[] buffer, Object ignored)
    {
        this(orb);

        if (buffer == null)
        {
            throw new IllegalArgumentException();
        }

        this.buffer = buffer;
    }

    public CDRInputStream(byte[] buffer)
    {
        this(null, buffer, null);
    }

    public CDRInputStream(final org.omg.CORBA.ORB orb, final byte[] buf)
    {
        this(orb, buf, null);

        if (orb == null)
        {
            throw new BAD_PARAM("don't pass in a null ORB");
        }
    }

    public CDRInputStream(final org.omg.CORBA.ORB orb,
                          final byte[] buf,
                          final boolean littleEndian )
    {
        this( orb, buf );
        this.littleEndian = littleEndian;
    }

    /**
     * This stream is self-configuring, i.e. configure() is private
     * and only called from the constructors.
     */

    private void configure(Configuration configuration)
        throws ConfigurationException
    {
        logger = configuration.getLogger("org.jacorb.orb.cdr");

        codesetEnabled  = configuration.getAttributeAsBoolean ("jacorb.codeset", true);
        cometInteropFix = configuration.getAttributeAsBoolean ("jacorb.interop.comet",false);
        laxBooleanEncoding = configuration.getAttributeAsBoolean("jacorb.interop.lax_boolean_encoding", false);
        sunInteropFix = configuration.getAttributeAsBoolean("jacorb.interop.sun", false);
        nullStringEncoding = configuration.getAttributeAsBoolean("jacorb.interop.null_string_encoding", false);

        mutator = (IORMutator) configuration.getAttributeAsObject("jacorb.iormutator");
        isMutatorEnabled = (mutator != null);

        codeSet = ((ORBSingleton)orb).getTCSDefault();
        codeSetW = ((ORBSingleton) orb).getTCSWDefault();
    }

    /**
     * Gets the Map that is used to demarshal shared valuetype instances.
     *
     * @return a <code>Map</code> value
     */
    private Map getValueMap()
    {
        if (valueMap == null)
        {
            // Unlike the valueMap in CDROutputStream, this one
            // does need to be an equality-based HashMap.
            valueMap = new HashMap();
        }
        return valueMap;
    }

    /**
     * Gets the Map that is used to implement indirections for RepositoryIDs.
     *
     * @return a <code>Map</code> value
     */
    private Map getRepIdMap()
    {
        if (repIdMap == null)
        {
            repIdMap = new HashMap();
        }
        return repIdMap;
    }


    /**
     * Gets the Map that is used to implement sharing for codebase
     * specifications.
     *
     * @return a <code>Map</code> value
     */
    private Map getCodebaseMap()
    {
        if (codebaseMap == null)
        {
            codebaseMap = new HashMap();
        }
        return codebaseMap;
    }

    public void setGIOPMinor(final  int giop_minor )
    {
        this.giop_minor = giop_minor;
    }

    public int getGIOPMinor()
    {
        return giop_minor;
    }

    @Override
    public void close()
    {
        // Don't need to call super.close as super is noop.

        if( closed )
        {
            return;
        }

        // commented out as this caused test failures.
        // as the buffer has been passed into this CDRInputStream
        // we cannot assume ownership of the buffer here (alphonse).
        // BufferManager.getInstance().returnBuffer(buffer);

        buffer = null;
        encaps_stack = null;
        closed = true;

        if (recursiveTCMap != null)
        {
            recursiveTCMap.clear();
        }
    }

    @Override
    public org.omg.CORBA.ORB orb()
    {
        return orb;
    }

    public void setCodeSet( CodeSet codeSet, CodeSet codeSetWide )
    {
        this.codeSet  = codeSet;
        this.codeSetW = codeSetWide;
    }

    private static final int _read4int
       (final boolean _littleEndian, final byte[] _buffer, final int _pos)
    {
        if (_littleEndian)
        {
            return (((_buffer[_pos+3] & 0xff) << 24) +
                    ((_buffer[_pos+2] & 0xff) << 16) +
                    ((_buffer[_pos+1] & 0xff) <<  8) +
                    ((_buffer[_pos]   & 0xff) <<  0));
        }

        return (((_buffer[_pos]   & 0xff) << 24) +
                ((_buffer[_pos+1] & 0xff) << 16) +
                ((_buffer[_pos+2] & 0xff) <<  8) +
                ((_buffer[_pos+3] & 0xff) <<  0));
    }

    private static final short _read2int
       (final boolean _littleEndian, final byte[] _buffer, final int _pos)
    {
        if (_littleEndian)
        {
            return  (short)(((_buffer[_pos+1] & 0xff) << 8) +
                            ((_buffer[_pos]   & 0xff) << 0));
        }
        return (short)(((_buffer[_pos ]    & 0xff) << 8) +
                ((_buffer[_pos + 1] & 0xff) << 0));
    }

    private final int _read_long()
    {
        int result;

        result = _read4int (littleEndian, buffer, pos);

        index += 4;
        pos += 4;
        return result;
    }

    private final long _read_longlong()
    {
        if (littleEndian)
        {
            return (_read_long() & 0xFFFFFFFFL) + ((long) _read_long() << 32);
        }

        return ((long) _read_long() << 32) + (_read_long() & 0xFFFFFFFFL);
    }

    private final void handle_chunking()
    {
        int remainder = 4 - (index % 4);
        int aligned_pos = (remainder != 4) ? pos + remainder : pos;

        if (chunk_end_pos >= pos && chunk_end_pos <= aligned_pos)
        {
            adjust_positions ();
        }
    }

    private final void adjust_positions()
    {
        chunk_end_pos = -1;
        int saved_pos = pos;
        int saved_index = index;
        int tag = read_long();

        if (tag < 0)
        {
            // tag is an end tag
            if (-tag > valueNestingLevel)
            {
                throw new INTERNAL
                (
                    "received end tag " + tag +
                    " with value nesting level " +
                    valueNestingLevel
                );
            }
            valueNestingLevel = -tag;
            valueNestingLevel--;

            if (valueNestingLevel > 0)
            {
                chunk_end_pos = pos;
                handle_chunking();
            }
        }
        else if (tag > 0 && tag < 0x7fffff00)
        {
            // tag is the chunk size tag of another chunk

            chunk_end_pos = pos + tag;
        }
        else // (tag == 0 || tag >= 0x7fffff00)
        {
            // tag is the null value tag or the value tag of a nested value

            pos = saved_pos;      // "unread" the tag
            index = saved_index;
        }
    }

    public final void skip(final int distance)
    {
        pos += distance;
        index += distance;
    }

    /**
     * close a CDR encapsulation and
     * restore index and byte order information
     */

    public final void closeEncapsulation()
    {
        if (encaps_stack == null)
        {
            throw new MARSHAL( "Internal Error - closeEncapsulation failed" );
        }

        EncapsInfo ei = encaps_stack.removeFirst();
        littleEndian = ei.littleEndian;
        int size = ei.size;
        int start = ei.start;

        if( pos < start + size )
        {
            pos = start + size;
        }

        index = ei.index + size;
    }

    /**
     * open a CDR encapsulation and
     * restore index and byte order information
     */

    public final int openEncapsulation()
    {
        boolean old_endian = littleEndian;
        int size = read_long();

        // Check if size looks sane. If not try changing byte order.
        // This is a specific fix for interoperability with the Iona
        // Comet COM/CORBA bridge that has problems with size marshalling.

        if (cometInteropFix && ((size < 0) || (size > buffer.length)))
        {
            int temp =
            (
                ((size >> 24) & 0x000000FF) +
                ((size >> 8)  & 0x0000FF00) +
                ((size << 8)  & 0x00FF0000) +
                ((size << 24) & 0xFF000000)
            );

            if (logger.isDebugEnabled())
            {
                logger.debug("Size of CDR encapsulation larger than buffer, swapping byte order. " +
                             "Size of CDR encapsulation was " + size + ", is now " + temp);
            }

            size = temp;
        }
        /* save current index plus size of the encapsulation on the stack.
           When the encapsulation is closed, this value will be restored as
           index */

        if (encaps_stack == null)
        {
            encaps_stack = new ArrayDeque<EncapsInfo>();
        }
        encaps_stack.addFirst(new EncapsInfo(old_endian, index, pos, size ));

        openEncapsulatedArray();

        return size;
    }

    public final void openEncapsulatedArray()
    {
        /* reset index  to zero, i.e. align relative  to the beginning
           of the encaps. */
        resetIndex();
        littleEndian = read_boolean();
    }


    /*
     * Return a copy of the current buffer.
     *
     * @return a <code>byte[]</code> value.
     */
    public byte[] getBufferCopy()
    {
        byte[] result = new byte[buffer.length];
        System.arraycopy
        (
            buffer,
            0,
            result,
            0,
            buffer.length
        );
        return result;
    }

    /**
     * Reads the next byte of data from the input stream. The value byte is
     * returned as an <code>int</code> in the range <code>0</code> to
     * <code>255</code>. If no byte is available because the end of the stream
     * has been reached, the value <code>-1</code> is returned.
     * @return the next byte of data, or <code>-1</code> if the end of the
     *         stream is reached.
     * @throws java.io.IOException if stream is closed.
     */
    @Override
    public int read()
        throws java.io.IOException
    {
        if( closed )
        {
            throw new java.io.IOException("Stream already closed!");
        }

        if( available() < 1 )
        {
            return -1;
        }

        ++index;
        return buffer[pos++]; // read_index++];
    }

    /**
     * @return the number of bytes that can be read (or skipped over) from this
     *         input stream.  This is not necessarily the number of 'valid' bytes.
     */
    @Override
    public int available()
    {
        return buffer.length - index;
    }

    /**
     * Has the effect of read(b, 0, b.length);
     * @see #read(byte[], int, int)
     */
    @Override
    public int read(final byte[] b)
        throws java.io.IOException
    {
        return read(b, 0, b.length);
    }

    /**
     * Performs as described by <code>java.io.InputStream.read(byte[], int, int)</code>,
     * but never blocks.
     */
    @Override
    public int read(final byte[] b, final int off, final int len)
        throws java.io.IOException
    {
        if( b == null )
        {
            throw new java.io.IOException("buffer may not be null");
        }

        if( off < 0 ||
            len < 0 ||
            off + len > b.length )
        {
            throw new java.io.IOException("buffer index out of bounds");
        }

        if( len == 0 )
        {
            return 0;
        }

        if( available() < 1 )
        {
            return -1;
        }

        if( closed )
        {
            throw new java.io.IOException("Stream already closed!");
        }

        int min = Math.min(len, available());
        System.arraycopy(buffer, index, b, off, min );
        pos += min;
        index += min;
        return min;
    }


    @Override
    public final org.omg.CORBA.Any read_any()
    {
        org.omg.CORBA.TypeCode _tc = read_TypeCode();
        org.omg.CORBA.Any any = orb.create_any();
        any.read_value( this, _tc );
        return any;
    }

    @Override
    public final boolean read_boolean()
    {
        if (this.chunk_end_pos == this.pos)
        {
            this.adjust_positions();
        }

        index++;
        byte value = buffer[pos++];

        return parseBoolean(value);
    }

    /** arrays */

    @Override
    public final void read_boolean_array
       (final boolean[] value, final int offset, final int length)
    {
        handle_chunking();

        if (length == 0)
        {
            return;
        }

        final int until = offset + length;

        int j = offset;
        do
        {
            final byte bb = buffer[pos++];
            value[j] = parseBoolean(bb);
            ++j;
        }
        while(j < until);

        index += length;
    }

    private final boolean parseBoolean(final byte value)
    {
        if (value == 0)
        {
            return false;
        }
        else
        {
            if (value == 1)
            {
                return true;
            }
            else if (laxBooleanEncoding)
            {
                // Technically only valid values are 0 (false) and 1 (true)
                // however some ORBs send values other than 1 for true.
                return true;
            }
            else
            {
                throw new MARSHAL ("Unexpected boolean value: " + value
                        + " pos: " + pos);
            }
        }
    }


    /**
     * <code>read_char</code> reads a character from the stream.
     *
     * @return a <code>char</code> value
     */
    @Override
    public final char read_char()
    {
        if (this.chunk_end_pos == this.pos)
        {
            this.adjust_positions();
        }

        index++;
        return (char)(buffer[pos++] & 0xFF);
    }


    /**
     * <code>read_char_array</code> reads an character array from the stream.
     *
     * @param value a <code>char[]</code>, the result array.
     * @param offset an <code>int</code>, an offset into <code>value</code>
     * @param length an <code>int</code>, the length of the array to read
     */
    @Override
    public final void read_char_array
        (final char[] value, final int offset, final int length)
    {
        if (value == null)
        {
            throw new MARSHAL("Cannot marshal result into null array.");
        }
        else if ( offset + length > value.length || length < 0 || offset < 0 )
        {
            throw new MARSHAL
                ("Cannot marshal as indices for array are out bounds.");
        }

        handle_chunking();

        for (int j = offset; j < offset + length; j++)
        {
            index++;
            value[j] = (char) (0xff & buffer[pos++]);
        }
    }

    @Override
    public final double read_double()
    {
        return Double.longBitsToDouble (read_longlong());
    }

    @Override
    public final void read_double_array
       (final double[] value, final int offset, final int length)
    {
        if (length == 0)
        {
            return;
        }

        handle_chunking();

        int remainder = 8 - (index % 8);
        if (remainder != 8)
        {
            index += remainder;
            pos += remainder;
        }

        for (int j = offset; j < offset + length; j++)
        {
            value[j] = Double.longBitsToDouble (_read_longlong());
        }
    }

    /**
     * @deprecated use {@link #read_fixed(short, short)} instead
     */
    @Override
    @Deprecated
    public BigDecimal read_fixed()
    {
        handle_chunking();

        final StringBuffer sb = new StringBuffer();

        final int signum = read_fixed_internal(sb, (short)-1);

        final BigDecimal result = new BigDecimal( new BigInteger( sb.toString()));

        return read_fixed_negate(signum, result);
    }

    @Override
    public BigDecimal read_fixed(short digits, short scale)
     {
        if (digits < 1)
        {
            throw new BAD_PARAM("digits must be a positive value: " + digits + ".");
        }

        if (scale < 0)
        {
            throw new BAD_PARAM("scale must be a non-negative value: " + scale +".");
        }

        if (scale > digits)
        {
            throw new BAD_PARAM("scale factor " + scale + " must be less than or equal to the total number of digits " + digits + ".");
        }

        handle_chunking();

        final StringBuffer sb = new StringBuffer();

        final int c = read_fixed_internal(sb, digits);

        final BigDecimal result = new BigDecimal( new BigInteger( sb.toString()), scale);

        return read_fixed_negate(c, result);
    }

    private BigDecimal read_fixed_negate(final int signum, final BigDecimal result)
    {
        if( signum == 0xD )
        {
            return result.negate();
        }

        return result;
    }

    /**
     * @param outBuffer a string representation of the read in fixed will be appended to the buffer.
     * @param digits the number of expected digits the read in fixed should have. -1 means unlimited.
     * @return the signum of the read in fixed (0xC or 0xD).
     */
    private int read_fixed_internal(StringBuffer outBuffer, short digits)
    {
        int b = buffer[pos++];
        int c = b & 0x0F; // second half byte
        index++;

        while(true)
        {
            c = (b & 0xF0) >>> 4;

            if (outBuffer.length() > 0 || c != 0)
            {
                outBuffer.append(c);
            }

            c = b & 0x0F;
            if( c == 0xC || c == 0xD )
            {
                break;
            }
            outBuffer.append(c);

            b = buffer[pos++];
            index++;
        }

        if ( (digits == 1 || digits == -1) && outBuffer.length() == 0)
        {
            outBuffer.append ('0');
        }
        if (digits != -1 && (outBuffer.length() > digits))
        {
            throw new MARSHAL("unexpected number of digits: expected " + digits + " got " + outBuffer.length() + " " + outBuffer);
        }

        return c;
    }

    @Override
    public final float read_float()
    {
        return Float.intBitsToFloat (read_long());
    }

    @Override
    public final void read_float_array
       (final float[] value, final int offset, final int length)
    {
        if (length == 0)
        {
            return;
        }

        handle_chunking();

        int remainder = 4 - (index % 4);
        if (remainder != 4)
        {
            index += remainder;
            pos += remainder;
        }

        for (int j = offset; j < offset + length; j++)
        {
            value[j] = Float.intBitsToFloat (_read_long());
        }
    }

    @Override
    public final int read_long()
    {
        handle_chunking();

        int result;

        int remainder = 4 - (index % 4);
        if (remainder != 4)
        {
            index += remainder;
            pos += remainder;
        }

        result = _read4int (littleEndian, buffer, pos);

        index += 4;
        pos += 4;
        return result;
    }

    @Override
    public final void read_long_array
       (final int[] value, final int offset, final int length)
    {
        if (length == 0)
        {
            return;
        }

        handle_chunking();

        int remainder = 4 - (index % 4);
        if (remainder != 4)
        {
            index += remainder;
            pos += remainder;
        }

        for (int j = offset; j < offset+length; j++)
        {
            value[j] = _read4int (littleEndian,buffer,pos);
            pos += 4;
        }

        index += 4 * length;
    }


    @Override
    public final long read_longlong()
    {
        handle_chunking();

        int remainder = 8 - (index % 8);
        if (remainder != 8)
        {
            index += remainder;
            pos += remainder;
        }

        return _read_longlong();
    }

    @Override
    public final void read_longlong_array
        (final long[] value, final int offset, final int length)
    {
        if (length == 0)
        {
            return;
        }

        handle_chunking();

        int remainder = 8 - (index % 8);
        if (remainder != 8)
        {
            index += remainder;
            pos += remainder;
        }

        if (littleEndian)
        {
            for(int j=offset; j < offset+length; j++)
            {
                value[j] = ( _read_long() & 0xFFFFFFFFL) +
                    ((long) _read_long() << 32);
            }
        }
        else
        {
            for(int j=offset; j < offset+length; j++)
            {
                value[j] = ((long) _read_long() << 32) +
                    (_read_long() & 0xFFFFFFFFL);
            }
        }

        // Do not need to modify pos and index as use read_long above
    }

    @Override
    public final org.omg.CORBA.Object read_Object()
    {
        if (! (orb instanceof org.jacorb.orb.ORB))
        {
            throw new MARSHAL
                ( "Cannot use the singleton ORB to receive object references, "
                       + "please initialize a full ORB instead.");
        }

        handle_chunking();

        org.omg.IOP.IOR ior = org.omg.IOP.IORHelper.read(this);

        if (isMutatorEnabled)
        {
            ior = mutator.mutateIncoming (ior);
        }

        if (ParsedIOR.isNull(ior))
        {
            return null;
        }
        else
        {
            return ((org.jacorb.orb.ORB)orb)._getObject(ior);
        }
    }

    @Override
    public org.omg.CORBA.Object read_Object(final java.lang.Class clazz)
    {
        if (org.omg.CORBA.portable.ObjectImpl.class.isAssignableFrom(clazz))
        {
            org.omg.CORBA.Object obj = read_Object();
            if (obj instanceof org.omg.CORBA.portable.ObjectImpl)
            {
                org.omg.CORBA.portable.ObjectImpl stub = null;
                try
                {
                    stub = (org.omg.CORBA.portable.ObjectImpl)clazz.newInstance();
                }
                catch (InstantiationException e)
                {
                    throw new MARSHAL("Exception in stub instantiation: " + e);
                }
                catch (IllegalAccessException e)
                {
                    throw new MARSHAL("Exception in stub instantiation: " + e);
                }
                stub._set_delegate(
                     ((org.omg.CORBA.portable.ObjectImpl)obj)._get_delegate());
                return stub;
            }
            return obj;
        }
        else if (clazz.isInterface() &&
                 java.rmi.Remote.class.isAssignableFrom(clazz))
        {
            return (org.omg.CORBA.Object)
                org.jacorb.util.ValueHandler.portableRemoteObject_narrow(
                                                           read_Object(), clazz);
        }
        else
        {
            return read_Object();
        }
    }

    @Override
    public final byte read_octet()
    {
        if (this.chunk_end_pos == this.pos)
        {
            this.adjust_positions();
        }

        index++;
        return buffer[pos++];
    }

    @Override
    public final void read_octet_array
        (final byte[] value, final int offset, final int length)
    {
        handle_chunking();
        System.arraycopy (buffer,pos,value,offset,length);
        index += length;
        pos += length;
    }

    /*
     * @deprecated
     * @see org.omg.CORBA.portable.InputStream#read_Principal()
     */
    @Override
    public final org.omg.CORBA.Principal read_Principal()
    {
        throw new NO_IMPLEMENT ("Principal deprecated");
    }

    @Override
    public final short read_short()
    {
        handle_chunking();

        int remainder = 2 - (index % 2);
        if (remainder != 2)
        {
            index += remainder;
            pos += remainder;
        }

        short result = _read2int (littleEndian,buffer,pos);
        pos += 2;
        index += 2;
        return result;
    }

    @Override
    public final void read_short_array
        (final short[] value, final int offset, final int length)
    {
        if (length == 0)
        {
            return;
        }

        handle_chunking();

        int remainder = 2 - (index % 2);

        if (remainder != 2)
        {
            index += remainder;
            pos += remainder;
        }

        for (int j = offset; j < offset + length; j++)
        {
            value[j] = _read2int (littleEndian, buffer, pos);
            pos += 2;
        }

        index += length * 2;
    }


    /**
     * <code>read_string</code> reads a string from the buffer. It is optimized
     * for whether it is reading a blank string, and whether codeset translation
     * is active.
     *
     * @return a <code>String</code> value, possibly blank, never null.
     */
    @Override
    public final String read_string()
    {
        String result = null;

        handle_chunking();

        int remainder = 4 - (index % 4);
        if( remainder != 4 )
        {
            index += remainder;
            pos += remainder;
        }

        // read size (#bytes)
        int size = _read4int( littleEndian, buffer, pos);

        if (size < 1 && ! nullStringEncoding)
        {
            throw new MARSHAL("invalid string size: " + size);
        }

        int start = pos + 4;

        index += (size + 4);
        pos += (size + 4);

        final int stringTerminatorPosition = start + size -1;

        if (nullStringEncoding && size == 0)
        {
            // Some ORBs wrongly encode empty string with a size 0
            return null;
        }
        else if (buffer.length < stringTerminatorPosition + 1)
        {
            throw new MARSHAL("buffer too small");
        }

        if ((buffer[stringTerminatorPosition] == 0))
        {
            size --;
        }
        else
        {
            throw new MARSHAL("unexpected string terminator value " + Integer.toHexString(buffer[stringTerminatorPosition]) + " at buffer index " + stringTerminatorPosition);
        }

        // Optimize for empty strings.
        if (size == 0)
        {
            return "";
        }

        if(start + size > buffer.length)
        {
            final String message = "Size (" + size + ") invalid for string extraction from buffer length of " + buffer.length + " from position " + start;
            if (logger.isDebugEnabled())
            {
                logger.debug(message);
            }
            throw new MARSHAL(message);
        }

        if (codesetEnabled)
        {

            try
            {
                result = new String (buffer, start, size, codeSet.getName() );
            }
            catch (java.io.UnsupportedEncodingException ex)
            {
                if (logger.isErrorEnabled())
                {
                    logger.error("Charset " + codeSet.getName() + " is unsupported");
                    result = "";
                }
            }
        }
        else
        {
            char[] buf = new char[size];

            for (int i=0; i<size; i++)
            {
                buf[i] = (char)(0xff & buffer[start + i]);
            }
            result = new String(buf);
        }

        return result;
    }


    @Override
    public final org.omg.CORBA.TypeCode read_TypeCode()
    {
        if (recursiveTCMap == null)
        {
            recursiveTCMap = new HashMap();
        }

        if (repeatedTCMap == null)
        {
            repeatedTCMap = new TreeMap();
        }

        try
        {
            return read_TypeCode(recursiveTCMap, repeatedTCMap);
        }
        finally
        {
            recursiveTCMap.clear();
            repeatedTCMap.clear();
        }
    }

    public final org.omg.CORBA.TypeCode read_TypeCode(Map recursiveTCMap, Map repeatedTCMap)
    {
        try
        {
            ++typeCodeNestingLevel;
            return typeCodeReader.readTypeCode(logger, this, recursiveTCMap, repeatedTCMap);
        }
        finally
        {
            --typeCodeNestingLevel;
        }
    }

    /**
     * Skip amount is
     * skip (size - ((pos - start_pos) - 4 - 4));
     * EncapsulationSize -
     * ( PositionAfterReadingId - start_pos
     *   - 4 [Size] - 4 [KindSize] ) = RemainingSizeToSkip
     * @param start_pos
     * @param size
     */
    public void skipRemainingTypeCode(final Integer start_pos, final int size)
    {
        skip (size - ((pos - start_pos.intValue()) - 4 - 4));
    }


    @Override
    public final int read_ulong()
    {
        handle_chunking();

        int result;

        int remainder = 4 - (index % 4);
        if (remainder != 4)
        {
            index += remainder;
            pos += remainder;
        }

        result = _read4int (littleEndian, buffer, pos);

        index += 4;
        pos += 4;
        return result;
    }

    @Override
    public final void read_ulong_array
        (final int[] value, final int offset, final int length)
    {
        if (length == 0)
        {
            return;
        }

        handle_chunking();

        int remainder = 4 - (index % 4);
        if (remainder != 4)
        {
            index += remainder;
            pos += remainder;
        }

        for (int j = offset; j < offset+length; j++)
        {
            value[j] = _read4int (littleEndian,buffer,pos);
            pos += 4;
        }

        index += 4 * length;
    }

    @Override
    public final long read_ulonglong()
    {
        handle_chunking();

        int remainder = 8 - (index % 8);
        if (remainder != 8)
        {
            index += remainder;
            pos += remainder;
        }

        if (littleEndian)
        {
            return (_read_long() & 0xFFFFFFFFL) + ((long) _read_long() << 32);
        }

        return ((long) _read_long() << 32) + (_read_long() & 0xFFFFFFFFL);
    }

    @Override
    public final void read_ulonglong_array
        (final long[] value, final int offset, final int length)
    {
        if (length == 0)
        {
            return;
        }

        handle_chunking();

        int remainder = 8 - (index % 8);
        if (remainder != 8)
        {
            index += remainder;
            pos += remainder;
        }

        if (littleEndian)
        {
            for (int j = offset; j < offset+length; j++)
            {
                value[j] = ( _read_long() & 0xFFFFFFFFL) +
                    ((long) _read_long() << 32);
            }
        }
        else
        {
            for (int j = offset; j < offset+length; j++)
            {
                value[j] = ((long) _read_long() << 32) +
                    (_read_long() & 0xFFFFFFFFL);
            }
        }

        // Do not need to modify pos and index as use read_long above
    }

    @Override
    public final short read_ushort()
    {
        handle_chunking();

        int remainder = 2 - (index % 2);
        if (remainder != 2)
        {
            index += remainder;
            pos += remainder;
        }

        short result = _read2int (littleEndian,buffer,pos);
        pos += 2;
        index += 2;
        return result;
    }

    @Override
    public final void read_ushort_array
        (final short[] value, final int offset, final int length)
    {
        if (length == 0)
        {
            return;
        }

        handle_chunking();

        int remainder = 2 - (index % 2);

        if (remainder != 2)
        {
            index += remainder;
            pos += remainder;
        }

        for (int j = offset; j < offset + length; j++)
        {
            value[j] = _read2int (littleEndian, buffer, pos);
            pos += 2;
        }

        index += length * 2;
    }

    @Override
    public final char read_wchar()
    {
        if (giop_minor == 0)
        {
            final int minor = Messages.getMsgType( buffer ) == MsgType_1_1._Reply ? 6 : 5;
            throw new MARSHAL("GIOP 1.0 does not support the type wchar", minor, CompletionStatus.COMPLETED_NO);
        }

        handle_chunking();

        if (giop_minor == 2)
        {
            //ignore size indicator
            read_wchar_size();

            boolean wchar_little_endian = readBOM();

            return read_wchar (wchar_little_endian);
        }

        return read_wchar (littleEndian);
    }


    @Override
    public byte readByte()
    {
        index++;
        return buffer[ pos++ ];
    }


    /**
     * The number of bytes this char takes. This is actually not
     * necessary since the encodings used are either fixed-length
     * (UTF-16) or have their length encoded internally (UTF-8).
     */
    private final int read_wchar_size()
    {
        index++;

        return buffer[ pos++ ];
    }

    private final char read_wchar(final boolean wchar_little_endian)
    {
        return codeSetW.read_wchar( this, giop_minor, wchar_little_endian );
    }

    /**
     * Read the byte order marker indicating the endianess.
     *
     * @return true for little endianess, false otherwise (including
     * no BOM present. In this case, big endianess is assumed per
     * spec).
     */
    @Override
    public final boolean readBOM()
    {
        if( (buffer[ pos     ] == (byte) 0xFE) &&
            (buffer[ pos + 1 ] == (byte) 0xFF) )
        {
            //encountering a byte order marker indicating big
            //endianess

            pos += 2;
            index += 2;

            return false;
        }
        else if( (buffer[ pos     ] == (byte) 0xFF) &&
                 (buffer[ pos + 1 ] == (byte) 0xFE) )
        {
            //encountering a byte order marker indicating
            //little endianess

            pos += 2;
            index += 2;

            return true;
        }
        else
        {
            //no BOM so big endian per spec.
            return false;
        }
    }

    @Override
    public final void read_wchar_array
        (final char[] value, final int offset, final int length)
    {
        handle_chunking();
        for(int j=offset; j < offset+length; j++)
        {
            value[j] = read_wchar(); // inlining later...
        }
    }

    @Override
    public final String read_wstring()
    {
        if (giop_minor == 0)
        {
            final int minor = Messages.getMsgType( buffer ) == MsgType_1_1._Reply ? 6 : 5;
            throw new MARSHAL("GIOP 1.0 does not support the IDL type wstring", minor, CompletionStatus.COMPLETED_NO);
        }

        handle_chunking();

        int remainder = 4 - (index % 4);
        if( remainder != 4 )
        {
            index += remainder;
            pos += remainder;
        }

        // read length indicator
        int size = _read4int( littleEndian, buffer, pos);
        index += 4;
        pos += 4;
        if (size == 0) return "";

        return codeSetW.read_wstring( this, size, this.giop_minor, this.littleEndian );
    }


    @Override
    public boolean markSupported()
    {
        return true;
    }

    @Override
    public void mark(final int readLimit)
    {
        marked_pos = pos;
        marked_index = index;
        marked_chunk_end_pos = chunk_end_pos;
        marked_valueNestingLevel = valueNestingLevel;
    }

    @Override
    public void reset()
        throws IOException
    {
        if( pos < 0 )
        {
            throw new MARSHAL("Mark has not been set!");
        }
        pos = marked_pos;
        index = marked_index;
        chunk_end_pos = marked_chunk_end_pos;
        valueNestingLevel = marked_valueNestingLevel;
    }

    // JacORB-specific

    private final void resetIndex()
    {
        index = 0;
    }

    public final void setLittleEndian(final boolean b)
    {
        littleEndian = b;
    }


    protected boolean getLittleEndian ()
    {
       return littleEndian;
    }

    /**
     * Reads an instance of the type described by type code <code>tc</code>
     * from this CDRInputStream, and remarshals it to the given OutputStream,
     * <code>out</code>.  Called from Any.
     */
    final void read_value(final org.omg.CORBA.TypeCode typeCode,
                          final org.omg.CORBA.portable.OutputStream out)
    {
        if (typeCode == null)
        {
            throw new BAD_PARAM("TypeCode is null");
        }

        int kind = typeCode.kind().value();

        try
        {
            switch (kind)
            {
                case TCKind._tk_null:       // 0
                    // fallthrough
                case TCKind._tk_void:       // 1
                {
                    break;
                }
                case TCKind._tk_short:      // 2
                {
                    out.write_short( read_short());
                    break;
                }
                case TCKind._tk_long:       // 3
                {
                    out.write_long( read_long());
                    break;
                }
                case TCKind._tk_ushort:     // 4
                {
                    out.write_ushort( read_ushort());
                    break;
                }
                case TCKind._tk_ulong:      // 5
                {
                    out.write_ulong( read_ulong());
                    break;
                }
                case TCKind._tk_float:      // 6
                {
                    out.write_float( read_float());
                    break;
                }
                case TCKind._tk_double:     // 7
                {
                    out.write_double( read_double());
                    break;
                }
                case TCKind._tk_boolean:    // 8
                {
                    out.write_boolean( read_boolean());
                    break;
                }
                case TCKind._tk_char:       // 9
                {
                    out.write_char( read_char());
                    break;
                }
                case TCKind._tk_octet:      // 10
                {
                    out.write_octet( read_octet());
                    break;
                }
                case TCKind._tk_any:        // 11
                {
                    out.write_any( read_any());
                    break;
                }
                case TCKind._tk_TypeCode:   // 12
                {
                    out.write_TypeCode( read_TypeCode());
                    break;
                }
                case TCKind._tk_Principal:  // 13
                {
                    throw new NO_IMPLEMENT ("Principal deprecated");
                }
                case TCKind._tk_objref:     // 14
                {
                    out.write_Object( read_Object());
                    break;
                }
                case TCKind._tk_struct:     // 15
                {
                    for( int i = 0; i < typeCode.member_count(); i++)
                    {
                        read_value( typeCode.member_type(i), out );
                    }
                    break;
                }
                case TCKind._tk_union:      // 16
                {
                    org.omg.CORBA.TypeCode disc = typeCode.discriminator_type();
                    disc = TypeCode.originalType(disc);
                    int def_idx = typeCode.default_index();
                    int member_idx = -1;
                    switch( disc.kind().value() )
                    {
                        case TCKind._tk_short:  // 2
                        {
                            short s = read_short();
                            out.write_short(s);
                            for(int i = 0 ; i < typeCode.member_count() ; i++)
                            {
                                if(i != def_idx)
                                {
                                    if(s == typeCode.member_label(i).extract_short())
                                    {
                                        member_idx = i;
                                        break;
                                    }
                                }
                            }
                            break;
                        }

                        case TCKind._tk_long:   // 3
                        {
                            int s = read_long();
                            out.write_long(s);
                            for(int i = 0 ; i < typeCode.member_count() ; i++)
                            {
                                if(i != def_idx)
                                {
                                    if(s == typeCode.member_label(i).extract_long())
                                    {
                                        member_idx = i;
                                        break;
                                    }
                                }
                            }
                            break;
                        }
                        case TCKind._tk_ushort: // 4
                        {
                            short s = read_ushort();
                            out.write_ushort(s);
                            for(int i = 0 ; i < typeCode.member_count() ; i++)
                            {
                                if(i != def_idx)
                                {
                                    if(s == typeCode.member_label(i).extract_ushort())
                                    {
                                        member_idx = i;
                                        break;
                                    }
                                }
                            }
                            break;
                        }

                        case TCKind._tk_ulong:  // 5
                        {
                            int s = read_ulong();
                            out.write_ulong(s);
                            for(int i = 0 ; i < typeCode.member_count() ; i++)
                            {
                                if(i != def_idx)
                                {
                                    if(s == typeCode.member_label(i).extract_ulong())
                                    {
                                        member_idx = i;
                                        break;
                                    }
                                }
                            }
                            break;
                        }
                        case TCKind._tk_float:      // 6
                            // fallthrough
                        case TCKind._tk_double:     // 7
                        {
                            throw new MARSHAL(
                                "Invalid union discriminator type: " + disc);
                        }
                        case TCKind._tk_boolean:    // 8
                        {
                            boolean b = read_boolean();
                            out.write_boolean( b );
                            for(int i = 0 ; i < typeCode.member_count() ; i++)
                            {
                                if( i != def_idx)
                                {
                                    if( b == typeCode.member_label(i).extract_boolean() )
                                    {
                                        member_idx = i;
                                        break;
                                    }
                                }
                            }
                            break;
                        }
                        case TCKind._tk_char:   // 9
                        {
                            char s = read_char();
                            out.write_char(s);
                            for(int i = 0 ; i < typeCode.member_count() ; i++)
                            {
                                if(i != def_idx)
                                {
                                    if(s == typeCode.member_label(i).extract_char())
                                    {
                                        member_idx = i;
                                        break;
                                    }
                                }
                            }
                            break;
                        }
                        case TCKind._tk_octet:      // 10
                            // fallthrough
                        case TCKind._tk_any:        // 11
                            // fallthrough
                        case TCKind._tk_TypeCode:   // 12
                            // fallthrough
                        case TCKind._tk_Principal:  // 13
                            // fallthrough
                        case TCKind._tk_objref:     // 14
                            // fallthrough
                        case TCKind._tk_struct:     // 15
                            // fallthrough
                        case TCKind._tk_union:      // 16
                        {
                            throw new MARSHAL(
                                "Invalid union discriminator type: " + disc);
                        }
                        case TCKind._tk_enum:       // 17
                        {
                            int s = read_long();
                            out.write_long(s);
                            for( int i = 0 ; i < typeCode.member_count() ; i++)
                            {
                                if( i != def_idx)
                                {
                                    int label =
                                    typeCode.member_label(i).create_input_stream().read_long();
                                    if(s == label)
                                    {
                                        member_idx = i;
                                        break;
                                    }
                                }
                            }
                            break;
                        }
                        case TCKind._tk_string:     // 18
                            // fallthrough
                        case TCKind._tk_sequence:   // 19
                            // fallthrough
                        case TCKind._tk_array:      // 20
                            // fallthrough
                        case TCKind._tk_alias:      // 21
                            // fallthrough
                        case TCKind._tk_except:     // 22
                        {
                            throw new MARSHAL(
                                "Invalid union discriminator type: " + disc);
                        }
                        case TCKind._tk_longlong:  // 23
                        {
                            long s = read_longlong();
                            out.write_longlong(s);
                            for(int i = 0 ; i < typeCode.member_count() ; i++)
                            {
                                if(i != def_idx)
                                {
                                    if(s == typeCode.member_label(i).extract_longlong())
                                    {
                                        member_idx = i;
                                        break;
                                    }
                                }
                            }
                            break;
                        }
                        case TCKind._tk_ulonglong:  // 24
                        {
                            long s = read_ulonglong();
                            out.write_ulonglong(s);
                            for(int i = 0 ; i < typeCode.member_count() ; i++)
                            {
                                if(i != def_idx)
                                {
                                    if(s == typeCode.member_label(i).extract_ulonglong())
                                    {
                                        member_idx = i;
                                        break;
                                    }
                                }
                            }
                            break;
                        }
                        default:
                        {
                            throw new MARSHAL("Invalid union discriminator type: " + disc);
                        }
                    } // switch

                    if( member_idx != -1 )
                    {
                        read_value( typeCode.member_type( member_idx ), out );
                    }
                    else if( def_idx != -1 )
                    {
                        read_value( typeCode.member_type( def_idx ), out );
                    }
                    break;
                }
                case TCKind._tk_enum:       // 17
                {
                    out.write_long( read_long() );
                    break;
                }
                case TCKind._tk_string:     // 18
                {
                    out.write_string( read_string());
                    break;
                }
                case TCKind._tk_sequence:   // 19
                {
                    int len = read_long();
                    out.write_long(len);
                    for( int i = 0; i < len; i++ )
                    {
                        read_value( typeCode.content_type(), out );
                    }
                    break;
                }
                case TCKind._tk_array:      // 20
                {
                    int length = typeCode.length();
                    for( int i = 0; i < length; i++ )
                    {
                        read_value( typeCode.content_type(), out );
                    }
                    break;
                }
                case TCKind._tk_alias:      // 21
                {
                    read_value( typeCode.content_type(), out  );
                    break;
                }
                case TCKind._tk_except:     // 22
                {
                    out.write_string( read_string());

                    for( int i = 0; i < typeCode.member_count(); i++)
                    {
                        read_value( typeCode.member_type(i), out );
                    }

                    break;
                }
                case TCKind._tk_longlong:   // 23
                {
                    out.write_longlong( read_longlong());
                    break;
                }
                case TCKind._tk_ulonglong:  // 24
                {
                    out.write_ulonglong( read_ulonglong());
                    break;
                }
                case TCKind._tk_longdouble: // 25
                {
                    throw new org.omg.CORBA.BAD_TYPECODE(
                        "type longdouble not supported in java");
                }
                case TCKind._tk_wchar:      // 26
                {
                    out.write_wchar( read_wchar());
                    break;
                }
                case TCKind._tk_wstring:    // 27
                {
                    out.write_wstring( read_wstring());
                    break;
                }
                case TCKind._tk_fixed:      // 28
                {
                    final short digits = typeCode.fixed_digits();
                    final short scale = typeCode.fixed_scale();
                    final BigDecimal value = read_fixed(digits, scale);

                    if (out instanceof CDROutputStream)
                    {
                        CDROutputStream cdrOut = (CDROutputStream) out;
                        cdrOut.write_fixed(value, digits, scale);
                    }
                    else
                    {
                        // TODO can we remove this? mixed usage orb classes from different vendors ...
                        out.write_fixed (value);
                    }
                    break;
                }
                case TCKind._tk_value:      // 29
                {
                    Serializable val = read_value();
                    ((org.omg.CORBA_2_3.portable.OutputStream)out).write_value(val, typeCode.id());
                    break;
                }
                case TCKind._tk_value_box:  // 30
                {
                    String id = typeCode.id();
                    org.omg.CORBA.portable.BoxedValueHelper helper =
                        ((org.jacorb.orb.ORB)orb).getBoxedValueHelper(id);
                    if (helper == null)
                    {
                        throw new MARSHAL ("No BoxedValueHelper for id " + id);
                    }
                    java.io.Serializable value = read_value(helper);
                    ((org.omg.CORBA_2_3.portable.OutputStream)out).write_value(value, helper);
                    break;
                }
                default:
                {
                    throw new MARSHAL("Cannot handle TypeCode with kind " + kind);
                }
            }
        }
        catch (BadKind ex)
        {
            throw new MARSHAL
                ("When processing TypeCode with kind: " + kind + " caught " + ex);
        }
        catch (Bounds ex)
        {
            throw new MARSHAL
                ("When processing TypeCode with kind: " + kind + " caught " + ex);
        }
    }

    @Override
    public java.io.Serializable read_value()
    {
        int tag = read_long();
        int start_offset = pos - 4;

        if (tag == 0xffffffff)
        {
            // indirection
            return read_indirect_value();
        }
        else if (tag == 0x00000000)
        {
            // null tag
            return null;
        }

        String codebase = ((tag & 1) != 0) ? read_codebase() : null;
        chunkedValue = ((tag & 8) != 0);

        int theTag = tag;
        tag = tag & 0xfffffff6;

        if (tag == 0x7fffff00)
        {
            throw new MARSHAL ("missing value type information");
        }
        else if (tag == 0x7fffff02)
        {
            return read_typed_value(start_offset, codebase);
        }
        else if (tag == 0x7fffff06)
        {
            return read_multi_typed_value( start_offset, codebase );
        }
        else
        {
            throw new MARSHAL("unknown value tag: 0x" +
                              Integer.toHexString(theTag) + " (offset=0x" +
                              Integer.toHexString(start_offset) + ")");
        }
    }

    /**
     * Overrides read_value(java.io.Serializable value) in
     * org.omg.CORBA_2_3.portable.InputStream
     */
    @Override
    public java.io.Serializable read_value(final String rep_id)
    {
        int tag = read_long();
        final int start_offset = pos - 4;

        if (tag == 0xffffffff)
        {
            // indirection
            return read_indirect_value();
        }
        else if (tag == 0x00000000)
        {
            // null tag
            return null;
        }

        final String codebase = ((tag & 1) != 0) ? read_codebase() : null;
        chunkedValue = ((tag & 8) != 0);

        int theTag = tag;
        tag = tag & 0xfffffff6;

        if (tag == 0x7fffff00)
        {
            return read_untyped_value ( new String[]{ rep_id }, start_offset, codebase);
        }
        else if (tag == 0x7fffff02)
        {
            return read_typed_value( start_offset, codebase );
        }
        else if (tag == 0x7fffff06)
        {
            return read_multi_typed_value( start_offset, codebase );
        }
        else
        {
            throw new MARSHAL("unknown value tag: 0x" +
                              Integer.toHexString(theTag) + " (offset=0x" +
                              Integer.toHexString(start_offset) + ")");
        }
    }

    /**
     * Unmarshals a valuetype instance from this stream.  The value returned
     * is the same value passed in, with all the data unmarshaled
     * (IDL-to-Java Mapping 1.2, August 2002, 1.13.1, p. 1-39).  The specified
     * value is an uninitialized value that is added to the ORB's indirection
     * table before unmarshaling (1.21.4.1, p. 1-117).
     *
     * This method is intended to be called from custom valuetype factories.
     * Unlike the other read_value() methods in this class, this method does
     * not expect a GIOP value tag nor a repository id in the stream.
     *
     * Overrides read_value(value) in
     * org.omg.CORBA_2_3.portable.InputStream
     */
    @Override
    public java.io.Serializable read_value(java.io.Serializable value)
    {
        if (value instanceof org.omg.CORBA.portable.Streamable)
        {
            register_value(value);
            ((org.omg.CORBA.portable.Streamable)value)._read(this);
        }
        else if (value instanceof org.omg.CORBA.portable.CustomValue )
        {
            register_value(value);
            ( ( org.omg.CORBA.portable.CustomValue ) value ).unmarshal(
                    new DataInputStream( this ) );
        }
        else
        {
            throw new BAD_PARAM("read_value is only implemented for Streamables");
        }
        return value;
    }

    /**
     * Overrides read_value(clz) in
     * org.omg.CORBA_2_3.portable.InputStream
     */

    @Override
    public java.io.Serializable read_value(final java.lang.Class clz)
    {
        int tag = read_long();
        int start_offset = pos - 4;

        if (tag == 0xffffffff)
        {
            // indirection
            return read_indirect_value();
        }
        else if (tag == 0x00000000)
        {
            // null tag
            return null;
        }

        String codebase = ((tag & 1) != 0) ? read_codebase() : null;
        chunkedValue = ((tag & 8) != 0);

        int theTag = tag;
        tag = tag & 0xfffffff6;

        if (tag == 0x7fffff00)
        {
            return read_untyped_value (new String[]{ValueHandler.getRMIRepositoryID(clz)},
                                                    start_offset, codebase);
        }
        else if (tag == 0x7fffff02)
        {
            return read_typed_value(start_offset, codebase);
        }
        else if (tag == 0x7fffff06)
        {
            return read_multi_typed_value(start_offset, codebase);
        }
        else
        {
            throw new MARSHAL("unknown value tag: 0x" +
                              Integer.toHexString(theTag) +
                              " (offset=0x" +
                              Integer.toHexString(start_offset) +
                              ")");
        }
    }

    /**
     * Overrides read_value(factory) in
     * org.omg.CORBA_2_3.portable.InputStream
     */
    @Override
    public java.io.Serializable read_value
        (final org.omg.CORBA.portable.BoxedValueHelper factory)
    {
        int tag = read_long();
        int start_offset = pos - 4;

        if (tag == 0xffffffff)
        {
            // indirection
            return read_indirect_value();
        }
        else if (tag == 0x00000000)
        {
            // null tag, explicit representation of null value
            return null;
        }

        String codebase = ((tag & 1) != 0) ? read_codebase() : null;
        chunkedValue = ((tag & 8) != 0);

        int theTag = tag;
        tag = tag & 0xfffffff6;

        if (tag == 0x7fffff00)
        {
            java.io.Serializable result = factory.read_value (this);

            if( result != null )
            {
                getValueMap().put(Integer.valueOf(start_offset), result);
            }

            return result;
        }
        else if (tag == 0x7fffff02)
        {
            final Serializable result = read_typed_value(start_offset, codebase, factory);

            if (result != null)
            {
                getValueMap().put(Integer.valueOf(start_offset), result);
            }

            return result;
        }
        else
        {
            throw new MARSHAL("unknown value tag: 0x" +
                              Integer.toHexString(theTag) + " (offset=0x" +
                              Integer.toHexString(start_offset) + ")");
        }
    }

    /**
     * Immediately reads a value from this stream; i.e. without any
     * repository id preceding it.  The expected type of the value is given
     * by `repository_id', and the index at which the value started is
     * `index'.
     */
    private java.io.Serializable read_untyped_value(final String[] repository_ids,
                                                    final int index,
                                                    final String codebase)
    {
        java.io.Serializable result = null;

        if (chunkedValue || (valueNestingLevel > 0 && !sunInteropFix))
        {
            valueNestingLevel++;
            readChunkSizeTag();
        }

        for (int i = 0; i < repository_ids.length; i++)
        {
            if (repository_ids[i].equals(org.omg.CORBA.WStringValueHelper.id()))
            {
                // special handling of strings, according to spec
                result = read_wstring();
                break;
            }
            else if(repository_ids[i].equals(org.omg.CORBA.StringValueHelper.id()))
            {
                // special handling of strings, according to spec
                result = read_string();
                break;
            }
            else if (repository_ids[i].startsWith("RMI:javax.rmi.CORBA.ClassDesc:"))
            {
                // special handling of java.lang.Class instances
                final String classCodebase = (String)read_value(String.class);
                final String reposId = (String)read_value(String.class);
                final String className =
                    org.jacorb.ir.RepositoryID.className(reposId, null);

                try
                {
                    result = loadClass(className, classCodebase);
                }
                catch (ClassNotFoundException e)
                {
                    if( i < repository_ids.length-1 )
                    {
                        continue;
                    }

                    throw new MARSHAL("class not found: " + className);
                }
                break;
            }
            else if (repository_ids[i].startsWith ("IDL:"))
            {
                org.omg.CORBA.portable.ValueFactory factory =
                    ((org.omg.CORBA_2_3.ORB)orb()).lookup_value_factory (repository_ids[i]);

                if (factory != null)
                {
                    currentValueIndex = index;
                    result = factory.read_value (this);
                    break;
                }

                if( i < repository_ids.length-1 )
                {
                    continue;
                }

                throw new MARSHAL ("No factory found for: " + repository_ids[0] );
            }
            else // RMI
            {
                final String className =
                    org.jacorb.ir.RepositoryID.className(repository_ids[i], null);

                try
                {
                    final Class clazz = loadClass(className, codebase);

                    if (IDLEntity.class.isAssignableFrom(clazz))
                    {
                        java.lang.reflect.Method readMethod = null;
                        if (clazz != org.omg.CORBA.Any.class)
                        {
                            String helperClassName = clazz.getName() + "Helper";

                            try
                            {
                                final ClassLoader classLoader = clazz.getClassLoader();
                                final Class helperClass;
                                if (classLoader == null)
                                {
                                    helperClass = ObjectUtil.classForName(helperClassName);
                                }
                                else
                                {
                                    helperClass =
                                        classLoader.loadClass(helperClassName);
                                }

                                Class[] paramTypes = {
                                    org.omg.CORBA.portable.InputStream.class
                                };
                                readMethod =
                                    helperClass.getMethod("read", paramTypes);
                            }
                            catch (ClassNotFoundException e)
                            {
                                throw new MARSHAL("Error loading class " + helperClassName
                                                  + ": " + e);
                            }
                            catch (NoSuchMethodException e)
                            {
                                throw new MARSHAL("No read method in helper class "
                                                  + helperClassName + ": " + e);
                            }
                        }

                        if (readMethod == null)
                        {
                            result = read_any();
                        }
                        else
                        {
                            try
                            {
                                result =
                                    (java.io.Serializable) readMethod.invoke(
                                        null,
                                        new java.lang.Object[] { this });
                            }
                            catch (IllegalAccessException e)
                            {
                                throw new MARSHAL("Internal error: " + e);
                            }
                            catch (java.lang.reflect.InvocationTargetException e)
                            {
                                throw new MARSHAL("Exception unmarshaling IDLEntity: "
                                                  + e.getTargetException());
                            }
                        }
                    }
                    else
                    {
                        result = ValueHandler.readValue(this, index, clazz,
                                                        repository_ids[i],
                                                        null);
                    }
                }
                catch (ClassNotFoundException e)
                {
                    if( i < repository_ids.length-1 )
                    {
                        continue;
                    }

                    throw new MARSHAL ("class not found: " + className);
                }
            }
        }

        // value type instances may be null...
        if( result != null )
        {
            getValueMap().put(Integer.valueOf(index), result);
        }

        return result;
    }

    /** Load the value's class, using the context class loader
     *  of the current thread if possible.  Here's Francisco
     *  Reverbel's <reverbel@ime.usp.br> explanation of why
     *  this is needed in JBoss:
     *
     *  "It seems that ValueHandler.loadClass() uses the thread
     *  context classloader only after it looks for other
     *  classloaders in the call stack (weird). In some
     *  situations (when EJBs are undeployed and then
     *  redeployed) it finds in the call stack a classloader
     *  used for an undeployed EJB. A value of class Foo is
     *  then unmarshalled with type
     *  classloaderOfEJB1:Foo, when the expected type is
     *  classloaderOfEJB2:Foo. I am getting ClassCastExceptions is this
     *  situation.
     *  Explicitly using the thread context class loader in the
     *  first place solves the problem."
     */
    private Class loadClass(String className, final String codebase) throws ClassNotFoundException
    {
        Class clazz;
        ClassLoader clazzLoader = Thread.currentThread().getContextClassLoader();

        if (clazzLoader == null)
        {
            clazz = ValueHandler.loadClass(className, codebase, null);
        }
        else
        {
            try
            {
                clazz = clazzLoader.loadClass(className);
            }
            catch (ClassNotFoundException e)
            {
                clazz = ValueHandler.loadClass(className, codebase, null);
            }
        }
        return clazz;
    }

    /**
     * try to read in the chunk size.
     * special handling if there's no chunk size
     * in the stream.
     */
    private void readChunkSizeTag()
    {
        int savedPos = pos;
        int savedIndex = index;
        int chunk_size_tag = read_long();

        if (!sunInteropFix || chunk_size_tag > 0 && chunk_size_tag < MAX_BLOCK_SIZE)
        {
            // valid chunk size: set the ending position of the chunk
            chunk_end_pos = pos + chunk_size_tag;
        }
        else
        {
            // reset buffer and remember that we're not within a chunk
            pos = savedPos;
            index = savedIndex;

            adjust_positions();
        }
    }

    /**
     * Reads a value with type information, i.e. one that is preceded
     * by a single RepositoryID.  It is assumed that the tag and the codebase
     * of the value have already been read.
     */
    private java.io.Serializable read_typed_value( final int index,
                                                   final String codebase)
    {
        return read_untyped_value ( new String[]{ read_repository_id() }, index, codebase);
    }

    /**
     * Reads a value using the specified factory. The preceeding single RepositoryID is ignored.
     * since the type information is most likely redundant.
     * It is assumed that the tag and the codebase
     * of the value have already been read.
     */
    private java.io.Serializable read_typed_value( final int index,
                                                   final String codebase,
                                                   final org.omg.CORBA.portable.BoxedValueHelper factory)
    {
        String repId = read_repository_id();

        if (!factory.get_id().equals(repId))
        {
            // just to be sure.
            throw new MARSHAL("unexpected RepositoryID. expected: " + factory.get_id() + " got: " + repId);
        }

        return factory.read_value(this);
    }

    /**
     * Reads a value with type information, i.e. one that is preceded
     * by an array of RepositoryIDs.  It is assumed that the tag and the codebase
     * of the value have already been read.
     */
    private java.io.Serializable read_multi_typed_value( final int index,
                                                         final String codebase)
    {
        int id_count = read_long();
        String[] ids = new String[ id_count ];

        for( int i = 0; i < id_count; i++ )
        {
            ids[i] = read_repository_id();
        }

        return read_untyped_value (ids, index, codebase);
    }


    /**
     * Reads a RepositoryID from the buffer, either directly or via
     * indirection.
     */
    private String read_repository_id()
    {
        int tag = read_long();
        if (tag == 0xffffffff)
        {
            // indirection
            int index = read_long();
            index = index + pos - 4;

            String repId = (String)getRepIdMap().get(Integer.valueOf(index));
            if (repId == null)
            {
                throw new MARSHAL("stale RepositoryID indirection");
            }
            return repId;
        }

        // a new id
        pos -= 4;
        index -= 4;
        int start_offset = pos;
        String repId = read_string();

        getRepIdMap().put(Integer.valueOf(start_offset), repId);
        return repId;
    }

    /**
     * Reads a codebase from the buffer, either directly or via
     * indirection.
     */
    private String read_codebase()
    {
        int tag = read_long();

        if (tag == 0xffffffff)
        {
            // indirection
            int index = read_long();
            index = index + pos - 4;
            String codebase = (String)getCodebaseMap().get(Integer.valueOf(index));
            if (codebase == null)
            {
                throw
                new MARSHAL("stale codebase indirection");
            }

            return codebase;
        }
        // a new codebase string
        pos -= 4;
        index -= 4;
        int start_offset = pos;
        String codebase = read_string();
        getCodebaseMap().put (Integer.valueOf(start_offset), codebase);
        return codebase;
    }

    /**
     * Reads an indirect value from this stream. It is assumed that the
     * value tag (0xffffffff) has already been read.
     */
    private java.io.Serializable read_indirect_value()
    {
        // indirection
        int index = read_long();
        index = index + pos - 4;
        java.lang.Object value = getValueMap().get (Integer.valueOf(index));

        if (value == null)
        {
            // Java to IDL Language Mapping, v1.1, page 1-44:
            //
            // "The ValueHandler object may receive an IndirectionException
            // from the ORB stream. The ORB input stream throws this exception
            // when it is called to unmarshal a value encoded as an indirection
            // that is in the process of being unmarshaled. This can occur when
            // the ORB stream calls the ValueHandler object to unmarshal an RMI
            // value whose state contains a recursive reference to itself.
            // Because the top-level ValueHandler.readValue call has not yet
            // returned a value, the ORB stream's indirection table contains no
            // entry for an object with the stream offset specified by the
            // indirection tag. This stream offset is returned in the
            // exception's offset field."

            throw new org.omg.CORBA.portable.IndirectionException (index);
        }

        return (java.io.Serializable)value;
    }

    /**
     * Reads an abstract interface from this stream. The abstract interface
     * Reads an abstract interface from this stream. The abstract interface
     * appears as a union with a boolean discriminator, which is true if the
     * union contains a CORBA object reference, or false if the union contains
     * a value.
     */
    @Override
    public java.lang.Object read_abstract_interface()
    {
        return read_boolean() ? (java.lang.Object)read_Object()
        : (java.lang.Object)read_value();
    }

    /**
     * Reads an abstract interface from this stream. The abstract interface
     * appears as a union with a boolean discriminator, which is true if the
     * union contains a CORBA object reference, or false if the union contains
     * a value.
     */
    @Override
    public java.lang.Object read_abstract_interface(final java.lang.Class clazz)
    {
        return read_boolean() ? (java.lang.Object)read_Object(clazz)
        : (java.lang.Object)read_value(clazz);
    }

    @Override
    public int get_pos()
    {
        return pos;
    }

    /**
     * Stores `value' into this stream's valueMap.  This is provided
     * as a callback for value factories, so that a value factory can
     * store an object into the map before actually reading its state.
     * This is essential for unmarshalling recursive values.
     */
    public void register_value(final java.io.Serializable value)
    {
        getValueMap().put(Integer.valueOf(currentValueIndex), value);
    }

    /**
     * <code>updateMutatorConnection</code> is an accessor that updates the
     * ior mutator.
     *
     * By making callers pass in a GIOPConnection not a transport this allows
     * callers to not have to call getTransport which would require a synchronized
     * lock. Therefore if the mutator has not been enabled this is effectively a
     * NOP.
     *
     * @param connection an <code>org.omg.ETF.Connection</code> value
     */
    public void updateMutatorConnection(GIOPConnection connection)
    {
        if (isMutatorEnabled)
        {
            mutator.updateConnection (connection.getTransport());
        }
    }

    /**
     * update the TypeCodeCache with the contents of the current repeatedTCMap using the
     * entries between startPosition and (startPosition + size).
     * the entries are stored using repositoryID as a key.
     */
    public void updateTypeCodeCache(String repositoryID, Integer startPosition, int size)
    {
        final Integer from = startPosition; //Integer.valueOf(startPosition.intValue());
        final Integer to = Integer.valueOf (startPosition + size);

        final SortedMap sortedMap = repeatedTCMap.subMap(from, to);
        final List toBeCached = new ArrayList();

        // If we have found anything between the original start position and the size.
        for (Iterator j = sortedMap.keySet().iterator(); j.hasNext(); )
        {
            final Integer key = (Integer) j.next();
            final TypeCode value = (TypeCode) sortedMap.get(key);

            // only remember the offset between the start of the nested TypeCode
            // and the start of the parent TypeCode here.
            final Integer offset = Integer.valueOf(key.intValue() - startPosition.intValue());
            toBeCached.add(new TypeCodeCache.Pair(value, offset));
        }

        typeCodeCache.cacheTypeCode(repositoryID, (TypeCodeCache.Pair[])toBeCached.toArray(new TypeCodeCache.Pair[toBeCached.size()]));
    }

    /**
     * try to locate a TypeCode identified by its repository id in the TypeCodeCache.
     * as cached complex TypeCodes may contain nested TypeCodes. the repeatedTCMap
     * will be updated with entries for these TypeCodes. this is necessary so that
     * later indirections pointing to these nested TypeCodes can be resolved.
         *
     * @param repositoryID repository id of the TypeCode that should be looked up in the cache
     * @param startPosition start position in the buffer of the TypeCode that should be looked up in the cache
     * @return the cached TypeCode or null
         */
    public org.omg.CORBA.TypeCode readTypeCodeCache(String repositoryID, Integer startPosition)
    {
        final TypeCodeCache.Pair[] result = typeCodeCache.getCachedTypeCodes(repositoryID);

        if (result == null)
        {
            return null;
        }

        if (result.length == 0)
        {
            return null;
        }

        for (int i = 0; i < result.length; i++)
        {
            // calculate the position of the nested TypeCode by adding its offset
            // to the startPosition of the parent TypeCode
            final Integer position = Integer.valueOf(startPosition.intValue() + result[i].position.intValue());
            repeatedTCMap.put(position, result[i].typeCode);
        }

        return result[0].typeCode;
    }

    /**
     * used for debug/informational output
     */
    public String getIndentString()
    {
        StringBuffer buffer = new StringBuffer();

        for (int i = 0; i < typeCodeNestingLevel; i++)
        {
            buffer.append("    ");
        }

        return buffer.toString();
    }


    /**
     * <code>start_value</code> implements ValueInputStream JavaToIDL mapping 02-01-12.
     * It should read a valuetype header for a nested custom valuetype and increment
     * the valuetype nesting depth.
     */
    @Override
    public void start_value ()
    {
        int valueTag = read_long ();

        if (valueTag == 0x00000000)
        {
            // null tag, just do nothing
            return;
        }

        // check that codebase is null
        String codebase = ((valueTag & 1) != 0) ? read_codebase() : null;
        if (codebase != null)
        {
            throw new MARSHAL ("Custom marshaled value should have null codebase");
        }

        chunkedValue = ((valueTag & 0x00000008) != 0);

        // single repository ID
        read_repository_id ();

        if (chunkedValue || (valueNestingLevel > 0 && !sunInteropFix))
        {
            valueNestingLevel++;
            readChunkSizeTag();

            handle_chunking();
        }
    }

    /**
     * <code>end_value</code> implements ValueInputStream JavaToIDL mapping 02-01-12.
     * It should read the end tag for the nested custom valuetype (after skipping
     * any data that precedes the end tag) and decrements the valuetype nesting depth.
     *
     */
    @Override
    public void end_value ()
    {
        // skip rest of chunk to its end
        if (chunk_end_pos != -1 && pos > chunk_end_pos)
        {
            skip (pos - chunk_end_pos);
        }

        handle_chunking ();
    }
}
