Buffer.java

// Copyright (c) ZeroC, Inc.

package com.zeroc.Ice;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;

/**
 * An instance of java.nio.ByteBuffer cannot grow beyond its initial capacity. This class wraps a
 * ByteBuffer and supports reallocation.
 *
 * @hidden Public because it's used by IceBT, SSL, and the 'Ice/background' test.
 */
public class Buffer {
    public Buffer(boolean direct) {
        this(direct, ByteOrder.LITTLE_ENDIAN);
    }

    public Buffer(boolean direct, ByteOrder order) {
        b = _emptyBuffer;
        _size = 0;
        _capacity = 0;
        _direct = direct;
        _order = order;
    }

    public Buffer(byte[] data) {
        this(data, ByteOrder.LITTLE_ENDIAN);
    }

    public Buffer(byte[] data, ByteOrder order) {
        b = ByteBuffer.wrap(data);
        b.order(order);
        _size = data.length;
        _capacity = 0;
        _direct = false;
        _order = order;
    }

    public Buffer(ByteBuffer data) {
        this(data, ByteOrder.LITTLE_ENDIAN);
    }

    public Buffer(ByteBuffer data, ByteOrder order) {
        b = data;
        b.order(order);
        _size = data.remaining();
        _capacity = 0;
        _direct = false;
        _order = order;
    }

    public Buffer(Buffer buf, boolean adopt) {
        b = buf.b;
        _size = buf._size;
        _capacity = buf._capacity;
        _direct = buf._direct;
        _shrinkCounter = buf._shrinkCounter;
        _order = buf._order;

        if (adopt) {
            buf.clear();
        }
    }

    public java.nio.Buffer position(int newPosition) {
        // Cast to java.nio.Buffer to avoid incompatible covariant
        // return type used in Java 9 java.nio.ByteBuffer
        return ((java.nio.Buffer) b).position(newPosition);
    }

    public java.nio.Buffer limit(int newLimit) {
        // Cast to java.nio.Buffer to avoid incompatible covariant
        // return type used in Java 9 java.nio.ByteBuffer
        return ((java.nio.Buffer) b).limit(newLimit);
    }

    public java.nio.Buffer flip() {
        // Cast to java.nio.Buffer to avoid incompatible covariant
        // return type used in Java 9 java.nio.ByteBuffer
        return ((java.nio.Buffer) b).flip();
    }

    public void swap(Buffer buf) {
        final ByteBuffer bb = buf.b;
        final int size = buf._size;
        final int capacity = buf._capacity;
        final boolean direct = buf._direct;
        final int shrinkCounter = buf._shrinkCounter;
        final ByteOrder order = buf._order;

        buf.b = b;
        buf._size = _size;
        buf._capacity = _capacity;
        buf._direct = _direct;
        buf._shrinkCounter = _shrinkCounter;
        buf._order = _order;

        b = bb;
        _size = size;
        _capacity = capacity;
        _direct = direct;
        _shrinkCounter = shrinkCounter;
        _order = order;
    }

    public int size() {
        return _size;
    }

    public boolean empty() {
        return _size == 0;
    }

    public void clear() {
        b = _emptyBuffer;
        _size = 0;
        _capacity = 0;
        _shrinkCounter = 0;
    }

    //
    // Call expand(n) to add room for n additional bytes. Note that expand()
    // examines the current position of the buffer first; we don't want to expand the buffer if the
    // caller is writing to a location that is already in the buffer.
    //
    public void expand(int n) {
        final int sz = b == _emptyBuffer ? n : b.position() + n;
        if (sz > _size) {
            resize(sz, false);
        }
    }

    public void resize(int n, boolean reading) {
        assert (b == _emptyBuffer || _capacity > 0);

        if (n == 0) {
            clear();
        } else if (n > _capacity) {
            reserve(n);
        }
        _size = n;

        //
        // When used for reading, we want to set the buffer's limit to the new size.
        //
        if (reading) {
            limit(_size);
        }
    }

    public void reset() {
        if (_size > 0 && _size * 2 < _capacity) {
            //
            // If the current buffer size is smaller than the buffer capacity, we shrink the buffer
            // memory to the current size. This is to avoid holding on to too much memory if it's
            // not needed anymore.
            //
            if (++_shrinkCounter > 2) {
                reserve(_size);
                _shrinkCounter = 0;
            }
        } else {
            _shrinkCounter = 0;
        }
        _size = 0;
        if (b != _emptyBuffer) {
            limit(b.capacity());
            position(0);
        }
    }

    private void reserve(int n) {
        if (n > _capacity) {
            _capacity = java.lang.Math.max(n, 2 * _capacity);
            _capacity = java.lang.Math.max(240, _capacity);
        } else if (n < _capacity) {
            _capacity = n;
        } else {
            return;
        }

        try {
            ByteBuffer buf;

            if (_direct) {
                buf = ByteBuffer.allocateDirect(_capacity);
            } else {
                buf = ByteBuffer.allocate(_capacity);
            }

            if (b == _emptyBuffer) {
                b = buf;
            } else {
                final int pos = b.position();
                position(0);
                limit(java.lang.Math.min(_capacity, b.capacity()));
                buf.put(b);
                b = buf;
                limit(b.capacity());
                position(pos);
            }

            b.order(_order); // Preserve the original order.
        } catch (OutOfMemoryError ex) {
            _capacity = b.capacity(); // Restore the previous capacity.
            throw ex;
        }
    }

    public ByteBuffer b;
    // Sentinel used for null buffer.
    public ByteBuffer _emptyBuffer = ByteBuffer.allocate(0);

    private int _size;
    private int _capacity; // Cache capacity to avoid excessive method calls.
    private boolean _direct; // Use direct buffers?
    private int _shrinkCounter;
    private ByteOrder _order;
}