/*
 *   @(#) $Id: ByteBuffer.java 358225 2005-12-21 08:17:23Z trustin $
 *
 *   Copyright 2004 The Apache Software Foundation
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 *
 */
package org.apache.mina.common;

import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamClass;
import java.io.OutputStream;
import java.nio.BufferOverflowException;
import java.nio.BufferUnderflowException;
import java.nio.ByteOrder;
import java.nio.CharBuffer;
import java.nio.DoubleBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.LongBuffer;
import java.nio.ShortBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.CoderResult;

import org.apache.mina.common.support.ByteBufferHexDumper;
import org.apache.mina.filter.codec.ProtocolEncoderOutput;
import org.apache.mina.util.Stack;

/**
 * A pooled byte buffer used by MINA applications.
 * <p>
 * This is a replacement for {@link java.nio.ByteBuffer}. Please refer to
 * {@link java.nio.ByteBuffer} and {@link java.nio.Buffer} documentation for
 * usage.  MINA does not use NIO {@link java.nio.ByteBuffer} directly for two
 * reasons:
 * <ul>
 *   <li>It doesn't provide useful getters and putters such as
 *       <code>fill</code>, <code>get/putString</code>, and
 *       <code>get/putAsciiInt()</code> enough.</li>
 *   <li>It is hard to distinguish if the buffer is created from MINA buffer
 *       pool or not.  MINA have to return used buffers back to pool.</li>
 *   <li>It is difficult to write variable-length data due to its fixed
 *       capacity</li>
 * </ul>
 * 
 * <h2>Allocation</h2>
 * <p>
 * You can get a heap buffer from buffer pool:
 * <pre>
 * ByteBuffer buf = ByteBuffer.allocate(1024, false);
 * </pre>
 * you can also get a direct buffer from buffer pool:
 * <pre>
 * ByteBuffer buf = ByteBuffer.allocate(1024, true);
 * </pre>
 * or you can let MINA choose:
 * <pre>
 * ByteBuffer buf = ByteBuffer.allocate(1024);
 * </pre>
 * 
 * <h2>Acquire/Release</h2>
 * <p>
 * <b>Please note that you never need to release the allocated buffer</b>
 * because MINA will release it automatically when:
 * <ul>
 *   <li>You pass the buffer by calling {@link IoSession#write(Object)}.</li>
 *   <li>You pass the buffer by calling {@link IoFilter.NextFilter#filterWrite(IoSession,IoFilter.WriteRequest)}.</li>
 *   <li>You pass the buffer by calling {@link ProtocolEncoderOutput#write(ByteBuffer)}.</li>
 * </ul>
 * And, you don't need to release any {@link ByteBuffer} which is passed as a parameter
 * of {@link IoHandler#messageReceived(IoSession, Object)} method.  They are released
 * automatically when the method returns.
 * <p>
 * You have to release buffers manually by calling {@link #release()} when:
 * <ul>
 *   <li>You allocated a buffer, but didn't pass the buffer to any of two methods above.</li>
 *   <li>You called {@link #acquire()} to prevent the buffer from being released.</li>
 * </ul>
 * 
 * <h2>Wrapping existing NIO buffers and arrays</h2>
 * <p>
 * This class provides a few <tt>wrap(...)</tt> methods that wraps
 * any NIO buffers and byte arrays.  Wrapped MINA buffers are not returned
 * to the buffer pool by default to prevent unexpected memory leakage by default.
 * In case you want to make it pooled, you can call {@link #setPooled(boolean)}
 * with <tt>true</tt> flag to enable pooling.
 *
 * <h2>AutoExpand</h2>
 * <p>
 * Writing variable-length data using NIO <tt>ByteBuffers</tt> is not really
 * easy, and it is because its size is fixed.  MINA <tt>ByteBuffer</tt>
 * introduces <tt>autoExpand</tt> property.  If <tt>autoExpand</tt> property
 * is true, you never get {@link BufferOverflowException} or
 * {@link IndexOutOfBoundsException} (except when index is negative).
 * It automatically expands its capacity and limit value.  For example:
 * <pre>
 * String greeting = messageBundle.getMessage( "hello" );
 * ByteBuffer buf = ByteBuffer.allocate( 16 );
 * // Turn on autoExpand (it is off by default)
 * buf.setAutoExpand( true );
 * buf.putString( greeting, utf8encoder );
 * </pre>
 * NIO <tt>ByteBuffer</tt> is reallocated by MINA <tt>ByteBuffer</tt> behind
 * the scene if the encoded data is larger than 16 bytes.  Its capacity will
 * increase by two times, and its limit will increase to the last position
 * the string is written.
 * 
 * @author The Apache Directory Project (dev@directory.apache.org)
 * @version $Rev: 358225 $, $Date: 2005-12-21 09:17:23 +0100 (mer, 21 déc 2005) $
 */
public abstract class ByteBuffer implements Comparable
{
    private static final int MINIMUM_CAPACITY = 1;

    private static final Stack containerStack = new Stack();

    private static final Stack[] heapBufferStacks = new Stack[] {
            new Stack(), new Stack(), new Stack(), new Stack(),
            new Stack(), new Stack(), new Stack(), new Stack(),
            new Stack(), new Stack(), new Stack(), new Stack(),
            new Stack(), new Stack(), new Stack(), new Stack(),
            new Stack(), new Stack(), new Stack(), new Stack(),
            new Stack(), new Stack(), new Stack(), new Stack(),
            new Stack(), new Stack(), new Stack(), new Stack(),
            new Stack(), new Stack(), new Stack(), new Stack(), };
    
    private static final Stack[] directBufferStacks = new Stack[] {
            new Stack(), new Stack(), new Stack(), new Stack(),
            new Stack(), new Stack(), new Stack(), new Stack(),
            new Stack(), new Stack(), new Stack(), new Stack(),
            new Stack(), new Stack(), new Stack(), new Stack(),
            new Stack(), new Stack(), new Stack(), new Stack(),
            new Stack(), new Stack(), new Stack(), new Stack(),
            new Stack(), new Stack(), new Stack(), new Stack(),
            new Stack(), new Stack(), new Stack(), new Stack(), };
    
    /**
     * Returns the direct or heap buffer which is capable of the specified
     * size.  This method tries to allocate direct buffer first, and then
     * tries heap buffer if direct buffer memory is exhausted.  Please use
     * {@link #allocate(int, boolean)} to allocate buffers of specific type.
     * 
     * @param capacity the capacity of the buffer
     */
    public static ByteBuffer allocate( int capacity )
    {
        try
        {
            // first try to allocate direct buffer
            return allocate( capacity, true );
        }
        catch( OutOfMemoryError e )
        {
            // if failed, try heap
            return allocate( capacity, false );
        }
    }
    
    /**
     * Returns the buffer which is capable of the specified size.
     * 
     * @param capacity the capacity of the buffer
     * @param direct <tt>true</tt> to get a direct buffer,
     *               <tt>false</tt> to get a heap buffer.
     */
    public static ByteBuffer allocate( int capacity, boolean direct )
    {
        java.nio.ByteBuffer nioBuffer = allocate0( capacity, direct );
        DefaultByteBuffer buf = allocateContainer();
        buf.init( nioBuffer, true );
        return buf;
    }

