/*
 * @(#)ByteBufferWithInfo.java	1.10 03/12/19
 *
 * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
 * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */

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

import java.nio.ByteBuffer;


import com.sun.corba.se.impl.encoding.BufferManagerWrite;
import com.sun.corba.se.impl.orbutil.ORBUtility;
import com.sun.corba.se.pept.transport.ByteBufferPool;
import com.sun.corba.se.spi.orb.ORB;


// Notes about the class.
// Assumptions, the ByteBuffer's position is set by the constructor's
// index variable and the ByteBuffer's limit points to the end of the
// data. Also, since the index variable tracks the current empty
// position in the buffer, the ByteBuffer's position is updated
// any time there's a call to this class's position().
// Although, a ByteBuffer's length is it's capacity(), the context in
// which length is used in this object, this.buflen is actually the
// ByteBuffer limit().

public class ByteBufferWithInfo
{
    private ORB orb;
    private boolean debug;
    // REVISIT - index should eventually be replaced with byteBuffer.position()
    private int     index;     // Current empty position in buffer.
    // REVISIT - CHANGE THESE TO PRIVATE
    public ByteBuffer byteBuffer;// Marshal buffer.
    public int     buflen;     // Total length of buffer. // Unnecessary...
    public int     needed;     // How many more bytes are needed on overflow.
    public boolean fragmented; // Did the overflow operation fragment?
	
    public ByteBufferWithInfo(org.omg.CORBA.ORB orb,
                              ByteBuffer byteBuffer,
                              int index)
    {
        this.orb = (com.sun.corba.se.spi.orb.ORB)orb;
        debug = this.orb.transportDebugFlag;
	this.byteBuffer = byteBuffer;
        if (byteBuffer != null)
        {
            this.buflen = byteBuffer.limit();
        }
        position(index);
	this.needed = 0;
        this.fragmented = false;
    }

    public ByteBufferWithInfo(org.omg.CORBA.ORB orb, ByteBuffer byteBuffer)
    {
	this(orb, byteBuffer, 0);
    }

    public ByteBufferWithInfo(org.omg.CORBA.ORB orb,
                              BufferManagerWrite bufferManager)
    {
        this(orb, bufferManager, true);
    }

    // Right now, EncapsOutputStream's do not use pooled byte buffers.
    // EncapsOutputStream's is the only one that does not use pooled
    // byte buffers. Hence, the reason for the boolean 'usePooledByteBuffers'.
    // See EncapsOutputStream for additional information.

    public ByteBufferWithInfo(org.omg.CORBA.ORB orb, 
                              BufferManagerWrite bufferManager,
                              boolean usePooledByteBuffers)
    {
        this.orb = (com.sun.corba.se.spi.orb.ORB)orb;
        debug = this.orb.transportDebugFlag;

        int bufferSize = bufferManager.getBufferSize();

        if (usePooledByteBuffers)
        {
            ByteBufferPool byteBufferPool = this.orb.getByteBufferPool();
            this.byteBuffer = byteBufferPool.getByteBuffer(bufferSize);

            if (debug)
            {
                // print address of ByteBuffer gotten from pool
                int bbAddress = System.identityHashCode(byteBuffer);
                StringBuffer sb = new StringBuffer(80);
                sb.append("constructor (ORB, BufferManagerWrite) - got ")
                  .append("ByteBuffer id (").append(bbAddress)
                  .append(") from ByteBufferPool.");
                String msgStr = sb.toString();
                dprint(msgStr);
            }
        }
        else
        {
             // don't allocate from pool, allocate non-direct ByteBuffer
             this.byteBuffer = ByteBuffer.allocate(bufferSize);
        }

        position(0);
        this.buflen = bufferSize;
        this.byteBuffer.limit(this.buflen);
        this.needed = 0;
        this.fragmented = false;
    }

    // Shallow copy constructor
    public ByteBufferWithInfo (ByteBufferWithInfo bbwi)
    {
        this.orb = bbwi.orb;
        this.debug = bbwi.debug;
        this.byteBuffer = bbwi.byteBuffer;
        this.buflen = bbwi.buflen;
        this.byteBuffer.limit(this.buflen);
        position(bbwi.position());
        this.needed = bbwi.needed;
        this.fragmented = bbwi.fragmented;
    }

    // So IIOPOutputStream seems more intuitive
    public int getSize() 
    {
        return position();
    }

    // accessor to buflen
    public int getLength()
    {
         return buflen;
    }

    // get position in this buffer
    public int position()
    {
        // REVISIT - This should be changed to return the
        //           value of byteBuffer.position() rather
        //           than this.index. But, byteBuffer.position
        //           is manipulated via ByteBuffer writes, reads,
        //           gets and puts. These locations need to be
        //           investigated and updated before
        //           byteBuffer.position() can be returned here.
        // return byteBuffer.position();
        return index;
    }

    // set position in this buffer
    public void position(int newPosition)
    {
        // REVISIT - This should be changed to set only the
        //           value of byteBuffer.position rather
        //           than this.index. This change should be made
        //           in conjunction with the change to this.position().
        byteBuffer.position(newPosition);
        index = newPosition;
    }

    // mutator to buflen
    public void setLength(int theLength)
    {
        buflen = theLength;
        byteBuffer.limit(buflen);
    }

    // Grow byteBuffer to a size larger than position() + needed
    public void growBuffer(com.sun.corba.se.spi.orb.ORB orb)
    {
        // This code used to live directly in CDROutputStream.grow.

        // Recall that the byteBuffer size is 'really' the limit or
        // buflen.

        int newLength = byteBuffer.limit() * 2;

        while (position() + needed >= newLength)
            newLength = newLength * 2;

        ByteBufferPool byteBufferPool = orb.getByteBufferPool();
        ByteBuffer newBB = byteBufferPool.getByteBuffer(newLength);

        if (debug)
        {
            // print address of ByteBuffer just gotten
            int newbbAddress = System.identityHashCode(newBB);
            StringBuffer sb = new StringBuffer(80);
            sb.append("growBuffer() - got ByteBuffer id (");
            sb.append(newbbAddress).append(") from ByteBufferPool.");
            String msgStr = sb.toString();
            dprint(msgStr);
        }

        byteBuffer.position(0);
        newBB.put(byteBuffer);

        // return 'old' byteBuffer reference to the ByteBuffer pool
        if (debug)
        {
            // print address of ByteBuffer being released
            int bbAddress = System.identityHashCode(byteBuffer);
            StringBuffer sb = new StringBuffer(80);
            sb.append("growBuffer() - releasing ByteBuffer id (");
            sb.append(bbAddress).append(") to ByteBufferPool.");
            String msgStr2 = sb.toString();
            dprint(msgStr2);
        }
        byteBufferPool.releaseByteBuffer(byteBuffer);

        // update the byteBuffer with a larger ByteBuffer
        byteBuffer = newBB;

        // limit and buflen must be set to newLength.
        buflen = newLength;
        byteBuffer.limit(buflen);
    }
   
    public String toString()
    {
        StringBuffer str = new StringBuffer("ByteBufferWithInfo:");

        str.append(" buflen = " + buflen);
        str.append(" byteBuffer.limit = " + byteBuffer.limit());
        str.append(" index = " + index);
        str.append(" position = " + position());
        str.append(" needed = " + needed);
        str.append(" byteBuffer = " + (byteBuffer == null ? "null" : "not null"));
        str.append(" fragmented = " + fragmented);

        return str.toString();
    }

    protected void dprint(String msg)
    {
        ORBUtility.dprint("ByteBufferWithInfo", msg);
    }
}
