/*
 * Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

/*
 * Licensed Materials - Property of IBM
 * RMI-IIOP v1.0
 * Copyright IBM Corp. 1998 1999  All Rights Reserved
 *
 */

package com.sun.corba.se.impl.encoding;

import java.io.Serializable;
import java.io.IOException;

import java.net.MalformedURLException;

import java.nio.ByteBuffer;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import java.math.BigDecimal;

import java.security.AccessController;
import java.security.PrivilegedExceptionAction;
import java.security.PrivilegedActionException;

import org.omg.CORBA.SystemException;
import org.omg.CORBA.portable.ValueBase;
import org.omg.CORBA.portable.IndirectionException;
import org.omg.CORBA.CompletionStatus;
import org.omg.CORBA.TCKind;
import org.omg.CORBA.TypeCodePackage.BadKind;
import org.omg.CORBA.CustomMarshal;
import org.omg.CORBA.TypeCode;
import org.omg.CORBA.Principal;
import org.omg.CORBA.Any;
import org.omg.CORBA.portable.BoxedValueHelper;
import org.omg.CORBA.portable.ValueFactory;
import org.omg.CORBA.portable.CustomValue;
import org.omg.CORBA.portable.StreamableValue;
import org.omg.CORBA.MARSHAL;
import org.omg.CORBA.portable.IDLEntity;

import javax.rmi.CORBA.Tie;
import javax.rmi.CORBA.ValueHandler;

import com.sun.corba.se.pept.protocol.MessageMediator;
import com.sun.corba.se.pept.transport.ByteBufferPool;

import com.sun.corba.se.spi.protocol.CorbaClientDelegate;

import com.sun.corba.se.spi.ior.IOR;
import com.sun.corba.se.spi.ior.IORFactories;
import com.sun.corba.se.spi.ior.iiop.GIOPVersion;

import com.sun.corba.se.spi.orb.ORB;
import com.sun.corba.se.spi.orb.ORBVersionFactory;

import com.sun.corba.se.spi.logging.CORBALogDomains;
import com.sun.corba.se.spi.presentation.rmi.PresentationManager;
import com.sun.corba.se.spi.presentation.rmi.StubAdapter;
import com.sun.corba.se.spi.presentation.rmi.PresentationDefaults;

import com.sun.corba.se.impl.logging.ORBUtilSystemException;
import com.sun.corba.se.impl.logging.OMGSystemException;

import com.sun.corba.se.impl.corba.PrincipalImpl;
import com.sun.corba.se.impl.corba.TypeCodeImpl;
import com.sun.corba.se.impl.corba.CORBAObjectImpl;

import com.sun.corba.se.impl.encoding.CDROutputObject;
import com.sun.corba.se.impl.encoding.CodeSetConversion;

import com.sun.corba.se.impl.util.Utility;
import com.sun.corba.se.impl.util.RepositoryId;

import com.sun.corba.se.impl.orbutil.RepositoryIdStrings;
import com.sun.corba.se.impl.orbutil.RepositoryIdInterface;
import com.sun.corba.se.impl.orbutil.RepositoryIdUtility;
import com.sun.corba.se.impl.orbutil.RepositoryIdFactory;

import com.sun.corba.se.impl.orbutil.ORBUtility;
import com.sun.corba.se.impl.orbutil.CacheTable;


import com.sun.org.omg.CORBA.portable.ValueHelper;

import com.sun.org.omg.SendingContext.CodeBase;