    private static DefaultByteBuffer allocateContainer()
    {
        DefaultByteBuffer buf;
        synchronized( containerStack )
        {
            buf = ( DefaultByteBuffer ) containerStack.pop();
        }
        
        if( buf == null )
        {
            buf = new DefaultByteBuffer();
        }
        return buf;
    }
    
    private static java.nio.ByteBuffer allocate0( int capacity, boolean direct )
    {
        Stack[] bufferStacks = direct? directBufferStacks : heapBufferStacks;
        int idx = getBufferStackIndex( bufferStacks, capacity );
        Stack stack = bufferStacks[ idx ];

        java.nio.ByteBuffer buf;
        synchronized( stack )
        {
            buf = ( java.nio.ByteBuffer ) stack.pop();
        }

        if( buf == null )
        {
            buf = direct ? java.nio.ByteBuffer.allocateDirect( MINIMUM_CAPACITY << idx ) :
                           java.nio.ByteBuffer.allocate( MINIMUM_CAPACITY << idx );
        }
        
        return buf;
    }
    
    private static void release0( java.nio.ByteBuffer buf )
    {
        Stack[] bufferStacks = buf.isDirect()? directBufferStacks : heapBufferStacks;
        Stack stack = bufferStacks[ getBufferStackIndex( bufferStacks, buf.capacity() ) ];
        synchronized( stack )
        {
            // push back
            stack.push( buf );
        }
    }
    
    /**
     * Wraps the specified NIO {@link java.nio.ByteBuffer} into MINA buffer.
     */
    public static ByteBuffer wrap( java.nio.ByteBuffer nioBuffer )
    {
        DefaultByteBuffer buf = allocateContainer();
        buf.init( nioBuffer, false );
        buf.setPooled( false );
        return buf;
    }
    
    /**
     * Wraps the specified byte array into MINA heap buffer.
     */
    public static ByteBuffer wrap( byte[] byteArray )
    {
        return wrap( java.nio.ByteBuffer.wrap( byteArray ) );
    }
    
    /**
     * Wraps the specified byte array into MINA heap buffer.
     * Please note that MINA buffers are going to be pooled, and
     * therefore there can be waste of memory if you wrap
     * your byte array specifying <tt>offset</tt> and <tt>length</tt>.
     */
    public static ByteBuffer wrap( byte[] byteArray, int offset, int length )
    {
        return wrap( java.nio.ByteBuffer.wrap( byteArray, offset, length ) );
    }
    
    private static int getBufferStackIndex( Stack[] bufferStacks, int size )
    {
        int targetSize = MINIMUM_CAPACITY;
        int stackIdx = 0;
        while( size > targetSize )
        {
            targetSize <<= 1;
            stackIdx ++ ;
            if( stackIdx >= bufferStacks.length )
            {
                throw new IllegalArgumentException(
                        "Buffer size is too big: " + size );
            }
        }

        return stackIdx;
    }

    protected ByteBuffer()
    {
    }

    /**
     * Increases the internal reference count of this buffer to defer
     * automatic release.  You have to invoke {@link #release()} as many
     * as you invoked this method to release this buffer.
     * 
     * @throws IllegalStateException if you attempt to acquire already
     *                               released buffer.
     */
    public abstract void acquire();

    /**
     * Releases the specified buffer to buffer pool.
     * 
     * @throws IllegalStateException if you attempt to release already
     *                               released buffer.
     */
    public abstract void release();

    /**
     * Returns the underlying NIO buffer instance.
     */
    public abstract java.nio.ByteBuffer buf();
    
    public abstract boolean isDirect();
    
    public abstract int capacity();
    
    /**
     * Returns <tt>true</tt> if and only if <tt>autoExpand</tt> is turned on.
     */
    public abstract boolean isAutoExpand();
    
    /**
     * Turns on or off <tt>autoExpand</tt>.
     */
    public abstract ByteBuffer setAutoExpand( boolean autoExpand );
    
    /**
     * Changes the capacity and limit of this buffer so this buffer get
     * the specified <tt>expectedRemaining</tt> room from the current position.
     * This method works even if you didn't set <tt>autoExpand</tt> to
     * <tt>true</tt>.
     */
    public abstract ByteBuffer expand( int expectedRemaining );
    
    /**
     * Changes the capacity and limit of this buffer so this buffer get
     * the specified <tt>expectedRemaining</tt> room from the specified
     * <tt>pos</tt>.
     * This method works even if you didn't set <tt>autoExpand</tt> to
     * <tt>true</tt>.
     */
    public abstract ByteBuffer expand( int pos, int expectedRemaining );
    
    /**
     * Returns <tt>true</tt> if and only if this buffer is returned back
     * to the buffer pool when released.
     * <p>
     * The default value of this property is <tt>true</tt> if and only if you
     * allocated this buffer using {@link #allocate(int)} or {@link #allocate(int, boolean)},
     * or <tt>false</tt> otherwise. (i.e. {@link #wrap(byte[])}, {@link #wrap(byte[], int, int)},
     * and {@link #wrap(java.nio.ByteBuffer)})
     */
    public abstract boolean isPooled();

    /**
     * Sets whether this buffer is returned back to the buffer pool when released.
     * <p>
     * The default value of this property is <tt>true</tt> if and only if you
     * allocated this buffer using {@link #allocate(int)} or {@link #allocate(int, boolean)},
     * or <tt>false</tt> otherwise. (i.e. {@link #wrap(byte[])}, {@link #wrap(byte[], int, int)},
     * and {@link #wrap(java.nio.ByteBuffer)})
     */
    public abstract void setPooled( boolean pooled );
    
    /**
     * @see java.nio.Buffer#position()
     */
    public abstract int position();

    /**
     * @see java.nio.Buffer#position(int)
     */
    public abstract ByteBuffer position( int newPosition );

    /**
     * @see java.nio.Buffer#limit()
     */
    public abstract int limit();

    /**
     * @see java.nio.Buffer#limit(int)
     */
    public abstract ByteBuffer limit( int newLimit );

    /**
     * @see java.nio.Buffer#mark()
     */
    public abstract ByteBuffer mark();

    /**
     * @see java.nio.Buffer#reset()
     */
    public abstract ByteBuffer reset();

    /**
     * @see java.nio.Buffer#clear()
     */
    public abstract ByteBuffer clear();
    
    /**
     * Clears this buffer and fills its content with <tt>NUL</tt>.
     * The position is set to zero, the limit is set to the capacity,
     * and the mark is discarded.
     */
    public ByteBuffer sweep()
    {
        clear();
        return fillAndReset( remaining() );
    }
    
    /**
     * Clears this buffer and fills its content with <tt>value</tt>.
     * The position is set to zero, the limit is set to the capacity,
     * and the mark is discarded.
     */
    public ByteBuffer sweep( byte value )
    {
        clear();
        return fillAndReset( value, remaining() );
    }

    /**
     * @see java.nio.Buffer#flip()
     */
    public abstract ByteBuffer flip();

    /**
     * @see java.nio.Buffer#rewind()
     */
    public abstract ByteBuffer rewind();

