/*
 * Written by Dawid Kurzyniec and released to the public domain, as explained
 * at http://creativecommons.org/licenses/publicdomain
 */

package edu.emory.mathcs.util.allocator;

/**
 * Abstraction of memory manager. Useful for I/O processing when direct buffer
 * management is required, e.g. if one wants to control maximum memory usage by
 * I/O buffers. Example application is in util.io.BufferedPipe. Concrete
 * implementations of this interface may implement various allocation policies.
 *
 * @author Dawid Kurzyniec
 * @version 1.0
 */
public interface Allocator {

    /**
     * Allocate a buffer with specified size. The operation can
     * block if memory limits are exceeded. The <tt>timeout</tt> value greater
     * than 0 indicates the timeout, the value of 0 will cause immediate
     * return of <tt>null</tt> if the buffer was not available, and value less
     * than zero will cause to wait for buffer inidefinitely. The
     * <tt>clear</tt> parameter indicates that the buffer should be zeroed out.
     * Since buffers may be recycled, the buffer returned may contain random
     * garbage if this value is false. If it is set to true, the [0..size]
     * region of the array within the buffer is filled with zeros.
     * This method may return a buffer containing an array larger than
     * requested.
     * @param size the requested buffer size.
     * @param timeout the timeout for this operation.
     * @throws InterruptedException if the operation was interrupted.
     */
    Buffer allocate(int size, boolean clear, long timeout) throws InterruptedException;

    /**
     * Represents the data buffer returned to the application as a result of
     * <tt>allocate()</tt> request. Contains actual byte[] array and the size
     * value. Users should access only the [0..size] region of the array,
     * since the initial content of the remaining part is undefined.
     */
    static abstract class Buffer {
        final protected byte[] data;
        final protected int size;
        private int refcount;

        protected Buffer(byte[] data, int size) {
            this.data = data;
            this.size = size;
            this.refcount = 1;
        }

        /**
         * Returns the data array encapsulated in this buffer.
         *
         * @return the data array.
         * @see #getSize()
         */
        public byte[] getData() {
            return refcount > 0 ? data : null;
        }

        /**
         * Returns the size of this buffer. The length of the array
         * encapsulated in this buffer can be larger than that value, but
         * users are expected to access only the [0..size] region of the
         * array.
         *
         * @return size of the buffer.
         * @see #getData()
         */
        public int getSize() {
            return refcount > 0 ? size : 0;
        }

        /**
         * Increase reference count on this buffer.
         */
        public synchronized void addRef() {
            if (refcount <= 0) {
                throw new IllegalStateException("refcount = " + refcount);
            }
            refcount++;
        }

        /**
         * Decrease the reference count on this buffer. When the reference
         * count reaches 0, the buffer may be reclaimed by the allocator
         * and results of any further usage of that buffer are undefined.
         */
        public synchronized void releaseRef() {
            refcount--;
            if (refcount == 0) {
                reclaim();
                return;
            }
            else if (refcount < 0) {
                throw new IllegalStateException("refcount = " + refcount);
            }
        }

        protected abstract void reclaim();
    }
}