public class CDRInputStream_1_0 extends CDRInputStreamBase
    implements RestorableInputStream
{
    private static final String kReadMethod = "read";
    private static final int maxBlockLength = 0x7fffff00;

    protected BufferManagerRead bufferManagerRead;
    protected ByteBufferWithInfo bbwi;

    // Set to the ORB's transportDebugFlag value.  This value is
    // used if the ORB is null.
    private boolean debug = false;

    protected boolean littleEndian;
    protected ORB orb;
    protected ORBUtilSystemException wrapper ;
    protected OMGSystemException omgWrapper ;
    protected ValueHandler valueHandler = null;

    // Value cache
    private CacheTable valueCache = null;

    // Repository ID cache
    private CacheTable repositoryIdCache = null;

    // codebase cache
    private CacheTable codebaseCache = null;

    // Current Class Stack (repository Ids of current class being read)
    // private Stack currentStack = null;

    // Length of current chunk, or a large positive number if not in a chunk
    protected int blockLength = maxBlockLength;

    // Read end flag (value nesting depth)
    protected int end_flag = 0;

    // Beginning with the resolution to interop issue 3526 (4328?),
    // only enclosing chunked valuetypes are taken into account
    // when computing the nesting level.  However, we still need
    // the old computation around for interoperability with our
    // older ORBs.
    private int chunkedValueNestingLevel = 0;

    // Flag used to determine whether blocksize was zero
    // private int checkForNullBlock = -1;

    // In block flag
    // private boolean inBlock = false;

    // Indicates whether we are inside a value
    // private boolean outerValueDone = true;

    // Int used by read_value(Serializable) that is set by this class
    // before calling ValueFactory.read_value
    protected int valueIndirection = 0;

    // Int set by readStringOrIndirection to communicate the actual
    // offset of the string length field back to the caller
    protected int stringIndirection = 0;

    // Flag indicating whether we are unmarshalling a chunked value
    protected boolean isChunked = false;

    // Repository ID handlers
    private RepositoryIdUtility repIdUtil;
    private RepositoryIdStrings repIdStrs;

    // Code set converters (created when first needed)
    private CodeSetConversion.BTCConverter charConverter;
    private CodeSetConversion.BTCConverter wcharConverter;

    // RMI-IIOP stream format version 2 case in which we know
    // that there is no more optional data available.  If the
    // Serializable's readObject method tries to read anything,
    // we must throw a MARSHAL with the special minor code
    // so that the ValueHandler can give the correct exception
    // to readObject.  The state is cleared when the ValueHandler
    // calls end_value after the readObject method exits.
    private boolean specialNoOptionalDataState = false;

    // Template method
    public CDRInputStreamBase dup()
    {
        CDRInputStreamBase result = null ;

        try {
            result = (CDRInputStreamBase)this.getClass().newInstance();
        } catch (Exception e) {
            throw wrapper.couldNotDuplicateCdrInputStream( e ) ;
        }
        result.init(this.orb,
                    this.bbwi.byteBuffer,
                    this.bbwi.buflen,
                    this.littleEndian,
                    this.bufferManagerRead);

        ((CDRInputStream_1_0)result).bbwi.position(this.bbwi.position());
        // To ensure we keep bbwi.byteBuffer.limit in sync with bbwi.buflen.
        ((CDRInputStream_1_0)result).bbwi.byteBuffer.limit(this.bbwi.buflen);

        return result;
    }

    /**
     * NOTE:  size passed to init means buffer size
     */
    public void init(org.omg.CORBA.ORB orb,
                     ByteBuffer byteBuffer,
                     int size,
                     boolean littleEndian,
                     BufferManagerRead bufferManager)
    {
        this.orb = (ORB)orb;
        this.wrapper = ORBUtilSystemException.get( (ORB)orb,
            CORBALogDomains.RPC_ENCODING ) ;
        this.omgWrapper = OMGSystemException.get( (ORB)orb,
            CORBALogDomains.RPC_ENCODING ) ;
        this.littleEndian = littleEndian;
        this.bufferManagerRead = bufferManager;
        this.bbwi = new ByteBufferWithInfo(orb,byteBuffer,0);
        this.bbwi.buflen = size;
        this.bbwi.byteBuffer.limit(bbwi.buflen);
        this.markAndResetHandler = bufferManagerRead.getMarkAndResetHandler();

        debug = ((ORB)orb).transportDebugFlag;
    }

    // See description in CDRInputStream
    void performORBVersionSpecificInit() {
        createRepositoryIdHandlers();
    }

    private final void createRepositoryIdHandlers()
    {
        repIdUtil = RepositoryIdFactory.getRepIdUtility();
        repIdStrs = RepositoryIdFactory.getRepIdStringsFactory();
    }

    public GIOPVersion getGIOPVersion() {
        return GIOPVersion.V1_0;
    }

    // Called by Request and Reply message. Valid for GIOP versions >= 1.2 only.
    // Illegal for GIOP versions < 1.2.
    void setHeaderPadding(boolean headerPadding) {
        throw wrapper.giopVersionError();
    }

    protected final int computeAlignment(int index, int align) {
        if (align > 1) {
            int incr = index & (align - 1);
            if (incr != 0)
                return align - incr;
        }

        return 0;
    }

    public int getSize()
    {
        return bbwi.position();
    }

    protected void checkBlockLength(int align, int dataSize) {
        // Since chunks can end at arbitrary points (though not within
        // primitive CDR types, arrays of primitives, strings, wstrings,
        // or indirections),
        // we must check here for termination of the current chunk.
        if (!isChunked)
            return;

        // RMI-IIOP stream format version 2 case in which we know
        // that there is no more optional data available.  If the
        // Serializable's readObject method tries to read anything,
        // we must throw a MARSHAL exception with the special minor code
        // so that the ValueHandler can give the correct exception
        // to readObject.  The state is cleared when the ValueHandler
        // calls end_value after the readObject method exits.
        if (specialNoOptionalDataState) {
            throw omgWrapper.rmiiiopOptionalDataIncompatible1() ;
        }

        boolean checkForEndTag = false;

        // Are we at the end of the current chunk?  If so,
        // try to interpret the next long as a chunk length.
        // (It has to be either a chunk length, end tag,
        // or valuetag.)
        //
        // If it isn't a chunk length, blockLength will
        // remain set to maxBlockLength.
        if (blockLength == get_offset()) {

            blockLength = maxBlockLength;
            start_block();

            // What's next is either a valuetag or
            // an end tag.  If it's a valuetag, we're
            // probably being called as part of the process
            // to read the valuetag.  If it's an end tag,
            // then there isn't enough data left in
            // this valuetype to read!
            if (blockLength == maxBlockLength)
                checkForEndTag = true;

        } else
        if (blockLength < get_offset()) {
            // Are we already past the end of the current chunk?
            // This is always an error.
            throw wrapper.chunkOverflow() ;
        }

        // If what's next on the wire isn't a chunk length or
        // what we want to read (which can't be split across chunks)
        // won't fit in the current chunk, throw this exception.
        // This probably means that we're in an RMI-IIOP
        // Serializable's readObject method or a custom marshaled
        // IDL type is reading too much/in an incorrect order
        int requiredNumBytes =
                            computeAlignment(bbwi.position(), align) + dataSize;

        if (blockLength != maxBlockLength &&
            blockLength < get_offset() + requiredNumBytes) {
            throw omgWrapper.rmiiiopOptionalDataIncompatible2() ;
        }

        // REVISIT - We should look at using the built in advancement
        //           of using ByteBuffer.get() rather than explicitly
        //           advancing the ByteBuffer's position.
        //           This is true for anywhere we are incrementing
        //           the ByteBuffer's position.
        if (checkForEndTag) {
            int nextLong = read_long();
            bbwi.position(bbwi.position() - 4);

            // It was an end tag, so there wasn't enough data
            // left in the valuetype's encoding on the wire
            // to read what we wanted
            if (nextLong < 0)
                throw omgWrapper.rmiiiopOptionalDataIncompatible3() ;
        }
    }

    protected void alignAndCheck(int align, int n) {

        checkBlockLength(align, n);

        // WARNING: Must compute real alignment after calling
        // checkBlockLength since it may move the position
        int alignResult = computeAlignment(bbwi.position(), align);
        bbwi.position(bbwi.position() + alignResult);

        if (bbwi.position() + n > bbwi.buflen)
            grow(align, n);
    }

    //
    // This can be overridden....
    //
    protected void grow(int align, int n) {

        bbwi.needed = n;

        bbwi = bufferManagerRead.underflow(bbwi);

    }

    //
    // Marshal primitives.
    //

    public final void consumeEndian() {
        littleEndian = read_boolean();
    }

    // No such type in java
    public final double read_longdouble() {
        throw wrapper.longDoubleNotImplemented( CompletionStatus.COMPLETED_MAYBE);
    }

    public final boolean read_boolean() {
        return (read_octet() != 0);
    }

    public final char read_char() {
        alignAndCheck(1, 1);

        return getConvertedChars(1, getCharConverter())[0];
    }

    public char read_wchar() {

        // Don't allow transmission of wchar/wstring data with
        // foreign ORBs since it's against the spec.
        if (ORBUtility.isForeignORB((ORB)orb)) {
            throw wrapper.wcharDataInGiop10( CompletionStatus.COMPLETED_MAYBE);
        }

        // If we're talking to one of our legacy ORBs, do what
        // they did:
        int b1, b2;

        alignAndCheck(2, 2);

        if (littleEndian) {
            b2 = bbwi.byteBuffer.get(bbwi.position()) & 0x00FF;
            bbwi.position(bbwi.position() + 1);
            b1 = bbwi.byteBuffer.get(bbwi.position()) & 0x00FF;
            bbwi.position(bbwi.position() + 1);
        } else {
            b1 = bbwi.byteBuffer.get(bbwi.position()) & 0x00FF;
            bbwi.position(bbwi.position() + 1);
            b2 = bbwi.byteBuffer.get(bbwi.position()) & 0x00FF;
            bbwi.position(bbwi.position() + 1);
        }

        return (char)((b1 << 8) + (b2 << 0));
    }

    public final byte read_octet() {

        alignAndCheck(1, 1);

        byte b = bbwi.byteBuffer.get(bbwi.position());
        bbwi.position(bbwi.position() + 1);

        return b;
    }

    public final short read_short() {
        int b1, b2;

        alignAndCheck(2, 2);

        if (littleEndian) {
            b2 = (bbwi.byteBuffer.get(bbwi.position()) << 0) & 0x000000FF;
            bbwi.position(bbwi.position() + 1);
            b1 = (bbwi.byteBuffer.get(bbwi.position()) << 8) & 0x0000FF00;
            bbwi.position(bbwi.position() + 1);
        } else {
            b1 = (bbwi.byteBuffer.get(bbwi.position()) << 8) & 0x0000FF00;
            bbwi.position(bbwi.position() + 1);
            b2 = (bbwi.byteBuffer.get(bbwi.position()) << 0) & 0x000000FF;
            bbwi.position(bbwi.position() + 1);
        }

        return (short)(b1 | b2);
    }

    public final short read_ushort() {
        return read_short();
    }

    public final int read_long() {
        int b1, b2, b3, b4;

        alignAndCheck(4, 4);

        int bufPos = bbwi.position();
        if (littleEndian) {
            b4 = bbwi.byteBuffer.get(bufPos++) & 0xFF;
            b3 = bbwi.byteBuffer.get(bufPos++) & 0xFF;
            b2 = bbwi.byteBuffer.get(bufPos++) & 0xFF;
            b1 = bbwi.byteBuffer.get(bufPos++) & 0xFF;
        } else {
            b1 = bbwi.byteBuffer.get(bufPos++) & 0xFF;
            b2 = bbwi.byteBuffer.get(bufPos++) & 0xFF;
            b3 = bbwi.byteBuffer.get(bufPos++) & 0xFF;
            b4 = bbwi.byteBuffer.get(bufPos++) & 0xFF;
        }
        bbwi.position(bufPos);

        return (b1 << 24) | (b2 << 16) | (b3 << 8) | b4;
    }

    public final int read_ulong() {
        return read_long();
    }

    public final long read_longlong() {
        long i1, i2;

        alignAndCheck(8, 8);

        if (littleEndian) {
            i2 = read_long() & 0xFFFFFFFFL;
            i1 = (long)read_long() << 32;
        } else {
            i1 = (long)read_long() << 32;
            i2 = read_long() & 0xFFFFFFFFL;
        }

        return (i1 | i2);
    }

    public final long read_ulonglong() {
        return read_longlong();
    }

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

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

    protected final void checkForNegativeLength(int length) {
        if (length < 0)
            throw wrapper.negativeStringLength( CompletionStatus.COMPLETED_MAYBE,
                new Integer(length) ) ;
    }

    protected final String readStringOrIndirection(boolean allowIndirection) {

        int len = read_long();

        //
        // Check for indirection
        //
        if (allowIndirection) {
            if (len == 0xffffffff)
                return null;
            else
                stringIndirection = get_offset() - 4;
        }

        checkForNegativeLength(len);

        return internalReadString(len);
    }

    private final String internalReadString(int len) {
        // Workaround for ORBs which send string lengths of
        // zero to mean empty string.
        //
        // IMPORTANT: Do not replace 'new String("")' with "", it may result
        // in a Serialization bug (See serialization.zerolengthstring) and
        // bug id: 4728756 for details
        if (len == 0)
            return new String("");

        char[] result = getConvertedChars(len - 1, getCharConverter());

        // Skip over the 1 byte null
        read_octet();

        return new String(result, 0, getCharConverter().getNumChars());
    }

    public final String read_string() {
        return readStringOrIndirection(false);
    }

    public String read_wstring() {
        // Don't allow transmission of wchar/wstring data with
        // foreign ORBs since it's against the spec.
        if (ORBUtility.isForeignORB((ORB)orb)) {
            throw wrapper.wcharDataInGiop10( CompletionStatus.COMPLETED_MAYBE);
        }

        int len = read_long();

        //
        // Workaround for ORBs which send string lengths of
        // zero to mean empty string.
        //
        //
        // IMPORTANT: Do not replace 'new String("")' with "", it may result
        // in a Serialization bug (See serialization.zerolengthstring) and
        // bug id: 4728756 for details
        if (len == 0)
            return new String("");

        checkForNegativeLength(len);

        len--;
        char[] c = new char[len];

        for (int i = 0; i < len; i++)
            c[i] = read_wchar();

        // skip the two null terminator bytes
        read_wchar();
        // bbwi.position(bbwi.position() + 2);

        return new String(c);
    }

    public final void read_octet_array(byte[] b, int offset, int length) {
        if ( b == null )
            throw wrapper.nullParam() ;

        // Must call alignAndCheck at least once to ensure
        // we aren't at the end of a chunk.  Of course, we
        // should only call it if we actually need to read
        // something, otherwise we might end up with an
        // exception at the end of the stream.
        if (length == 0)
            return;

        alignAndCheck(1, 1);

        int n = offset;
        while (n < length+offset) {
            int avail;
            int bytes;
            int wanted;

            avail = bbwi.buflen - bbwi.position();
            if (avail <= 0) {
                grow(1, 1);
                avail = bbwi.buflen - bbwi.position();
            }
            wanted = (length + offset) - n;
            bytes = (wanted < avail) ? wanted : avail;
            // Microbenchmarks are showing a loop of ByteBuffer.get(int) being
            // faster than ByteBuffer.get(byte[], int, int).
            for (int i = 0; i < bytes; i++) {
                b[n+i] = bbwi.byteBuffer.get(bbwi.position() + i);
            }

            bbwi.position(bbwi.position() + bytes);

            n += bytes;
        }
    }

    public Principal read_Principal() {
        int len = read_long();
        byte[] pvalue = new byte[len];
        read_octet_array(pvalue,0,len);

        Principal p = new PrincipalImpl();
        p.name(pvalue);
        return p;
    }

    public TypeCode read_TypeCode() {
        TypeCodeImpl tc = new TypeCodeImpl(orb);
        tc.read_value(parent);
        return tc;
    }

    public Any read_any() {
        Any any = orb.create_any();
        TypeCodeImpl tc = new TypeCodeImpl(orb);

        // read off the typecode

        // REVISIT We could avoid this try-catch if we could peek the typecode
        // kind off this stream and see if it is a tk_value.  Looking at the
        // code we know that for tk_value the Any.read_value() below
        // ignores the tc argument anyway (except for the kind field).
        // But still we would need to make sure that the whole typecode,
        // including encapsulations, is read off.
        try {
            tc.read_value(parent);
        } catch (MARSHAL ex) {
            if (tc.kind().value() != TCKind._tk_value)
                throw ex;
            // We can be sure that the whole typecode encapsulation has been
            // read off.
            dprintThrowable(ex);
        }
        // read off the value of the any
        any.read_value(parent, tc);

        return any;
    }

    public org.omg.CORBA.Object read_Object() {
        return read_Object(null);
    }

    // ------------ RMI related methods --------------------------

    // IDL to Java ptc-00-01-08 1.21.4.1
    //
    // The clz argument to read_Object can be either a stub
    // Class or the "Class object for the RMI/IDL interface type
    // that is statically expected."
    // This functions as follows:
    // 1. If clz==null, just use the repository ID from the stub
    // 2. If clz is a stub class, just use it as a static factory.
    //    clz is a stub class iff StubAdapter.isStubClass( clz ).
    //    In addition, clz is a IDL stub class iff
    //    IDLEntity.class.isAssignableFrom( clz ).
    // 3. If clz is an interface, use it to create the appropriate
    //    stub factory.

    public org.omg.CORBA.Object read_Object(Class clz)
    {
        // In any case, we must first read the IOR.
        IOR ior = IORFactories.makeIOR(parent) ;
        if (ior.isNil()) {
            return null ;
        }

        PresentationManager.StubFactoryFactory sff = ORB.getStubFactoryFactory() ;
        String codeBase = ior.getProfile().getCodebase() ;
        PresentationManager.StubFactory stubFactory = null ;

        if (clz == null) {
            RepositoryId rid = RepositoryId.cache.getId( ior.getTypeId() ) ;
            String className = rid.getClassName() ;
            orb.validateIORClass(className);
            boolean isIDLInterface = rid.isIDLType() ;

            if (className == null || className.equals( "" ))
                stubFactory = null ;
            else
                try {
                    stubFactory = sff.createStubFactory( className,
                        isIDLInterface, codeBase, (Class)null,
                        (ClassLoader)null );
                } catch (Exception exc) {
                    // Could not create stubFactory, so use null.
                    // XXX stubFactory handling is still too complex:
                    // Can we resolve the stubFactory question once in
                    // a single place?
                    stubFactory = null ;
                }
        } else if (StubAdapter.isStubClass( clz )) {
            stubFactory = PresentationDefaults.makeStaticStubFactory(
                clz ) ;
        } else {
            // clz is an interface class
            boolean isIDL = IDLEntity.class.isAssignableFrom( clz ) ;
            stubFactory = sff.createStubFactory( clz.getName(),
                isIDL, codeBase, clz, clz.getClassLoader() ) ;
        }
        return internalIORToObject( ior, stubFactory, orb ) ;
    }

    /*
     * This is used as a general utility (e.g., the PortableInterceptor
     * implementation uses it.   If stubFactory is null, the ior's
     * IIOPProfile must support getServant.
     */
    public static org.omg.CORBA.Object internalIORToObject(
        IOR ior, PresentationManager.StubFactory stubFactory, ORB orb)
    {
        ORBUtilSystemException wrapper = ORBUtilSystemException.get(
            (ORB)orb, CORBALogDomains.RPC_ENCODING ) ;

        java.lang.Object servant = ior.getProfile().getServant() ;
        if (servant != null ) {
            if (servant instanceof Tie) {
                String codebase = ior.getProfile().getCodebase();
                org.omg.CORBA.Object objref = (org.omg.CORBA.Object)
                    Utility.loadStub( (Tie)servant, stubFactory, codebase,
                        false);

                // If we managed to load a stub, return it, otherwise we
                // must fail...
                if (objref != null) {
                    return objref;
                } else {
                    throw wrapper.readObjectException() ;
                }
            } else if (servant instanceof org.omg.CORBA.Object) {
                if (!(servant instanceof
                        org.omg.CORBA.portable.InvokeHandler)) {
                    return (org.omg.CORBA.Object) servant;
                }
            } else
                throw wrapper.badServantReadObject() ;
        }

        CorbaClientDelegate del = ORBUtility.makeClientDelegate( ior ) ;
        org.omg.CORBA.Object objref = null ;
        try {
            objref = stubFactory.makeStub() ;
        } catch (Throwable e) {
            wrapper.stubCreateError( e ) ;

            if (e instanceof ThreadDeath) {
                throw (ThreadDeath) e;
            }

            // Return the "default" stub...
            objref = new CORBAObjectImpl() ;
        }

        StubAdapter.setDelegate( objref, del ) ;
        return objref;
    }

    public java.lang.Object read_abstract_interface()
    {
        return read_abstract_interface(null);
    }

    public java.lang.Object read_abstract_interface(java.lang.Class clz)
    {
        boolean object = read_boolean();

        if (object) {
            return read_Object(clz);
        } else {
            return read_value();
        }
    }

    public Serializable read_value()
    {
        return read_value((Class)null);
    }

    private Serializable handleIndirection() {
        int indirection = read_long() + get_offset() - 4;
        if (valueCache != null && valueCache.containsVal(indirection)) {

            java.io.Serializable cachedValue
                = (java.io.Serializable)valueCache.getKey(indirection);
            return cachedValue;
        } else {
            // In RMI-IIOP the ValueHandler will recognize this
            // exception and use the provided indirection value
            // to lookup a possible indirection to an object
            // currently on the deserialization stack.
            throw new IndirectionException(indirection);
        }
    }

    private String readRepositoryIds(int valueTag,
                                     Class expectedType,
                                     String expectedTypeRepId) {
        return readRepositoryIds(valueTag, expectedType,
                                 expectedTypeRepId, null);
    }

    /**
     * Examines the valuetag to see how many (if any) repository IDs
     * are present on the wire.  If no repository ID information
     * is on the wire but the expectedType or expectedTypeRepId
     * is known, it will return one of those (favoring the
     * expectedType's repId). Failing that, it uses the supplied
     * BoxedValueHelper to obtain the repository ID, as a last resort.
     */
    private String readRepositoryIds(int valueTag,
                                     Class expectedType,
                                     String expectedTypeRepId,
                                     BoxedValueHelper factory) {
        switch(repIdUtil.getTypeInfo(valueTag)) {
            case RepositoryIdUtility.NO_TYPE_INFO :
                // Throw an exception if we have no repository ID info and
                // no expectedType to work with.  Otherwise, how would we
                // know what to unmarshal?
                if (expectedType == null) {
                    if (expectedTypeRepId != null) {
                        return expectedTypeRepId;
                    } else if (factory != null) {
                        return factory.get_id();
                    } else {
                        throw wrapper.expectedTypeNullAndNoRepId(
                            CompletionStatus.COMPLETED_MAYBE);
                    }
                }
                return repIdStrs.createForAnyType(expectedType);
            case RepositoryIdUtility.SINGLE_REP_TYPE_INFO :
                return read_repositoryId();
            case RepositoryIdUtility.PARTIAL_LIST_TYPE_INFO :
                return read_repositoryIds();
            default:
                throw wrapper.badValueTag( CompletionStatus.COMPLETED_MAYBE,
                    Integer.toHexString(valueTag) ) ;
        }
    }

    public Serializable read_value(Class expectedType) {

        // Read value tag
        int vType = readValueTag();

        // Is value null?
        if (vType == 0)
            return null;

        // Is this an indirection to a previously
        // read valuetype?
        if (vType == 0xffffffff)
            return handleIndirection();

        // Save where this valuetype started so we
        // can put it in the indirection valueCache
        // later
        int indirection = get_offset() - 4;

        // Need to save this special marker variable
        // to restore its value during recursion
        boolean saveIsChunked = isChunked;

        isChunked = repIdUtil.isChunkedEncoding(vType);

        java.lang.Object value = null;

        String codebase_URL = null;
        if (repIdUtil.isCodeBasePresent(vType)) {
            codebase_URL = read_codebase_URL();
        }

        // Read repository id(s)
        String repositoryIDString
            = readRepositoryIds(vType, expectedType, null);

        // If isChunked was determined to be true based
        // on the valuetag, this will read a chunk length
        start_block();

        // Remember that end_flag keeps track of all nested
        // valuetypes and is used for older ORBs
        end_flag--;
        if (isChunked)
            chunkedValueNestingLevel--;

        if (repositoryIDString.equals(repIdStrs.getWStringValueRepId())) {
            value = read_wstring();
        } else
        if (repositoryIDString.equals(repIdStrs.getClassDescValueRepId())) {
            // read in the class whether with the old ClassDesc or the
            // new one
            value = readClass();
        } else {

            Class valueClass = expectedType;

            // By this point, either the expectedType or repositoryIDString
            // is guaranteed to be non-null.
            if (expectedType == null ||
                !repositoryIDString.equals(repIdStrs.createForAnyType(expectedType))) {

                valueClass = getClassFromString(repositoryIDString,
                                                codebase_URL,
                                                expectedType);
            }

            if (valueClass == null) {
                // No point attempting to use value handler below, since the
                // class information is not available.
                throw wrapper.couldNotFindClass(
                    CompletionStatus.COMPLETED_MAYBE,
                    new ClassNotFoundException());
            }

            if (valueClass != null &&
                org.omg.CORBA.portable.IDLEntity.class.isAssignableFrom(valueClass)) {

                value =  readIDLValue(indirection,
                                      repositoryIDString,
                                      valueClass,
                                      codebase_URL);

            } else {

                // Must be some form of RMI-IIOP valuetype

                try {
                    if (valueHandler == null)
                        valueHandler = ORBUtility.createValueHandler();

                    value = valueHandler.readValue(parent,
                                                   indirection,
                                                   valueClass,
                                                   repositoryIDString,
                                                   getCodeBase());

                } catch(SystemException sysEx) {
                    // Just rethrow any CORBA system exceptions
                    // that come out of the ValueHandler
                    throw sysEx;
                } catch(Exception ex) {
                    throw wrapper.valuehandlerReadException(
                        CompletionStatus.COMPLETED_MAYBE, ex ) ;
                } catch(Error e) {
                    throw wrapper.valuehandlerReadError(
                        CompletionStatus.COMPLETED_MAYBE, e ) ;
                }
            }
        }

        // Skip any remaining chunks until we get to
        // an end tag or a valuetag.  If we see a valuetag,
        // that means there was another valuetype in the sender's
        // version of this class that we need to skip over.
        handleEndOfValue();

        // Read and process the end tag if we're chunking.
        // Assumes that we're at the position of the end tag
        // (handleEndOfValue should assure this)
        readEndTag();

        // Cache the valuetype that we read
        if (valueCache == null)
            valueCache = new CacheTable(orb,false);
        valueCache.put(value, indirection);

        // Allow for possible continuation chunk.
        // If we're a nested valuetype inside of a chunked
        // valuetype, and that enclosing valuetype has
        // more data to write, it will need to have this
        // new chunk begin after we wrote our end tag.
        isChunked = saveIsChunked;
        start_block();

        return (java.io.Serializable)value;
    }

    public Serializable read_value(BoxedValueHelper factory) {

        // Read value tag
        int vType = readValueTag();

        if (vType == 0)
            return null; // value is null
        else if (vType == 0xffffffff) { // Indirection tag
            int indirection = read_long() + get_offset() - 4;
            if (valueCache != null && valueCache.containsVal(indirection))
                {
                    java.io.Serializable cachedValue =
                           (java.io.Serializable)valueCache.getKey(indirection);
                    return cachedValue;
                }
            else {
                throw new IndirectionException(indirection);
            }
        }
        else {
            int indirection = get_offset() - 4;

            // end_block();

            boolean saveIsChunked = isChunked;
            isChunked = repIdUtil.isChunkedEncoding(vType);

            java.lang.Object value = null;

            String codebase_URL = null;
            if (repIdUtil.isCodeBasePresent(vType)){
                codebase_URL = read_codebase_URL();
            }

            // Read repository id
            String repositoryIDString
                = readRepositoryIds(vType, null, null, factory);

            // Compare rep. ids to see if we should use passed helper
            if (!repositoryIDString.equals(factory.get_id()))
                factory = Utility.getHelper(null, codebase_URL, repositoryIDString);

            start_block();
            end_flag--;
            if (isChunked)
                chunkedValueNestingLevel--;

            if (factory instanceof ValueHelper) {
                value = readIDLValueWithHelper((ValueHelper)factory, indirection);
            } else {
                valueIndirection = indirection;  // for callback
                value = factory.read_value(parent);
            }

            handleEndOfValue();
            readEndTag();

            // Put into valueCache
            if (valueCache == null)
                valueCache = new CacheTable(orb,false);
            valueCache.put(value, indirection);

            // allow for possible continuation chunk
            isChunked = saveIsChunked;
            start_block();

            return (java.io.Serializable)value;
        }
    }

    private boolean isCustomType(ValueHelper helper) {
        try{
            TypeCode tc = helper.get_type();
            int kind = tc.kind().value();
            if (kind == TCKind._tk_value) {
                return (tc.type_modifier() == org.omg.CORBA.VM_CUSTOM.value);
            }
        } catch(BadKind ex) {
            throw wrapper.badKind(ex) ;
        }

        return false;
    }

    // This method is actually called indirectly by
    // read_value(String repositoryId).
    // Therefore, it is not a truly independent read call that handles
    // header information itself.
    public java.io.Serializable read_value(java.io.Serializable value) {

        // Put into valueCache using valueIndirection
        if (valueCache == null)
            valueCache = new CacheTable(orb,false);
        valueCache.put(value, valueIndirection);

        if (value instanceof StreamableValue)
            ((StreamableValue)value)._read(parent);
        else if (value instanceof CustomValue)
            ((CustomValue)value).unmarshal(parent);

        return value;
    }

    public java.io.Serializable read_value(java.lang.String repositoryId) {

        // if (inBlock)
        //    end_block();

        // Read value tag
        int vType = readValueTag();

        if (vType == 0)
            return null; // value is null
        else if (vType == 0xffffffff) { // Indirection tag
            int indirection = read_long() + get_offset() - 4;
            if (valueCache != null && valueCache.containsVal(indirection))
                {
                    java.io.Serializable cachedValue =
                          (java.io.Serializable)valueCache.getKey(indirection);
                    return cachedValue;
                }
            else {
                throw new IndirectionException(indirection);
            }
        }
        else {
            int indirection = get_offset() - 4;

            // end_block();

            boolean saveIsChunked = isChunked;
            isChunked = repIdUtil.isChunkedEncoding(vType);

            java.lang.Object value = null;

            String codebase_URL = null;
            if (repIdUtil.isCodeBasePresent(vType)){
                codebase_URL = read_codebase_URL();
            }

            // Read repository id
            String repositoryIDString
                = readRepositoryIds(vType, null, repositoryId);

            ValueFactory factory =
               Utility.getFactory(null, codebase_URL, orb, repositoryIDString);

            start_block();
            end_flag--;
            if (isChunked)
                chunkedValueNestingLevel--;

            valueIndirection = indirection;  // for callback
            value = factory.read_value(parent);

            handleEndOfValue();
            readEndTag();

            // Put into valueCache
            if (valueCache == null)
                valueCache = new CacheTable(orb,false);
            valueCache.put(value, indirection);

            // allow for possible continuation chunk
            isChunked = saveIsChunked;
            start_block();

            return (java.io.Serializable)value;
        }
    }

    private Class readClass() {

        String codebases = null, classRepId = null;

        if (orb == null ||
            ORBVersionFactory.getFOREIGN().equals(orb.getORBVersion()) ||
            ORBVersionFactory.getNEWER().compareTo(orb.getORBVersion()) <= 0) {

            codebases = (String)read_value(java.lang.String.class);
            classRepId = (String)read_value(java.lang.String.class);
        } else {
            // Pre-Merlin/J2EE 1.3 ORBs wrote the repository ID
            // and codebase strings in the wrong order.
            classRepId = (String)read_value(java.lang.String.class);
            codebases = (String)read_value(java.lang.String.class);
        }

        if (debug) {
            dprint("readClass codebases: "
                   + codebases
                   + " rep Id: "
                   + classRepId);
        }

        Class cl = null;

        RepositoryIdInterface repositoryID
            = repIdStrs.getFromString(classRepId);

        try {
            cl = repositoryID.getClassFromType(codebases);
        } catch(ClassNotFoundException cnfe) {
            throw wrapper.cnfeReadClass( CompletionStatus.COMPLETED_MAYBE,
                cnfe, repositoryID.getClassName() ) ;
        } catch(MalformedURLException me) {
            throw wrapper.malformedUrl( CompletionStatus.COMPLETED_MAYBE,
                me, repositoryID.getClassName(), codebases ) ;
        }

        return cl;
    }

    private java.lang.Object readIDLValueWithHelper(ValueHelper helper, int indirection)
    {
        // look for two-argument static read method
        Method readMethod;
        try {
            Class argTypes[] = {org.omg.CORBA.portable.InputStream.class, helper.get_class()};
            readMethod = helper.getClass().getDeclaredMethod(kReadMethod, argTypes);
        }
        catch(NoSuchMethodException nsme) { // must be boxed value helper
            java.lang.Object result = helper.read_value(parent);
            return result;
        }

        // found two-argument read method, so must be non-boxed value...
        // ...create a blank instance
        java.lang.Object val = null;
        try {
            val = helper.get_class().newInstance();
        } catch(java.lang.InstantiationException ie) {
            throw wrapper.couldNotInstantiateHelper( ie,
                helper.get_class() ) ;
        } catch(IllegalAccessException iae){
            // Value's constructor is protected or private
            //
            // So, use the helper to read the value.
            //
            // NOTE : This means that in this particular case a recursive ref.
            // would fail.
            return helper.read_value(parent);
        }

        // add blank instance to cache table
        if (valueCache == null)
            valueCache = new CacheTable(orb,false);
        valueCache.put(val, indirection);

        // if custom type, call unmarshal method
        if (val instanceof CustomMarshal && isCustomType(helper)) {
            ((CustomMarshal)val).unmarshal(parent);
            return val;
        }

        // call two-argument read method using reflection
        try {
            java.lang.Object args[] = {parent, val};
            readMethod.invoke(helper, args);
            return val;
        } catch(IllegalAccessException iae2) {
            throw wrapper.couldNotInvokeHelperReadMethod( iae2, helper.get_class() ) ;
        } catch(InvocationTargetException ite){
            throw wrapper.couldNotInvokeHelperReadMethod( ite, helper.get_class() ) ;
        }
    }

    private java.lang.Object readBoxedIDLEntity(Class clazz, String codebase)
    {
        Class cls = null ;

        try {
            ClassLoader clazzLoader = (clazz == null ? null : clazz.getClassLoader());

            cls = Utility.loadClassForClass(clazz.getName()+"Helper", codebase,
                                                   clazzLoader, clazz, clazzLoader);
            final Class helperClass = cls ;

            final Class argTypes[] = {org.omg.CORBA.portable.InputStream.class};

            // getDeclaredMethod requires RuntimePermission accessDeclaredMembers
            // if a different class loader is used (even though the javadoc says otherwise)
            Method readMethod = null;
            try {
                readMethod = (Method)AccessController.doPrivileged(
                    new PrivilegedExceptionAction() {
                        public java.lang.Object run() throws NoSuchMethodException {
                            return helperClass.getDeclaredMethod(kReadMethod, argTypes);
                        }
                    }
                );
            } catch (PrivilegedActionException pae) {
                // this gets caught below
                throw (NoSuchMethodException)pae.getException();
            }

            java.lang.Object args[] = {parent};
            return readMethod.invoke(null, args);

        } catch (ClassNotFoundException cnfe) {
            throw wrapper.couldNotInvokeHelperReadMethod( cnfe, cls ) ;
        } catch(NoSuchMethodException nsme) {
            throw wrapper.couldNotInvokeHelperReadMethod( nsme, cls ) ;
        } catch(IllegalAccessException iae) {
            throw wrapper.couldNotInvokeHelperReadMethod( iae, cls ) ;
        } catch(InvocationTargetException ite) {
            throw wrapper.couldNotInvokeHelperReadMethod( ite, cls ) ;
        }
    }

    private java.lang.Object readIDLValue(int indirection, String repId,
                                          Class clazz, String codebase)
    {
        ValueFactory factory ;

        // Always try to find a ValueFactory first, as required by the spec.
        // There are some complications here in the IDL 3.0 mapping (see 1.13.8),
        // but basically we must always be able to override the DefaultFactory
        // or Helper mappings that are also used.  This appears to be the case
        // even in the boxed value cases.  The original code only did the lookup
        // in the case of class implementing either StreamableValue or CustomValue,
        // but abstract valuetypes only implement ValueBase, and really require
        // the use of the repId to find a factory (including the DefaultFactory).
        try {
            // use new-style OBV support (factory object)
            factory = Utility.getFactory(clazz, codebase, orb, repId);
        } catch (MARSHAL marshal) {
            // XXX log marshal at one of the INFO levels

            // Could not get a factory, so try alternatives
            if (!StreamableValue.class.isAssignableFrom(clazz) &&
                !CustomValue.class.isAssignableFrom(clazz) &&
                ValueBase.class.isAssignableFrom(clazz)) {
                // use old-style OBV support (helper object)
                BoxedValueHelper helper = Utility.getHelper(clazz, codebase, repId);
                if (helper instanceof ValueHelper)
                    return readIDLValueWithHelper((ValueHelper)helper, indirection);
                else
                    return helper.read_value(parent);
            } else {
                // must be a boxed IDLEntity, so make a reflective call to the
                // helper's static read method...
                return readBoxedIDLEntity(clazz, codebase);
            }
        }

        // If there was no error in getting the factory, use it.
        valueIndirection = indirection;  // for callback
        return factory.read_value(parent);
    }

    /**
     * End tags are only written for chunked valuetypes.
     *
     * Before Merlin, our ORBs wrote end tags which took into account
     * all enclosing valuetypes.  This was changed by an interop resolution
     * (see details around chunkedValueNestingLevel) to only include
     * enclosing chunked types.
     *
     * ORB versioning and end tag compaction are handled here.
     */
    private void readEndTag() {
        if (isChunked) {

            // Read the end tag
            int anEndTag = read_long();

            // End tags should always be negative, and the outermost
            // enclosing chunked valuetype should have a -1 end tag.
            //
            // handleEndOfValue should have assured that we were
            // at the end tag position!
            if (anEndTag >= 0) {
                throw wrapper.positiveEndTag( CompletionStatus.COMPLETED_MAYBE,
                    new Integer(anEndTag), new Integer( get_offset() - 4 ) ) ;
            }

            // If the ORB is null, or if we're sure we're talking to
            // a foreign ORB, Merlin, or something more recent, we
            // use the updated end tag computation, and are more strenuous
            // about the values.
            if (orb == null ||
                ORBVersionFactory.getFOREIGN().equals(orb.getORBVersion()) ||
                ORBVersionFactory.getNEWER().compareTo(orb.getORBVersion()) <= 0) {

                // If the end tag we read was less than what we were expecting,
                // then the sender must think it's sent more enclosing
                // chunked valuetypes than we have.  Throw an exception.
                if (anEndTag < chunkedValueNestingLevel)
                    throw wrapper.unexpectedEnclosingValuetype(
                        CompletionStatus.COMPLETED_MAYBE, new Integer( anEndTag ),
                        new Integer( chunkedValueNestingLevel ) ) ;

                // If the end tag is bigger than what we expected, but
                // still negative, then the sender has done some end tag
                // compaction.  We back up the stream 4 bytes so that the
                // next time readEndTag is called, it will get down here
                // again.  Even with fragmentation, we'll always be able
                // to do this.
                if (anEndTag != chunkedValueNestingLevel) {
                    bbwi.position(bbwi.position() - 4);
                 }

            } else {

                // When talking to Kestrel or Ladybird, we use our old
                // end tag rules and are less strict.  If the end tag
                // isn't what we expected, we back up, assuming
                // compaction.
                if (anEndTag != end_flag) {
                    bbwi.position(bbwi.position() - 4);
                }
            }

            // This only keeps track of the enclosing chunked
            // valuetypes
            chunkedValueNestingLevel++;
        }

        // This keeps track of all enclosing valuetypes
        end_flag++;
    }

    protected int get_offset() {
        return bbwi.position();
    }

    private void start_block() {

        // if (outerValueDone)
        if (!isChunked)
            return;

        // if called from alignAndCheck, need to reset blockLength
        // to avoid an infinite recursion loop on read_long() call
        blockLength = maxBlockLength;

        blockLength = read_long();

        // Must remember where we began the chunk to calculate how far
        // along we are.  See notes above about chunkBeginPos.

        if (blockLength > 0 && blockLength < maxBlockLength) {
            blockLength += get_offset();  // _REVISIT_ unsafe, should use a Java long

            // inBlock = true;
        } else {

            // System.out.println("start_block snooped a " + Integer.toHexString(blockLength));

            // not a chunk length field
            blockLength = maxBlockLength;

            bbwi.position(bbwi.position() - 4);
        }
    }

    // Makes sure that if we were reading a chunked value, we end up
    // at the right place in the stream, no matter how little the
    // unmarshalling code read.
    //
    // After calling this method, if we are chunking, we should be
    // in position to read the end tag.
    private void handleEndOfValue() {

        // If we're not chunking, we don't have to worry about
        // skipping remaining chunks or finding end tags
        if (!isChunked)
            return;

        // Skip any remaining chunks
        while (blockLength != maxBlockLength) {
            end_block();
            start_block();
        }

        // Now look for the end tag

        // This is a little wasteful since we're reading
        // this long up to 3 times in the worst cases (once
        // in start_block, once here, and once in readEndTag
        //
        // Peek next long
        int nextLong = read_long();
        bbwi.position(bbwi.position() - 4);

        // We did find an end tag, so we're done.  readEndTag
        // should take care of making sure it's the correct
        // end tag, etc.  Remember that since end tags,
        // chunk lengths, and valuetags have non overlapping
        // ranges, we can tell by the value what the longs are.
        if (nextLong < 0)
            return;

        if (nextLong == 0 || nextLong >= maxBlockLength) {

            // A custom marshaled valuetype left extra data
            // on the wire, and that data had another
            // nested value inside of it.  We've just
            // read the value tag or null of that nested value.
            //
            // In an attempt to get by it, we'll try to call
            // read_value() to get the nested value off of
            // the wire.  Afterwards, we must call handleEndOfValue
            // recursively to read any further chunks that the containing
            // valuetype might still have after the nested
            // value.
            read_value();
            handleEndOfValue();
        } else {
            // This probably means that the code to skip chunks has
            // an error, and ended up setting blockLength to something
            // other than maxBlockLength even though we weren't
            // starting a new chunk.
            throw wrapper.couldNotSkipBytes( CompletionStatus.COMPLETED_MAYBE,
                new Integer( nextLong ), new Integer( get_offset() ) ) ;
        }
    }

    private void end_block() {

        // if in a chunk, check for underflow or overflow
        if (blockLength != maxBlockLength) {
            if (blockLength == get_offset()) {
                // Chunk ended correctly
                blockLength = maxBlockLength;
            } else {
                // Skip over anything left by bad unmarshaling code (ex:
                // a buggy custom unmarshaler).  See handleEndOfValue.
                if (blockLength > get_offset()) {
                    skipToOffset(blockLength);
                } else {
                    throw wrapper.badChunkLength( new Integer( blockLength ),
                        new Integer( get_offset() ) ) ;
                }
            }
        }
    }

    private int readValueTag(){
        // outerValueDone = false;
        return read_long();
    }

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

    // ------------ End RMI related methods --------------------------

    public final void read_boolean_array(boolean[] value, int offset, int length) {
        for(int i=0; i < length; i++) {
            value[i+offset] = read_boolean();
        }
    }

    public final void read_char_array(char[] value, int offset, int length) {
        for(int i=0; i < length; i++) {
            value[i+offset] = read_char();
        }
    }

    public final void read_wchar_array(char[] value, int offset, int length) {
        for(int i=0; i < length; i++) {
            value[i+offset] = read_wchar();
        }
    }

    public final void read_short_array(short[] value, int offset, int length) {
        for(int i=0; i < length; i++) {
            value[i+offset] = read_short();
        }
    }

    public final void read_ushort_array(short[] value, int offset, int length) {
        read_short_array(value, offset, length);
    }

    public final void read_long_array(int[] value, int offset, int length) {
        for(int i=0; i < length; i++) {
            value[i+offset] = read_long();
        }
    }

    public final void read_ulong_array(int[] value, int offset, int length) {
        read_long_array(value, offset, length);
    }

    public final void read_longlong_array(long[] value, int offset, int length) {
        for(int i=0; i < length; i++) {
            value[i+offset] = read_longlong();
        }
    }

    public final void read_ulonglong_array(long[] value, int offset, int length) {
        read_longlong_array(value, offset, length);
    }

    public final void read_float_array(float[] value, int offset, int length) {
        for(int i=0; i < length; i++) {
            value[i+offset] = read_float();
        }
    }

    public final void read_double_array(double[] value, int offset, int length) {
        for(int i=0; i < length; i++) {
            value[i+offset] = read_double();
        }
    }

    public final void read_any_array(org.omg.CORBA.Any[] value, int offset, int length) {
        for(int i=0; i < length; i++) {
            value[i+offset] = read_any();
        }
    }

    //--------------------------------------------------------------------//
    // CDRInputStream state management.
    //

    /**
     * Are we at the end of the input stream?
     */
//     public final boolean isAtEnd() {
//      return bbwi.position() == bbwi.buflen;
//     }

//     public int available() throws IOException {
//         return bbwi.buflen - bbwi.position();
//     }

    private String read_repositoryIds() {

        // Read # of repository ids
        int numRepIds = read_long();
        if (numRepIds == 0xffffffff) {
            int indirection = read_long() + get_offset() - 4;
            if (repositoryIdCache != null && repositoryIdCache.containsOrderedVal(indirection))
                return (String)repositoryIdCache.getKey(indirection);
            else
                throw wrapper.unableToLocateRepIdArray( new Integer( indirection ) ) ;
        } else {

            // read first array element and store it as an indirection to the whole array
            int indirection = get_offset();
            String repID = read_repositoryId();
            if (repositoryIdCache == null)
                repositoryIdCache = new CacheTable(orb,false);
            repositoryIdCache.put(repID, indirection);

            // read and ignore the subsequent array elements, but put them in the
            // indirection table in case there are later indirections back to them
            for (int i = 1; i < numRepIds; i++) {
                read_repositoryId();
            }

            return repID;
        }
    }

    private final String read_repositoryId()
    {
        String result = readStringOrIndirection(true);

        if (result == null) { // Indirection
            int indirection = read_long() + get_offset() - 4;

            if (repositoryIdCache != null && repositoryIdCache.containsOrderedVal(indirection))
                return (String)repositoryIdCache.getKey(indirection);
            else
                throw wrapper.badRepIdIndirection( CompletionStatus.COMPLETED_MAYBE,
                    new Integer(bbwi.position()) ) ;
        } else {
            if (repositoryIdCache == null)
                repositoryIdCache = new CacheTable(orb,false);
            repositoryIdCache.put(result, stringIndirection);
        }

        return result ;
    }

    private final String read_codebase_URL()
    {
        String result = readStringOrIndirection(true);

        if (result == null) { // Indirection
            int indirection = read_long() + get_offset() - 4;

            if (codebaseCache != null && codebaseCache.containsVal(indirection))
                return (String)codebaseCache.getKey(indirection);
            else
                throw wrapper.badCodebaseIndirection(
                    CompletionStatus.COMPLETED_MAYBE,
                    new Integer(bbwi.position()) ) ;
        } else {
            if (codebaseCache == null)
                codebaseCache = new CacheTable(orb,false);
            codebaseCache.put(result, stringIndirection);
        }

        return result;
    }

    /* DataInputStream methods */

    public java.lang.Object read_Abstract () {
        return read_abstract_interface();
    }

    public java.io.Serializable read_Value () {
        return read_value();
    }

    public void read_any_array (org.omg.CORBA.AnySeqHolder seq, int offset, int length) {
        read_any_array(seq.value, offset, length);
    }

    public void read_boolean_array (org.omg.CORBA.BooleanSeqHolder seq, int offset, int length) {
        read_boolean_array(seq.value, offset, length);
    }

    public void read_char_array (org.omg.CORBA.CharSeqHolder seq, int offset, int length) {
        read_char_array(seq.value, offset, length);
    }

    public void read_wchar_array (org.omg.CORBA.WCharSeqHolder seq, int offset, int length) {
        read_wchar_array(seq.value, offset, length);
    }

    public void read_octet_array (org.omg.CORBA.OctetSeqHolder seq, int offset, int length) {
        read_octet_array(seq.value, offset, length);
    }

    public void read_short_array (org.omg.CORBA.ShortSeqHolder seq, int offset, int length) {
        read_short_array(seq.value, offset, length);
    }

    public void read_ushort_array (org.omg.CORBA.UShortSeqHolder seq, int offset, int length) {
        read_ushort_array(seq.value, offset, length);
    }

    public void read_long_array (org.omg.CORBA.LongSeqHolder seq, int offset, int length) {
        read_long_array(seq.value, offset, length);
    }

    public void read_ulong_array (org.omg.CORBA.ULongSeqHolder seq, int offset, int length) {
        read_ulong_array(seq.value, offset, length);
    }

    public void read_ulonglong_array (org.omg.CORBA.ULongLongSeqHolder seq, int offset, int length) {
        read_ulonglong_array(seq.value, offset, length);
    }

    public void read_longlong_array (org.omg.CORBA.LongLongSeqHolder seq, int offset, int length) {
        read_longlong_array(seq.value, offset, length);
    }

    public void read_float_array (org.omg.CORBA.FloatSeqHolder seq, int offset, int length) {
        read_float_array(seq.value, offset, length);
    }

    public void read_double_array (org.omg.CORBA.DoubleSeqHolder seq, int offset, int length) {
        read_double_array(seq.value, offset, length);
    }

    public java.math.BigDecimal read_fixed(short digits, short scale) {
        // digits isn't really needed here
        StringBuffer buffer = read_fixed_buffer();
        if (digits != buffer.length())
            throw wrapper.badFixed( new Integer(digits),
                new Integer(buffer.length()) ) ;
        buffer.insert(digits - scale, '.');
        return new BigDecimal(buffer.toString());
    }

    // This method is unable to yield the correct scale.
    public java.math.BigDecimal read_fixed() {
        return new BigDecimal(read_fixed_buffer().toString());
    }

    // Each octet contains (up to) two decimal digits.
    // If the fixed type has an odd number of decimal digits, then the representation
    // begins with the first (most significant) digit.
    // Otherwise, this first half-octet is all zero, and the first digit
    // is in the second half-octet.
    // The sign configuration, in the last half-octet of the representation,
    // is 0xD for negative numbers and 0xC for positive and zero values.
    private StringBuffer read_fixed_buffer() {
        StringBuffer buffer = new StringBuffer(64);
        byte doubleDigit;
        int firstDigit;
        int secondDigit;
        boolean wroteFirstDigit = false;
        boolean more = true;
        while (more) {
            doubleDigit = this.read_octet();
            firstDigit = (int)((doubleDigit & 0xf0) >> 4);
            secondDigit = (int)(doubleDigit & 0x0f);
            if (wroteFirstDigit || firstDigit != 0) {
                buffer.append(Character.forDigit(firstDigit, 10));
                wroteFirstDigit = true;
            }
            if (secondDigit == 12) {
                // positive number or zero
                if ( ! wroteFirstDigit) {
                    // zero
                    return new StringBuffer("0.0");
                } else {
                    // positive number
                    // done
                }
                more = false;
            } else if (secondDigit == 13) {
                // negative number
                buffer.insert(0, '-');
                more = false;
            } else {
                buffer.append(Character.forDigit(secondDigit, 10));
                wroteFirstDigit = true;
            }
        }
        return buffer;
    }

    private final static String _id = "IDL:omg.org/CORBA/DataInputStream:1.0";
    private final static String[] _ids = { _id };

    public String[] _truncatable_ids() {
        if (_ids == null)
            return null;

        return (String[])_ids.clone();
    }

    /* for debugging */

    public void printBuffer() {
        CDRInputStream_1_0.printBuffer(this.bbwi);
    }

    public static void printBuffer(ByteBufferWithInfo bbwi) {

        System.out.println("----- Input Buffer -----");
        System.out.println();
        System.out.println("Current position: " + bbwi.position());
        System.out.println("Total length : " + bbwi.buflen);
        System.out.println();

        try {

            char[] charBuf = new char[16];

            for (int i = 0; i < bbwi.buflen; i += 16) {

                int j = 0;

                // For every 16 bytes, there is one line
                // of output.  First, the hex output of
                // the 16 bytes with each byte separated
                // by a space.
                while (j < 16 && j + i < bbwi.buflen) {
                    int k = bbwi.byteBuffer.get(i + j);
                    if (k < 0)
                        k = 256 + k;
                    String hex = Integer.toHexString(k);
                    if (hex.length() == 1)
                        hex = "0" + hex;
                    System.out.print(hex + " ");
                    j++;
                }

                // Add any extra spaces to align the
                // text column in case we didn't end
                // at 16
                while (j < 16) {
                    System.out.print("   ");
                    j++;
                }

                // Now output the ASCII equivalents.  Non-ASCII
                // characters are shown as periods.
                int x = 0;
                while (x < 16 && x + i < bbwi.buflen) {
                    if (ORBUtility.isPrintable((char)bbwi.byteBuffer.get(i + x)))
                        charBuf[x] = (char)bbwi.byteBuffer.get(i + x);
                    else
                        charBuf[x] = '.';
                    x++;
                }
                System.out.println(new String(charBuf, 0, x));
            }

        } catch (Throwable t) {
            t.printStackTrace();
        }

        System.out.println("------------------------");
    }

    public ByteBuffer getByteBuffer() {
        ByteBuffer result = null;
        if (bbwi != null) {
            result = bbwi.byteBuffer;
        }
        return result;
    }

    public int getBufferLength() {
        return bbwi.buflen;
    }

    public void setBufferLength(int value) {
        bbwi.buflen = value;
        bbwi.byteBuffer.limit(bbwi.buflen);
    }

    public void setByteBufferWithInfo(ByteBufferWithInfo bbwi) {
        this.bbwi = bbwi;
    }

    public void setByteBuffer(ByteBuffer byteBuffer) {
        bbwi.byteBuffer = byteBuffer;
    }

    public int getIndex() {
        return bbwi.position();
    }

    public void setIndex(int value) {
        bbwi.position(value);
    }

    public boolean isLittleEndian() {
        return littleEndian;
    }

    public void orb(org.omg.CORBA.ORB orb) {
        this.orb = (ORB)orb;
    }

    public BufferManagerRead getBufferManager() {
        return bufferManagerRead;
    }

    private void skipToOffset(int offset) {

        // Number of bytes to skip
        int len = offset - get_offset();

        int n = 0;

        while (n < len) {
            int avail;
            int bytes;
            int wanted;

            avail = bbwi.buflen - bbwi.position();
            if (avail <= 0) {
                grow(1, 1);
                avail = bbwi.buflen - bbwi.position();
            }

            wanted = len - n;
            bytes = (wanted < avail) ? wanted : avail;
            bbwi.position(bbwi.position() + bytes);
            n += bytes;
        }
    }


    // Mark and reset -------------------------------------------------

    protected MarkAndResetHandler markAndResetHandler = null;

    protected class StreamMemento
    {
        // These are the fields that may change after marking
        // the stream position, so we need to save them.
        private int blockLength_;
        private int end_flag_;
        private int chunkedValueNestingLevel_;
        private int valueIndirection_;
        private int stringIndirection_;
        private boolean isChunked_;
        private javax.rmi.CORBA.ValueHandler valueHandler_;
        private ByteBufferWithInfo bbwi_;
        private boolean specialNoOptionalDataState_;

        public StreamMemento()
        {
            blockLength_ = blockLength;
            end_flag_ = end_flag;
            chunkedValueNestingLevel_ = chunkedValueNestingLevel;
            valueIndirection_ = valueIndirection;
            stringIndirection_ = stringIndirection;
            isChunked_ = isChunked;
            valueHandler_ = valueHandler;
            specialNoOptionalDataState_ = specialNoOptionalDataState;
            bbwi_ = new ByteBufferWithInfo(bbwi);
        }
    }

    public java.lang.Object createStreamMemento() {
        return new StreamMemento();
    }

    public void restoreInternalState(java.lang.Object streamMemento) {

        StreamMemento mem = (StreamMemento)streamMemento;

        blockLength = mem.blockLength_;
        end_flag = mem.end_flag_;
        chunkedValueNestingLevel = mem.chunkedValueNestingLevel_;
        valueIndirection = mem.valueIndirection_;
        stringIndirection = mem.stringIndirection_;
        isChunked = mem.isChunked_;
        valueHandler = mem.valueHandler_;
        specialNoOptionalDataState = mem.specialNoOptionalDataState_;
        bbwi = mem.bbwi_;
    }

    public int getPosition() {
        return get_offset();
    }

    public void mark(int readlimit) {
        markAndResetHandler.mark(this);
    }

    public void reset() {
        markAndResetHandler.reset();
    }

    // ---------------------------------- end Mark and Reset

    // Provides a hook so subclasses of CDRInputStream can provide
    // a CodeBase.  This ultimately allows us to grab a Connection
    // instance in IIOPInputStream, the only subclass where this
    // is actually used.
    CodeBase getCodeBase() {
        return parent.getCodeBase();
    }

    /**
     * Attempts to find the class described by the given
     * repository ID string and expected type.  The first
     * attempt is to find the class locally, falling back
     * on the URL that came with the value.  The second
     * attempt is to use a URL from the remote CodeBase.
     */
    private Class getClassFromString(String repositoryIDString,
                                     String codebaseURL,
                                     Class expectedType)
    {
        RepositoryIdInterface repositoryID
            = repIdStrs.getFromString(repositoryIDString);

        try {
            try {
                // First try to load the class locally, then use
                // the provided URL (if it isn't null)
                return repositoryID.getClassFromType(expectedType,
                                                     codebaseURL);
            } catch (ClassNotFoundException cnfeOuter) {

                try {

                    if (getCodeBase() == null) {
                        return null; // class cannot be loaded remotely.
                    }

                    // Get a URL from the remote CodeBase and retry
                    codebaseURL = getCodeBase().implementation(repositoryIDString);

                    // Don't bother trying to find it locally again if
                    // we got a null URL
                    if (codebaseURL == null)
                        return null;

                    return repositoryID.getClassFromType(expectedType,
                                                         codebaseURL);
                } catch (ClassNotFoundException cnfeInner) {
                    dprintThrowable(cnfeInner);
                    // Failed to load the class
                    return null;
                }
            }
        } catch (MalformedURLException mue) {
            // Always report a bad URL
            throw wrapper.malformedUrl( CompletionStatus.COMPLETED_MAYBE,
                mue, repositoryIDString, codebaseURL ) ;
        }
    }

    /**
     * Attempts to find the class described by the given
     * repository ID string.  At most, three attempts are made:
     * Try to find it locally, through the provided URL, and
     * finally, via a URL from the remote CodeBase.
     */
    private Class getClassFromString(String repositoryIDString,
                                     String codebaseURL)
    {
        RepositoryIdInterface repositoryID
            = repIdStrs.getFromString(repositoryIDString);

        for (int i = 0; i < 3; i++) {

            try {

                switch (i)
                {
                    case 0:
                        // First try to load the class locally
                        return repositoryID.getClassFromType();
                    case 1:
                        // Try to load the class using the provided
                        // codebase URL (falls out below)
                        break;
                    case 2:
                        // Try to load the class using a URL from the
                        // remote CodeBase
                        codebaseURL = getCodeBase().implementation(repositoryIDString);
                        break;
                }

                // Don't bother if the codebaseURL is null
                if (codebaseURL == null)
                    continue;

                return repositoryID.getClassFromType(codebaseURL);

            } catch(ClassNotFoundException cnfe) {
                // Will ultimately return null if all three
                // attempts fail, but don't do anything here.
            } catch (MalformedURLException mue) {
                throw wrapper.malformedUrl( CompletionStatus.COMPLETED_MAYBE,
                    mue, repositoryIDString, codebaseURL ) ;
            }
        }

        // If we get here, we have failed to load the class
        dprint("getClassFromString failed with rep id "
               + repositoryIDString
               + " and codebase "
               + codebaseURL);

        return null;
    }

    // Utility method used to get chars from bytes
    char[] getConvertedChars(int numBytes,
                             CodeSetConversion.BTCConverter converter) {

        // REVISIT - Look at CodeSetConversion.BTCConverter to see
        //           if it can work with an NIO ByteBuffer. We should
        //           avoid getting the bytes into an array if possible.

        // To be honest, I doubt this saves much real time
        if (bbwi.buflen - bbwi.position() >= numBytes) {
            // If the entire string is in this buffer,
            // just convert directly from the bbwi rather than
            // allocating and copying.
            byte[] tmpBuf;
            if (bbwi.byteBuffer.hasArray())
            {
                tmpBuf = bbwi.byteBuffer.array();
            }
            else
            {
                 tmpBuf = new byte[bbwi.buflen];
                 // Microbenchmarks are showing a loop of ByteBuffer.get(int)
                 // being faster than ByteBuffer.get(byte[], int, int).
                 for (int i = 0; i < bbwi.buflen; i++)
                     tmpBuf[i] = bbwi.byteBuffer.get(i);
            }
            char[] result = converter.getChars(tmpBuf,bbwi.position(),numBytes);

            bbwi.position(bbwi.position() + numBytes);
            return result;
        } else {
            // Stretches across buffers.  Unless we provide an
            // incremental conversion interface, allocate and
            // copy the bytes.
            byte[] bytes = new byte[numBytes];
            read_octet_array(bytes, 0, bytes.length);

            return converter.getChars(bytes, 0, numBytes);
        }
    }

    protected CodeSetConversion.BTCConverter getCharConverter() {
        if (charConverter == null)
            charConverter = parent.createCharBTCConverter();

        return charConverter;
    }

    protected CodeSetConversion.BTCConverter getWCharConverter() {
        if (wcharConverter == null)
            wcharConverter = parent.createWCharBTCConverter();

        return wcharConverter;
    }

    protected void dprintThrowable(Throwable t) {
        if (debug && t != null)
            t.printStackTrace();
    }

    protected void dprint(String msg) {
        if (debug) {
            ORBUtility.dprint(this, msg);
        }
    }

    /**
     * Aligns the current position on the given octet boundary
     * if there are enough bytes available to do so.  Otherwise,
     * it just returns.  This is used for some (but not all)
     * GIOP 1.2 message headers.
     */

    void alignOnBoundary(int octetBoundary) {
        int needed = computeAlignment(bbwi.position(), octetBoundary);

        if (bbwi.position() + needed <= bbwi.buflen)
        {
            bbwi.position(bbwi.position() + needed);
        }
    }

    public void resetCodeSetConverters() {
        charConverter = null;
        wcharConverter = null;
    }

    public void start_value() {
        // Read value tag
        int vType = readValueTag();

        if (vType == 0) {
            // Stream needs to go into a state where it
            // throws standard exception until end_value
            // is called.  This means the sender didn't
            // send any custom data.  If the reader here
            // tries to read more, we need to throw an
            // exception before reading beyond where
            // we're supposed to
            specialNoOptionalDataState = true;

            return;
        }

        if (vType == 0xffffffff) {
            // One should never indirect to a custom wrapper
            throw wrapper.customWrapperIndirection(
                CompletionStatus.COMPLETED_MAYBE);
        }

        if (repIdUtil.isCodeBasePresent(vType)) {
            throw wrapper.customWrapperWithCodebase(
                CompletionStatus.COMPLETED_MAYBE);
        }

        if (repIdUtil.getTypeInfo(vType)
            != RepositoryIdUtility.SINGLE_REP_TYPE_INFO) {
            throw wrapper.customWrapperNotSingleRepid(
                CompletionStatus.COMPLETED_MAYBE);
        }


        // REVISIT - Could verify repository ID even though
        // it isn't used elsewhere
        read_repositoryId();

        // Note: isChunked should be true here.  Should have
        // been set to true in the containing value's read_value
        // method.

        start_block();
        end_flag--;
        chunkedValueNestingLevel--;
    }

    public void end_value() {

        if (specialNoOptionalDataState) {
            specialNoOptionalDataState = false;
            return;
        }

        handleEndOfValue();
        readEndTag();

        // Note that isChunked should still be true here.
        // If the containing valuetype is the highest
        // chunked value, it will get set to false
        // at the end of read_value.

        // allow for possible continuation chunk
        start_block();
    }

    public void close() throws IOException
    {

        // tell BufferManagerRead to release any ByteBuffers
        getBufferManager().close(bbwi);

        // It's possible bbwi.byteBuffer is shared between
        // this InputStream and an OutputStream. Thus, we check
        // if the Input/Output streams are using the same ByteBuffer.
        // If they sharing the same ByteBuffer we need to ensure only
        // one of those ByteBuffers are released to the ByteBufferPool.

        if (bbwi != null && getByteBuffer() != null)
        {
            MessageMediator messageMediator = parent.getMessageMediator();
            if (messageMediator != null)
            {
                CDROutputObject outputObj =
                             (CDROutputObject)messageMediator.getOutputObject();
                if (outputObj != null)
                {
                    if (outputObj.isSharing(getByteBuffer()))
                    {
                        // Set OutputStream's ByteBuffer and bbwi to null
                        // so its ByteBuffer cannot be released to the pool
                        outputObj.setByteBuffer(null);
                        outputObj.setByteBufferWithInfo(null);
                    }
                }
            }

            // release this stream's ByteBuffer to the pool
            ByteBufferPool byteBufferPool = orb.getByteBufferPool();
            if (debug)
            {
                // print address of ByteBuffer being released
                int bbAddress = System.identityHashCode(bbwi.byteBuffer);
                StringBuffer sb = new StringBuffer(80);
                sb.append(".close - releasing ByteBuffer id (");
                sb.append(bbAddress).append(") to ByteBufferPool.");
                String msg = sb.toString();
                dprint(msg);
            }
            byteBufferPool.releaseByteBuffer(bbwi.byteBuffer);
            bbwi.byteBuffer = null;
            bbwi = null;
        }
    }
}