    /**
     * @see java.nio.Buffer#remaining()
     */
    public abstract int remaining();

    /**
     * @see java.nio.Buffer#hasRemaining()
     */
    public boolean hasRemaining()
    {
        return remaining() > 0;
    }

    /**
     * @see java.nio.ByteBuffer#get()
     */
    public abstract byte get();

    /**
     * Reads one unsigned byte as a short integer.
     */
    public short getUnsigned()
    {
        return ( short ) ( get() & 0xff );
    }

    /**
     * @see java.nio.ByteBuffer#put(byte)
     */
    public abstract ByteBuffer put( byte b );

    /**
     * @see java.nio.ByteBuffer#get(int)
     */
    public abstract byte get( int index );

    /**
     * Reads one byte as an unsigned short integer.
     */
    public short getUnsigned( int index )
    {
        return ( short ) ( get( index ) & 0xff );
    }

    /**
     * @see java.nio.ByteBuffer#put(int, byte)
     */
    public abstract ByteBuffer put( int index, byte b );

    /**
     * @see java.nio.ByteBuffer#get(byte[], int, int)
     */
    public abstract ByteBuffer get( byte[] dst, int offset, int length );

    /**
     * @see java.nio.ByteBuffer#get(byte[])
     */
    public ByteBuffer get( byte[] dst )
    {
        return get( dst, 0, dst.length );
    }

    /**
     * Writes the content of the specified <tt>src</tt> into this buffer.
     */
    public abstract ByteBuffer put( java.nio.ByteBuffer src );

    /**
     * Writes the content of the specified <tt>src</tt> into this buffer.
     */
    public ByteBuffer put( ByteBuffer src )
    {
        return put( src.buf() );
    }

    /**
     * @see java.nio.ByteBuffer#put(byte[], int, int)
     */
    public abstract ByteBuffer put( byte[] src, int offset, int length );

    /**
     * @see java.nio.ByteBuffer#put(byte[])
     */
    public ByteBuffer put( byte[] src )
    {
        return put( src, 0, src.length );
    }

    /**
     * @see java.nio.ByteBuffer#compact()
     */
    public abstract ByteBuffer compact();

    public String toString()
    {
        StringBuffer buf = new StringBuffer();
        if( isDirect() )
        {
            buf.append( "DirectBuffer" );
        }
        else
        {
            buf.append( "HeapBuffer" );
        }
        buf.append( "[pos=" );
        buf.append( position() );
        buf.append( " lim=" );
        buf.append( limit() );
        buf.append( " cap=" );
        buf.append( capacity() );
        buf.append( ": " );
        buf.append( getHexDump() );
        buf.append( ']' );
        return buf.toString();
    }

    public int hashCode()
    {
        int h = 1;
        int p = position();
        for( int i = limit() - 1; i >= p; i -- )
        {
            h = 31 * h + get( i );
        }
        return h;
    }

    public boolean equals( Object o )
    {
        if( !( o instanceof ByteBuffer ) )
        {
            return false;
        }
        
        ByteBuffer that = ( ByteBuffer ) o;
        if( this.remaining() != that.remaining() )
        {
            return false;
        }
        
        int p = this.position();
        for( int i = this.limit() - 1, j = that.limit() - 1; i >= p; i --, j --)
        {
            byte v1 = this.get( i );
            byte v2 = that.get( j );
            if( v1 != v2 )
            {
                return false;
            }
        }
        return true;
    }

    public int compareTo( Object o )
    {
        ByteBuffer that = ( ByteBuffer ) o;
        int n = this.position() + Math.min( this.remaining(), that.remaining() );
        for( int i = this.position(), j = that.position(); i < n; i ++, j ++ )
        {
            byte v1 = this.get( i );
            byte v2 = that.get( j );
            if( v1 == v2 )
            {
                continue;
            }
            if( v1 < v2 )
            {
                return -1;
            }
            
            return +1;
        }
        return this.remaining() - that.remaining();
    }

    /**
     * @see java.nio.ByteBuffer#order()
     */
    public abstract ByteOrder order();

    /**
     * @see java.nio.ByteBuffer#order(ByteOrder)
     */
    public abstract ByteBuffer order( ByteOrder bo );

    /**
     * @see java.nio.ByteBuffer#getChar()
     */
    public abstract char getChar();

    /**
     * @see java.nio.ByteBuffer#putChar(char)
     */
    public abstract ByteBuffer putChar( char value );

    /**
     * @see java.nio.ByteBuffer#getChar(int)
     */
    public abstract char getChar( int index );

    /**
     * @see java.nio.ByteBuffer#putChar(int, char)
     */
    public abstract ByteBuffer putChar( int index, char value );

    /**
     * @see java.nio.ByteBuffer#asCharBuffer()
     */
    public abstract CharBuffer asCharBuffer();

    /**
     * @see java.nio.ByteBuffer#getShort()
     */
    public abstract short getShort();

    /**
     * Reads two bytes unsigned integer.
     */
    public int getUnsignedShort()
    {
        return getShort() & 0xffff;
    }

    /**
     * @see java.nio.ByteBuffer#putShort(short)
     */
    public abstract ByteBuffer putShort( short value );

    /**
     * @see java.nio.ByteBuffer#getShort()
     */
    public abstract short getShort( int index );

    /**
     * Reads two bytes unsigned integer.
     */
    public int getUnsignedShort( int index )
    {
        return getShort( index ) & 0xffff;
    }

    /**
     * @see java.nio.ByteBuffer#putShort(int, short)
     */
    public abstract ByteBuffer putShort( int index, short value );

    /**
     * @see java.nio.ByteBuffer#asShortBuffer()
     */
    public abstract ShortBuffer asShortBuffer();

    /**
     * @see java.nio.ByteBuffer#getInt()
     */
    public abstract int getInt();

    /**
     * Reads four bytes unsigned integer.
     */
    public long getUnsignedInt()
    {
        return getInt() & 0xffffffffL;
    }

    /**
     * @see java.nio.ByteBuffer#putInt(int)
     */
    public abstract ByteBuffer putInt( int value );

    /**
     * @see java.nio.ByteBuffer#getInt(int)
     */
    public abstract int getInt( int index );

    /**
     * Reads four bytes unsigned integer.
     */
    public long getUnsignedInt( int index )
    {
        return getInt( index ) & 0xffffffffL;
    }

    /**
     * @see java.nio.ByteBuffer#putInt(int, int)
     */
    public abstract ByteBuffer putInt( int index, int value );

    /**
     * @see java.nio.ByteBuffer#asIntBuffer()
     */
    public abstract IntBuffer asIntBuffer();

    /**
     * @see java.nio.ByteBuffer#getLong()
     */
    public abstract long getLong();

    /**
     * @see java.nio.ByteBuffer#putLong(int, long)
     */
    public abstract ByteBuffer putLong( long value );

    /**
     * @see java.nio.ByteBuffer#getLong(int)
     */
    public abstract long getLong( int index );

    /**
     * @see java.nio.ByteBuffer#putLong(int, long)
     */
    public abstract ByteBuffer putLong( int index, long value );

    /**
     * @see java.nio.ByteBuffer#asLongBuffer()
     */
    public abstract LongBuffer asLongBuffer();

    /**
     * @see java.nio.ByteBuffer#getFloat()
     */
    public abstract float getFloat();

    /**
     * @see java.nio.ByteBuffer#putFloat(float)
     */
    public abstract ByteBuffer putFloat( float value );

    /**
     * @see java.nio.ByteBuffer#getFloat(int)
     */
    public abstract float getFloat( int index );

    /**
     * @see java.nio.ByteBuffer#putFloat(int, float)
     */
    public abstract ByteBuffer putFloat( int index, float value );

    /**
     * @see java.nio.ByteBuffer#asFloatBuffer()
     */
    public abstract FloatBuffer asFloatBuffer();

    /**
     * @see java.nio.ByteBuffer#getDouble()
     */
    public abstract double getDouble();

    /**
     * @see java.nio.ByteBuffer#putDouble(double)
     */
    public abstract ByteBuffer putDouble( double value );

    /**
     * @see java.nio.ByteBuffer#getDouble(int)
     */
    public abstract double getDouble( int index );

    /**
     * @see java.nio.ByteBuffer#putDouble(int, double)
     */
    public abstract ByteBuffer putDouble( int index, double value );

    /**
     * @see java.nio.ByteBuffer#asDoubleBuffer()
     */
    public abstract DoubleBuffer asDoubleBuffer();
    
    /**
     * Returns an {@link InputStream} that reads the data from this buffer.
     * {@link InputStream#read()} returns <tt>-1</tt> if the buffer position
     * reaches to the limit.
     */
    public InputStream asInputStream()
    {
        return new InputStream()
        {
            public int available() throws IOException
            {
                return ByteBuffer.this.remaining();
            }

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

            public boolean markSupported()
            {
                return true;
            }

            public int read() throws IOException
            {
                if( ByteBuffer.this.hasRemaining() )
                {
                    return ByteBuffer.this.get() & 0xff;
                }
                else
                {
                    return -1;
                }
            }

            public int read( byte[] b, int off, int len ) throws IOException
            {
                int remaining = ByteBuffer.this.remaining();
                if( remaining > 0 )
                {
                    int readBytes = Math.min( remaining, len );
                    ByteBuffer.this.get( b, off, readBytes );
                    return readBytes;
                }
                else
                {
                    return -1;
                }
            }

            public synchronized void reset() throws IOException
            {
                ByteBuffer.this.reset();
            }

            public long skip( long n ) throws IOException
            {
                int bytes;
                if( n > Integer.MAX_VALUE )
                {
                    bytes = ByteBuffer.this.remaining();
                }
                else
                {
                    bytes = Math.min( ByteBuffer.this.remaining(), ( int ) n );
                }
                ByteBuffer.this.skip( bytes );
                return bytes;
            }
        };
    }
    
    /**
     * Returns an {@link OutputStream} that appends the data into this buffer.
     * Please note that the {@link OutputStream#write(int)} will throw a
     * {@link BufferOverflowException} instead of an {@link IOException}
     * in case of buffer overflow.  Please set <tt>autoExpand</tt> property by
     * calling {@link #setAutoExpand(boolean)} to prevent the unexpected runtime
     * exception.
     */
    public OutputStream asOutputStream()
    {
        return new OutputStream()
        {
            public void write( byte[] b, int off, int len ) throws IOException
            {
                ByteBuffer.this.put( b, off, len );
            }

            public void write( int b ) throws IOException
            {
                ByteBuffer.this.put( ( byte ) b );
            }
        };
    }

    /**
     * Returns hexdump of this buffer.
     */
    public String getHexDump()
    {
        return ByteBufferHexDumper.getHexdump( this );
    }

    ////////////////////////////////
    // String getters and putters //
    ////////////////////////////////

    /**
     * Reads a <code>NUL</code>-terminated string from this buffer using the
     * specified <code>decoder</code> and returns it.  This method reads
     * until the limit of this buffer if no <tt>NUL</tt> is found.
     */
    public String getString( CharsetDecoder decoder ) throws CharacterCodingException
    {
        if( !hasRemaining() )
        {
            return "";
        }

        boolean utf16 = decoder.charset().name().startsWith( "UTF-16" );

        int oldPos = position();
        int oldLimit = limit();
        int end;

        if( !utf16 )
        {
            while( hasRemaining() )
            {
                if( get() == 0 )
                {
                    break;
                }
            }

            end = position();
            if( end == oldLimit )
            {
                limit( end );
            }
            else
            {
                limit( end - 1 );
            }
        }
        else
        {
            while( remaining() >= 2 )
            {
                if( ( get() == 0 ) && ( get() == 0 ) )
                {
                    break;
                }
            }

            end = position();
            if( end == oldLimit || end == oldLimit - 1 )
            {
                limit( end );
            }
            else
            {
                limit( end - 2 );
            }
        }

        position( oldPos );
        if( !hasRemaining() )
        {
            limit( oldLimit );
            position( end );
            return "";
        }
        decoder.reset();

        int expectedLength = (int) ( remaining() * decoder.averageCharsPerByte() ) + 1;
        CharBuffer out = CharBuffer.allocate( expectedLength );
        for( ;; )
        {
            CoderResult cr;
            if ( hasRemaining() )
            {
                cr = decoder.decode( buf(), out, true );
            }
            else
            {
                cr = decoder.flush( out );
            }
            
            if ( cr.isUnderflow() )
            {
                break;
            }
            
            if ( cr.isOverflow() )
            {
                CharBuffer o = CharBuffer.allocate( out.capacity() + expectedLength );
                out.flip();
                o.put(out);
                out = o;
                continue;
            }

            cr.throwException();
        }
        
        limit( oldLimit );
        position( end );
        return out.flip().toString();
    }

    /**
     * Reads a <code>NUL</code>-terminated string from this buffer using the
     * specified <code>decoder</code> and returns it.
     * 
     * @param fieldSize the maximum number of bytes to read
     */
    public String getString( int fieldSize, CharsetDecoder decoder ) throws CharacterCodingException
    {
        checkFieldSize( fieldSize );

        if( fieldSize == 0 )
        {
            return "";
        }

        if( !hasRemaining() )
        {
            return "";
        }

        boolean utf16 = decoder.charset().name().startsWith( "UTF-16" );

        if( utf16 && ( ( fieldSize & 1 ) != 0 ) )
        {
            throw new IllegalArgumentException( "fieldSize is not even." );
        }

        int i;
        int oldPos = position();
        int oldLimit = limit();
        int end = position() + fieldSize;

        if( oldLimit < end )
        {
            throw new BufferUnderflowException();
        }

        if( !utf16 )
        {
            for( i = 0; i < fieldSize; i ++ )
            {
                if( get() == 0 )
                {
                    break;
                }
            }

            if( i == fieldSize )
            {
                limit( end );
            }
            else
            {
                limit( position() - 1 );
            }
        }
        else
        {
            for( i = 0; i < fieldSize; i += 2 )
            {
                if( ( get() == 0 ) && ( get() == 0 ) )
                {
                    break;
                }
            }

            if( i == fieldSize )
            {
                limit( end );
            }
            else
            {
                limit( position() - 2 );
            }
        }

        position( oldPos );
        if( !hasRemaining() )
        {
            limit( oldLimit );
            position( end );
            return "";
        }
        decoder.reset();

        int expectedLength = (int) ( remaining() * decoder.averageCharsPerByte() ) + 1;
        CharBuffer out = CharBuffer.allocate( expectedLength );
        for( ;; )
        {
            CoderResult cr;
            if ( hasRemaining() )
            {
                cr = decoder.decode( buf(), out, true );
            }
            else
            {
                cr = decoder.flush( out );
            }
            
            if ( cr.isUnderflow() )
            {
                break;
            }
            
            if ( cr.isOverflow() )
            {
                CharBuffer o = CharBuffer.allocate( out.capacity() + expectedLength );
                out.flip();
                o.put(out);
                out = o;
                continue;
            }

            cr.throwException();
        }
        
        limit( oldLimit );
        position( end );
        return out.flip().toString();
    }
    
    /**
     * Writes the content of <code>in</code> into this buffer using the
     * specified <code>encoder</code>.  This method doesn't terminate
     * string with <tt>NUL</tt>.  You have to do it by yourself.
     * 
     * @throws BufferOverflowException if the specified string doesn't fit
     */
    public ByteBuffer putString(
            CharSequence val, CharsetEncoder encoder ) throws CharacterCodingException
    {
        if( val.length() == 0 )
        {
            return this;
        }
        
        CharBuffer in = CharBuffer.wrap( val ); 
        int expectedLength = (int) (in.remaining() * encoder.averageBytesPerChar()) + 1;

        encoder.reset();

        for (;;) {
            CoderResult cr;
            if( in.hasRemaining() )
            {
                cr = encoder.encode( in, buf(), true );
            }
            else
            {
                cr = encoder.flush( buf() );
            }
            
            if( cr.isUnderflow() )
            {
                break;
            }
            if( cr.isOverflow() && isAutoExpand() )
            {
                autoExpand( expectedLength );
                continue;
            }
            cr.throwException();
        }
        return this;
    }

    /**
     * Writes the content of <code>in</code> into this buffer as a 
     * <code>NUL</code>-terminated string using the specified
     * <code>encoder</code>.
     * <p>
     * If the charset name of the encoder is UTF-16, you cannot specify
     * odd <code>fieldSize</code>, and this method will append two
     * <code>NUL</code>s as a terminator.
     * <p>
     * Please note that this method doesn't terminate with <code>NUL</code>
     * if the input string is longer than <tt>fieldSize</tt>.
     * 
     * @param fieldSize the maximum number of bytes to write
     */
    public ByteBuffer putString(
            CharSequence val, int fieldSize, CharsetEncoder encoder ) throws CharacterCodingException
    {
        checkFieldSize( fieldSize );

        if( fieldSize == 0 )
            return this;
        
        autoExpand( fieldSize );
        
        boolean utf16 = encoder.charset().name().startsWith( "UTF-16" );

        if( utf16 && ( ( fieldSize & 1 ) != 0 ) )
        {
            throw new IllegalArgumentException( "fieldSize is not even." );
        }

        int oldLimit = limit();
        int end = position() + fieldSize;

        if( oldLimit < end )
        {
            throw new BufferOverflowException();
        }

        if( val.length() == 0 )
        {
            if( !utf16 )
            {
                put( ( byte ) 0x00 );
            }
            else
            {
                put( ( byte ) 0x00 );
                put( ( byte ) 0x00 );
            }
            position( end );
            return this;
        }
        
        CharBuffer in = CharBuffer.wrap( val ); 
        limit( end );
        encoder.reset();

        for (;;) {
            CoderResult cr;
            if( in.hasRemaining() )
            {
                cr = encoder.encode( in, buf(), true );
            }
            else
            {
                cr = encoder.flush( buf() );
            }
            
            if( cr.isUnderflow() || cr.isOverflow() )
            {
                break;
            }
            cr.throwException();
        }

        limit( oldLimit );

        if( position() < end )
        {
            if( !utf16 )
            {
                put( ( byte ) 0x00 );
            }
            else
            {
                put( ( byte ) 0x00 );
                put( ( byte ) 0x00 );
            }
        }

        position( end );
        return this;
    }

    /**
     * Reads a string which has a 16-bit length field before the actual
     * encoded string, using the specified <code>decoder</code> and returns it.
     * This method is a shortcut for <tt>getPrefixedString(2, decoder)</tt>.
     */
    public String getPrefixedString( CharsetDecoder decoder ) throws CharacterCodingException
    {
        return getPrefixedString( 2, decoder );
    }
    
    /**
     * Reads a string which has a length field before the actual
     * encoded string, using the specified <code>decoder</code> and returns it.
     * 
     * @param prefixLength the length of the length field (1, 2, or 4)
     */
    public String getPrefixedString( int prefixLength, CharsetDecoder decoder ) throws CharacterCodingException
    {
        if( !prefixedDataAvailable( prefixLength ) )
        {
            throw new BufferUnderflowException();
        }

        int fieldSize = 0;
        
        switch( prefixLength )
        {
        case 1:
            fieldSize = getUnsigned();
            break;
        case 2:
            fieldSize = getUnsignedShort();
            break;
        case 4:
            fieldSize = getInt();
            break;
        }
        
        if( fieldSize == 0 )
        {
            return "";
        }

        boolean utf16 = decoder.charset().name().startsWith( "UTF-16" );

        if( utf16 && ( ( fieldSize & 1 ) != 0 ) )
        {
            throw new BufferDataException( "fieldSize is not even for a UTF-16 string." );
        }

        int oldLimit = limit();
        int end = position() + fieldSize;

        if( oldLimit < end )
        {
            throw new BufferUnderflowException();
        }

        limit( end );
        decoder.reset();

        int expectedLength = (int) ( remaining() * decoder.averageCharsPerByte() ) + 1;
        CharBuffer out = CharBuffer.allocate( expectedLength );
        for( ;; )
        {
            CoderResult cr;
            if ( hasRemaining() )
            {
                cr = decoder.decode( buf(), out, true );
            }
            else
            {
                cr = decoder.flush( out );
            }
            
            if ( cr.isUnderflow() )
            {
                break;
            }
            
            if ( cr.isOverflow() )
            {
                CharBuffer o = CharBuffer.allocate( out.capacity() + expectedLength );
                out.flip();
                o.put(out);
                out = o;
                continue;
            }

            cr.throwException();
        }
        
        limit( oldLimit );
        position( end );
        return out.flip().toString();
    }

    /**
     * Writes the content of <code>in</code> into this buffer as a 
     * string which has a 16-bit length field before the actual
     * encoded string, using the specified <code>encoder</code>.
     * This method is a shortcut for <tt>putPrefixedString(in, 2, 0, encoder)</tt>.
     * 
     * @throws BufferOverflowException if the specified string doesn't fit
     */
    public ByteBuffer putPrefixedString( CharSequence in, CharsetEncoder encoder ) throws CharacterCodingException
    {
        return putPrefixedString( in, 2, 0, encoder );
    }
    
    /**
     * Writes the content of <code>in</code> into this buffer as a 
     * string which has a 16-bit length field before the actual
     * encoded string, using the specified <code>encoder</code>.
     * This method is a shortcut for <tt>putPrefixedString(in, prefixLength, 0, encoder)</tt>.
     * 
     * @param prefixLength the length of the length field (1, 2, or 4)
     * 
     * @throws BufferOverflowException if the specified string doesn't fit
     */
    public ByteBuffer putPrefixedString( CharSequence in, int prefixLength, CharsetEncoder encoder ) throws CharacterCodingException
    {
        return putPrefixedString( in, prefixLength, 0, encoder );
    }
    
    /**
     * Writes the content of <code>in</code> into this buffer as a 
     * string which has a 16-bit length field before the actual
     * encoded string, using the specified <code>encoder</code>.
     * This method is a shortcut for <tt>putPrefixedString(in, prefixLength, padding, ( byte ) 0, encoder)</tt>.
     * 
     * @param prefixLength the length of the length field (1, 2, or 4)
     * @param padding the number of padded <tt>NUL</tt>s (1 (or 0), 2, or 4)
     *  
     * @throws BufferOverflowException if the specified string doesn't fit
     */
    public ByteBuffer putPrefixedString( CharSequence in, int prefixLength, int padding, CharsetEncoder encoder ) throws CharacterCodingException
    {
        return putPrefixedString( in, prefixLength, padding, ( byte ) 0, encoder );
    }
    
    /**
     * Writes the content of <code>in</code> into this buffer as a 
     * string which has a 16-bit length field before the actual
     * encoded string, using the specified <code>encoder</code>.
     * 
     * @param prefixLength the length of the length field (1, 2, or 4)
     * @param padding the number of padded bytes (1 (or 0), 2, or 4)
     * @param padValue the value of padded bytes
     *  
     * @throws BufferOverflowException if the specified string doesn't fit
     */
    public ByteBuffer putPrefixedString( CharSequence val, int prefixLength, int padding, byte padValue, CharsetEncoder encoder ) throws CharacterCodingException
    {
        int maxLength;
        switch( prefixLength )
        {
        case 1:
            maxLength = 255;
            break;
        case 2:
            maxLength = 65535;
            break;
        case 4:
            maxLength = Integer.MAX_VALUE;
            break;
        default:
            throw new IllegalArgumentException( "prefixLength: " + prefixLength );
        }
        
        if( val.length() > maxLength )
        {
            throw new IllegalArgumentException( "The specified string is too long." );
        }
        if( val.length() == 0 )
        {
            switch( prefixLength )
            {
            case 1:
                put( ( byte ) 0 );
                break;
            case 2:
                putShort( ( short ) 0 );
                break;
            case 4:
                putInt( 0 );
                break;
            }
            return this;
        }
        
        int padMask;
        switch( padding )
        {
        case 0:
        case 1:
            padMask = 0;
            break;
        case 2:
            padMask = 1;
            break;
        case 4:
            padMask = 3;
            break;
        default:
            throw new IllegalArgumentException( "padding: " + padding );
        }

        CharBuffer in = CharBuffer.wrap( val ); 
        int expectedLength = (int) (in.remaining() * encoder.averageBytesPerChar()) + 1;

        skip( prefixLength ); // make a room for the length field
        int oldPos = position();
        encoder.reset();

        for (;;) {
            CoderResult cr;
            if( in.hasRemaining() )
            {
                cr = encoder.encode( in, buf(), true );
            }
            else
            {
                cr = encoder.flush( buf() );
            }
            
            if( position() - oldPos > maxLength )
            {
                throw new IllegalArgumentException( "The specified string is too long." );
            }
            
            if( cr.isUnderflow() )
            {
                break;
            }
            if( cr.isOverflow() && isAutoExpand() )
            {
                autoExpand( expectedLength );
                continue;
            }
            cr.throwException();
        }
        
        // Write the length field
        fill( padValue, padding - ( ( position() - oldPos ) & padMask ) );
        int length = position() - oldPos;
        switch( prefixLength )
        {
        case 1:
            put( oldPos - 1, ( byte ) length );
            break;
        case 2:
            putShort( oldPos - 2, ( short ) length );
            break;
        case 4:
            putInt( oldPos - 4, length );
            break;
        }
        return this;
    }
    
    /**
     * Reads a Java object from the buffer using the context {@link ClassLoader}
     * of the current thread.
     */
    public Object getObject() throws ClassNotFoundException
    {
        return getObject( Thread.currentThread().getContextClassLoader() );
    }
    
    /**
     * Reads a Java object from the buffer using the specified <tt>classLoader</tt>.
     */
    public Object getObject( final ClassLoader classLoader ) throws ClassNotFoundException
    {
        if( !prefixedDataAvailable( 4 ) )
        {
            throw new BufferUnderflowException();
        }

        int length = getInt();
        if( length <= 4 )
        {
            throw new BufferDataException( "Object length should be greater than 4: " + length );
        }

        int oldLimit = limit();
        limit( position() + length );
        try
        {
            ObjectInputStream in = new ObjectInputStream( asInputStream() )
            {
                protected ObjectStreamClass readClassDescriptor() throws IOException, ClassNotFoundException
                {
                    String className = readUTF();
                    Class  clazz = Class.forName( className, true, classLoader );
                    ObjectStreamClass descriptor = ObjectStreamClass.lookup(clazz);
                    return descriptor;
                }
            };
            return in.readObject();
        }
        catch( IOException e )
        {
            throw new BufferDataException( e );
        }
        finally
        {
            limit( oldLimit );
        }
    }
    
    /**
     * Writes the specified Java object to the buffer.
     */
    public ByteBuffer putObject( Object o )
    {
        int oldPos = position();
        skip( 4 ); // Make a room for the length field.
        try
        {
            ObjectOutputStream out = new ObjectOutputStream( asOutputStream() )
            {
                protected void writeClassDescriptor( ObjectStreamClass desc ) throws IOException
                {
                    writeUTF( desc.getName() );
                }
            };
            out.writeObject( o );
            out.flush();
        }
        catch( IOException e )
        {
            throw new BufferDataException( e );
        }
        
        // Fill the length field
        int newPos = position();
        position( oldPos );
        putInt( newPos - oldPos - 4 );
        position( newPos );
        return this;
    }
    
    /**
     * Returns <tt>true</tt> if this buffer contains a data which has a data
     * length as a prefix and the buffer has remaining data as enough as
     * specified in the data length field.  This method is identical with
     * <tt>prefixedDataAvailable( prefixLength, Integer.MAX_VALUE )</tt>.
     * Please not that using this method can allow DoS (Denial of Service)
     * attack in case the remote peer sends too big data length value.
     * It is recommended to use {@link #prefixedDataAvailable(int, int)}
     * instead. 
     * 
     * @param prefixLength the length of the prefix field (1, 2, or 4)
     * 
     * @throws IllegalArgumentException if prefixLength is wrong
     * @throws BufferDataException if data length is negative
     */
    public boolean prefixedDataAvailable( int prefixLength )
    {
        return prefixedDataAvailable( prefixLength, Integer.MAX_VALUE );
    }
    
    /**
     * Returns <tt>true</tt> if this buffer contains a data which has a data
     * length as a prefix and the buffer has remaining data as enough as
     * specified in the data length field.
     * 
     * @param prefixLength the length of the prefix field (1, 2, or 4)
     * @param maxDataLength the allowed maximum of the read data length
     * 
     * @throws IllegalArgumentException if prefixLength is wrong
     * @throws BufferDataException if data length is negative or greater then <tt>maxDataLength</tt>
     */
    public boolean prefixedDataAvailable( int prefixLength, int maxDataLength )
    {
        if( remaining() < prefixLength )
        {
            return false;
        }

        int dataLength;
        switch( prefixLength )
        {
        case 1:
            dataLength = getUnsigned( position() );
            break;
        case 2:
            dataLength = getUnsignedShort( position() );
            break;
        case 4:
            dataLength = getInt( position() );
            break;
        default:
            throw new IllegalArgumentException( "prefixLength: " + prefixLength );
        }
        
        if( dataLength < 0 || dataLength > maxDataLength )
        {
            throw new BufferDataException( "dataLength: " + dataLength );
        }
        
        return remaining() - prefixLength >= dataLength;
    }

    //////////////////////////
    // Skip or fill methods //
    //////////////////////////

    /**
     * Forwards the position of this buffer as the specified <code>size</code>
     * bytes.
     */
    public ByteBuffer skip( int size )
    {
        autoExpand( size );
        return position( position() + size );
    }

    /**
     * Fills this buffer with the specified value.
     * This method moves buffer position forward.
     */
    public ByteBuffer fill( byte value, int size )
    {
        autoExpand( size );
        int q = size >>> 3;
        int r = size & 7;

        if( q > 0 )
        {
            int intValue = value | ( value << 8 ) | ( value << 16 )
                           | ( value << 24 );
            long longValue = intValue;
            longValue <<= 32;
            longValue |= intValue;

            for( int i = q; i > 0; i -- )
            {
                putLong( longValue );
            }
        }

        q = r >>> 2;
        r = r & 3;

        if( q > 0 )
        {
            int intValue = value | ( value << 8 ) | ( value << 16 )
                           | ( value << 24 );
            putInt( intValue );
        }

        q = r >> 1;
        r = r & 1;

        if( q > 0 )
        {
            short shortValue = ( short ) ( value | ( value << 8 ) );
            putShort( shortValue );
        }

        if( r > 0 )
        {
            put( value );
        }

        return this;
    }

    /**
     * Fills this buffer with the specified value.
     * This method does not change buffer position.
     */
    public ByteBuffer fillAndReset( byte value, int size )
    {
        autoExpand( size );
        int pos = position();
        try
        {
            fill( value, size );
        }
        finally
        {
            position( pos );
        }
        return this;
    }

    /**
     * Fills this buffer with <code>NUL (0x00)</code>.
     * This method moves buffer position forward.
     */
    public ByteBuffer fill( int size )
    {
        autoExpand( size );
        int q = size >>> 3;
        int r = size & 7;

        for( int i = q; i > 0; i -- )
        {
            putLong( 0L );
        }

        q = r >>> 2;
        r = r & 3;

        if( q > 0 )
        {
            putInt( 0 );
        }

        q = r >> 1;
        r = r & 1;

        if( q > 0 )
        {
            putShort( ( short ) 0 );
        }

        if( r > 0 )
        {
            put( ( byte ) 0 );
        }

        return this;
    }

    /**
     * Fills this buffer with <code>NUL (0x00)</code>.
     * This method does not change buffer position.
     */
    public ByteBuffer fillAndReset( int size )
    {
        autoExpand( size );
        int pos = position();
        try
        {
            fill( size );
        }
        finally
        {
            position( pos );
        }

        return this;
    }

    /**
     * This method forwards the call to {@link #expand(int)} only when
     * <tt>autoExpand</tt> property is <tt>true</tt>.
     */
    protected ByteBuffer autoExpand( int expectedRemaining )
    {
        if( isAutoExpand() )
        {
            expand( expectedRemaining );
        }
        return this;
    }
    
    /**
     * This method forwards the call to {@link #expand(int)} only when
     * <tt>autoExpand</tt> property is <tt>true</tt>.
     */
    protected ByteBuffer autoExpand( int pos, int expectedRemaining )
    {
        if( isAutoExpand() )
        {
            expand( pos, expectedRemaining );
        }
        return this;
    }
    

    private static void checkFieldSize( int fieldSize )
    {
        if( fieldSize < 0 )
        {
            throw new IllegalArgumentException(
                    "fieldSize cannot be negative: " + fieldSize );
        }
    }
    
    private static class DefaultByteBuffer extends ByteBuffer
    {
        private java.nio.ByteBuffer buf;
        private int refCount = 1;
        private boolean autoExpand;
        private boolean pooled;

        protected DefaultByteBuffer()
        {
        }

        private synchronized void init( java.nio.ByteBuffer buf, boolean clear )
        {
            this.buf = buf;
            if( clear )
            {
                buf.clear();
            }
            buf.order( ByteOrder.BIG_ENDIAN );
            autoExpand = false;
            pooled = true;
            refCount = 1;
        }
        
        public synchronized void acquire()
        {
            if( refCount <= 0 )
            {
                throw new IllegalStateException( "Already released buffer." );
            }

            refCount ++;
        }

        public void release()
        {
            synchronized( this )
            {
                if( refCount <= 0 )
                {
                    refCount = 0;
                    throw new IllegalStateException(
                            "Already released buffer.  You released the buffer too many times." );
                }

                refCount --;
                if( refCount > 0)
                {
                    return;
                }
            }

            if( pooled )
            {
                release0( buf );
            }

            synchronized( containerStack )
            {
                containerStack.push( this );
            }
        }

        public java.nio.ByteBuffer buf()
        {
            return buf;
        }
        
        public boolean isDirect()
        {
            return buf.isDirect();
        }
        
        public boolean isReadOnly()
        {
            return buf.isReadOnly();
        }
        
        public boolean isAutoExpand()
        {
            return autoExpand;
        }
        
        public ByteBuffer setAutoExpand( boolean autoExpand )
        {
            this.autoExpand = autoExpand;
            return this;
        }
        
        public boolean isPooled()
        {
            return pooled;
        }
        
        public void setPooled( boolean pooled )
        {
            this.pooled = pooled;
        }

        public int capacity()
        {
            return buf.capacity();
        }
        
        public int position()
        {
            return buf.position();
        }

        public ByteBuffer position( int newPosition )
        {
            autoExpand( newPosition, 0 );
            buf.position( newPosition );
            return this;
        }

        public int limit()
        {
            return buf.limit();
        }

        public ByteBuffer limit( int newLimit )
        {
            autoExpand( newLimit, 0 );
            buf.limit( newLimit );
            return this;
        }

        public ByteBuffer mark()
        {
            buf.mark();
            return this;
        }

        public ByteBuffer reset()
        {
            buf.reset();
            return this;
        }

        public ByteBuffer clear()
        {
            buf.clear();
            return this;
        }

        public ByteBuffer flip()
        {
            buf.flip();
            return this;
        }

        public ByteBuffer rewind()
        {
            buf.rewind();
            return this;
        }

        public int remaining()
        {
            return buf.remaining();
        }

        public byte get()
        {
            return buf.get();
        }

        public ByteBuffer put( byte b )
        {
            autoExpand( 1 );
            buf.put( b );
            return this;
        }

        public byte get( int index )
        {
            return buf.get( index );
        }

        public ByteBuffer put( int index, byte b )
        {
            autoExpand( index, 1 );
            buf.put( index, b );
            return this;
        }

        public ByteBuffer get( byte[] dst, int offset, int length )
        {
            buf.get( dst, offset, length );
            return this;
        }

        public ByteBuffer put( java.nio.ByteBuffer src )
        {
            autoExpand( src.remaining() );
            buf.put( src );
            return this;
        }

        public ByteBuffer put( byte[] src, int offset, int length )
        {
            autoExpand( length );
            buf.put( src, offset, length );
            return this;
        }

        public ByteBuffer compact()
        {
            buf.compact();
            return this;
        }

        public int compareTo( ByteBuffer that )
        {
            return this.buf.compareTo( that.buf() );
        }

        public ByteOrder order()
        {
            return buf.order();
        }

        public ByteBuffer order( ByteOrder bo )
        {
            buf.order( bo );
            return this;
        }

        public char getChar()
        {
            return buf.getChar();
        }

        public ByteBuffer putChar( char value )
        {
            autoExpand( 2 );
            buf.putChar( value );
            return this;
        }

        public char getChar( int index )
        {
            return buf.getChar( index );
        }

        public ByteBuffer putChar( int index, char value )
        {
            autoExpand( index, 2 );
            buf.putChar( index, value );
            return this;
        }

        public CharBuffer asCharBuffer()
        {
            return buf.asCharBuffer();
        }

        public short getShort()
        {
            return buf.getShort();
        }

        public ByteBuffer putShort( short value )
        {
            autoExpand( 2 );
            buf.putShort( value );
            return this;
        }

        public short getShort( int index )
        {
            return buf.getShort( index );
        }

        public ByteBuffer putShort( int index, short value )
        {
            autoExpand( index, 2 );
            buf.putShort( index, value );
            return this;
        }

        public ShortBuffer asShortBuffer()
        {
            return buf.asShortBuffer();
        }

        public int getInt()
        {
            return buf.getInt();
        }

        public ByteBuffer putInt( int value )
        {
            autoExpand( 4 );
            buf.putInt( value );
            return this;
        }

        public int getInt( int index )
        {
            return buf.getInt( index );
        }

        public ByteBuffer putInt( int index, int value )
        {
            autoExpand( index, 4 );
            buf.putInt( index, value );
            return this;
        }

        public IntBuffer asIntBuffer()
        {
            return buf.asIntBuffer();
        }

        public long getLong()
        {
            return buf.getLong();
        }

        public ByteBuffer putLong( long value )
        {
            autoExpand( 8 );
            buf.putLong( value );
            return this;
        }

        public long getLong( int index )
        {
            return buf.getLong( index );
        }

        public ByteBuffer putLong( int index, long value )
        {
            autoExpand( index, 8 );
            buf.putLong( index, value );
            return this;
        }

        public LongBuffer asLongBuffer()
        {
            return buf.asLongBuffer();
        }

        public float getFloat()
        {
            return buf.getFloat();
        }

        public ByteBuffer putFloat( float value )
        {
            autoExpand( 4 );
            buf.putFloat( value );
            return this;
        }

        public float getFloat( int index )
        {
            return buf.getFloat( index );
        }

        public ByteBuffer putFloat( int index, float value )
        {
            autoExpand( index, 4 );
            buf.putFloat( index, value );
            return this;
        }

        public FloatBuffer asFloatBuffer()
        {
            return buf.asFloatBuffer();
        }

        public double getDouble()
        {
            return buf.getDouble();
        }

        public ByteBuffer putDouble( double value )
        {
            autoExpand( 8 );
            buf.putDouble( value );
            return this;
        }

        public double getDouble( int index )
        {
            return buf.getDouble( index );
        }

        public ByteBuffer putDouble( int index, double value )
        {
            autoExpand( index, 8 );
            buf.putDouble( index, value );
            return this;
        }

        public DoubleBuffer asDoubleBuffer()
        {
            return buf.asDoubleBuffer();
        }

        public ByteBuffer expand( int expectedRemaining )
        {
            if( autoExpand )
            {
                int pos = buf.position();
                int limit = buf.limit();
                int end = pos + expectedRemaining;
                if( end > limit ) {
                    ensureCapacity( end );
                    buf.limit( end );
                }
            }
            return this;
        }
        
        public ByteBuffer expand( int pos, int expectedRemaining )
        {
            if( autoExpand )
            {
                int limit = buf.limit();
                int end = pos + expectedRemaining;
                if( end > limit ) {
                    ensureCapacity( end );
                    buf.limit( end );
                }
            }
            return this;
        }
        
        private void ensureCapacity( int requestedCapacity )
        {
            if( requestedCapacity <= buf.capacity() )
            {
                return;
            }
            
            int newCapacity = MINIMUM_CAPACITY;
            while( newCapacity < requestedCapacity )
            {
                newCapacity <<= 1;
            }
            
            java.nio.ByteBuffer oldBuf = this.buf;
            java.nio.ByteBuffer newBuf = allocate0( newCapacity, isDirect() );
            newBuf.clear();
            newBuf.order( oldBuf.order() );

            int pos = oldBuf.position();
            int limit = oldBuf.limit();
            oldBuf.clear();
            newBuf.put( oldBuf );
            newBuf.position( 0 );
            newBuf.limit( limit );
            newBuf.position( pos );
            this.buf = newBuf;
            release0( oldBuf );
        }
    }
    
    // JV : monitoring informations
    /**
     * total Bytes used for direct memory pool
     */
    public static long getDirectMemoryUsage() {
    	long directMem=0;
    	for(int i=0;i<directBufferStacks.length;i++) {
    		directMem+=directBufferStacks[i].size()*(MINIMUM_CAPACITY << i);
    	}
    	return directMem;
    }
    
    /**
     * total Bytes used for heap memory pool
     */    
    public static long getHeapMemoryUsage() {
    	long heapMem=0;
    	for(int i=0;i<heapBufferStacks.length;i++) {
    		heapMem+=heapBufferStacks[i].size()*(MINIMUM_CAPACITY << i);
    	}
    	return heapMem;
    }
    
    /**
     * get the number of ByteBuffer allocated for each kind of direct memory ByteBuffer length
     */
    public static int[] getCountOfDirectMemoryBuffers() {
    	int[] res=new int[directBufferStacks.length];
    	for(int i=0;i<directBufferStacks.length;i++) {
    		res[i]=directBufferStacks[i].size();
    	}
    	return res;
    }

    /**
     * get the number of ByteBuffer allocated for each kind of direct memory ByteBuffer length
     */
    public static int[] getCountOfHeapMemoryBuffers() {
    	int[] res=new int[heapBufferStacks.length];
    	for(int i=0;i<heapBufferStacks.length;i++) {
    		res[i]=heapBufferStacks[i].size();
    	}
    	return res;
    }
}
