/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2000-2010 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

/*
 * @(#)ByteBufferPool.java	1.8 06/27/07
 */ 

package com.sun.messaging.jmq.io;

import java.util.ArrayList;
import java.util.List;
import java.util.HashMap;
import java.util.Map;
import java.util.Iterator;
import java.util.Set;
import java.nio.ByteBuffer;

import com.sun.messaging.jmq.util.DiagManager;
import com.sun.messaging.jmq.util.DiagManager.Data;
import com.sun.messaging.jmq.util.DiagDictionaryEntry;

/**
 *
 * A pool of ByteBuffers
 *
 * Nio direct ByteBuffers are relatively expensive to allocate and
 * deallocate. To make direct ByteBuffers easier to use we introduce the
 * ByteBuffer pool. this pool should really be only used for direct
 * ByteBuffers. Standard ByteBuffers are better managed by the Java heap.
 *
 * The pool has the following properties:
 *
 * poolCapacity: The maximum number of buffer bytes that the pool will hold.
 *     poolSize: The number of buffer bytes currently in the pool.
 *   bigBufSize: The size a buffer needs to be to be considered "big".
 *     bigRatio: The max number of bytes "big" buffers can occupy in the pool.
 *    useDirect: True to allocate direct buffers, false to allocate normal
 *    blockSize: The allocation block size. This size of all buffers will
 *               be a multiple of blockSize.
 *
 * bigBufSize and bigRatio are used to prevent large buffers from
 * blowing out most of the pool.
 *
 * The pool is implemented as a HashMap of ArrayLists keyed by buffer size.
 * Each ArrayList holds buffers of one size.
 *
 */
public class ByteBufferPool implements DiagManager.Data {

    private static boolean DEBUG = false;

    // Capacity of buffer pool in bytes
    public static final int DEFAULT_CAPACITY = 1024 * 1024;

    // Default block size in bytes.
    public static final int DEFAULT_BLOCKSIZE = 128;

    // Any buffer larger than 64k is considered big
    public static final int DEFAULT_BIGBUFSIZE = 64 * 1024;

    // By default big buffers can take up half of the pool
    public static final float DEFAULT_BIGRATIO = 0.5f;

    // Hash table for holding lists of buffers
    protected HashMap table = null;

    // True if pool is managing direct buffers, else false
    protected boolean useDirect = true;

    // Allocation block size in bytes. All allocations will be a multiple
    // of blockSize. This increases the likelyhood of buffer reuse.
    protected int blockSize = DEFAULT_BLOCKSIZE;

    // Maximum capacity of the pool in bytes
    protected int poolCapacity = DEFAULT_CAPACITY;

    // Any buffers this size or larger are considered "big"
    protected int bigBufSize  = DEFAULT_BIGBUFSIZE;

    // Determines the ammount of space in the pool that can be used
    // by "big" buffers. This prevents large buffers from taking
    // up all the space in the pool
    protected float bigRatio = DEFAULT_BIGRATIO;

    // Current number of bytes in the pool that are used by big buffers
    protected int bigPoolSize  = 0;

    // Current number of bytes in the pool that are used by all buffers
    protected int poolSize = 0;

    // Current number of buffers in the pool
    protected int nBufs = 0;

    // Number of direct bytes allocated. Direct buffers are very expensive
    // to allocate and are not free'd efficiently, so once we reach
    // our limit of direct bytes to allocate we revert to using 
    // heap ByteBuffers.
    protected int directBytesAllocated = 0;
    protected int heapBytesAllocated = 0;
    protected int directBufsAllocated = 0;
    protected int heapBufsAllocated = 0;

    // Diagnostic counters
    protected int   hits = 0;   // # of times we found a buffer in the pool
    protected int misses = 0;   // # of times we had to allocate a new one

    protected int adds = 0;     // # of times we added a buffer to the pool
    protected int drops = 0;    // # of times we failed to add a buffer
    protected int heapDrops = 0;// # of buffers we dropped because they
                                // were heap buffers
    protected String poolContents = null;
    protected double utilization = 0;

    ArrayList diagDictionary = null;

    /**
     * Create an empty packet pool with a default capacity that
     * manages direct ByteBuffers. Currently the default capacity
     * is 1MB.
     */
    public ByteBufferPool() {
        this(DEFAULT_CAPACITY, true);
    }

    /**
     * Create an empty packet pool.
     *
     * @param   capacity    Capacity in bytes of the pool
     * @param   useDirect   true to manage direct ByteBuffers else false
     */
    public ByteBufferPool(int capacity, boolean useDirect) {
        this.poolCapacity = capacity;
        this.useDirect = useDirect;
        table = new HashMap();
        DiagManager.register(this);
    }

    /**
     * Set the big buffer size. Any buffer that is this size
     * or larger is considered big.
     */
    public void setBigBufSize(int n) {
        this.bigBufSize = n;
    }

    /**
     * Get the big buffer size.
     */
    public int getBigBufSize() {
        return this.bigBufSize;
    }

    /**
     * Set the big ratio for the pool. This ratio determines the
     * number of bytes in the pool that can be occupied by big buffers.
     * For example, if the big ratio is 0.5, then half of the pool
     * can be filled with big buffers. 
     */
    public void setBigRatio(float n) {
        this.bigRatio = n;
    }

    /**
     * Get the big ratio.
     */
    public float getBigRatio() {
        return this.bigRatio;
    }

    /**
     * Set the pool's capacity. 
     */
    public void setCapacity(int n) {
        this.poolCapacity = n;
    }

    /**
     * Get the pool's capacity
     */
    public int getCapacity() {
        return poolCapacity;
    }

    /**
     * Set the pool's capacity. 
     */
    public void setBlockSize(int n) {
        this.blockSize = n;
    }

    /**
     * Get the pool's capacity
     */
    public int getBlockSize() {
        return blockSize;
    }


    /**
     * Get utilization as a percentage. Ideal utilization would
     * be 1.0. poor utilization would be 0.1
     */
    public double getUtilization() {
        return (hits/(double)(hits + misses));
    }

    /**
     * Get a buffer from the pool of at least the specified size.
     * If the pool is empty a newly allocated buffer will be returned.
     * ByteBuffer.limit will be set to bufSize.
     * ByteBuffer.capacity may be larger than bufSize.
     */
    public synchronized ByteBuffer get(int bufSize) {

        int allocateSize = 0;
        ArrayList buffers = null;
        ByteBuffer b = null;

        // The size of the buffer we allocate is a multiple of the blocksize
        allocateSize = (bufSize / blockSize) * blockSize;
        if (bufSize % blockSize > 0) {
            allocateSize += blockSize;
        }

        buffers = (ArrayList)table.get(new Integer(allocateSize));

        if (buffers == null || buffers.isEmpty()) {
            // No buffer of the correct size in the pool. Allocate one
            misses++;
            if (directBytesAllocated >= poolCapacity) {
                // Once we allocate "poolCapacity" worth of direct bytes
                // allocate heap bytes instead.
                b = ByteBuffer.allocate(bufSize);
                heapBytesAllocated += bufSize;
                heapBufsAllocated += 1;
            } else if (useDirect) {
                b = ByteBuffer.allocateDirect(allocateSize);
                directBytesAllocated += allocateSize;
                directBufsAllocated += 1;
            } else {
                b = ByteBuffer.allocate(allocateSize);
            }
            if ( DEBUG ) {
                System.out.println(super.toString() +
                    " get(): miss: allocating new buffer. Requested " +
                     bufSize + " bytes, allocating " + allocateSize);
            }
        } else {
            b = (ByteBuffer)(buffers.remove(buffers.size() - 1));
            poolSize -= b.capacity();
            if (b.capacity() >= bigBufSize) {
                bigPoolSize -= b.capacity();
            }
            nBufs--;
            hits++;
            if ( DEBUG ) {
                System.out.println(super.toString() +
                    " get(): hit: Requested " +
                     bufSize + " bytes, returning " + b.capacity());
            }
        }

        b.limit(bufSize);
        b.rewind();

        return b;
    }

    /**
     * Return a buffer to the pool. If the pool capacity is exceeded the
     * buffer is not placed in the pool (and presumeably left for 
     * garbage collection).
     *
     * Also, if the ByteBuffer.isDirect is inconsitent with the "useDirect"
     * parameter passed in the pool constructor, the buffer is disgarded.
     */
    public synchronized void put(ByteBuffer b) {
        if (b == null) return;

        // If it's a direct pool, only save direct buffers
        if (useDirect && !b.isDirect()) {
            if ( DEBUG ) {
                System.out.println(super.toString() +
                    " put(): drop: buffer is not direct. " +
                    "Dropping " + b.capacity() +
                    " bytes onto floor ");
            }
            drops++;
            return;
        }

        ArrayList buffers = null;
        boolean bigBuf = false;

        // See if it is a big buffer
        if (b.capacity() >= bigBufSize) {
            bigBuf = true;
        }

        // If it's a big buffer and we've exceeded the space allowed
        // for big buffers, drip it on floor.
        if (bigBuf && bigPoolSize > (poolCapacity * bigRatio)) {
            drops++;
            if ( DEBUG ) {
                System.out.println(super.toString() +
                    " put(): drop: big pool capacity of " +
                    (poolCapacity * bigRatio) +
                    "exceeded. dropping " + b.capacity() +
                    " bytes onto floor ");
            }
        } else if (poolSize < poolCapacity) {
            Integer key = new Integer(b.capacity());
            // Add buffer to the pool
            buffers = (ArrayList)table.get(key);
            if (buffers == null) {
                buffers = new ArrayList(64);
                table.put(key, buffers);
            }
            buffers.add(b);
            poolSize += b.capacity();
            nBufs++;
	    adds++;
            if (bigBuf) {
                bigPoolSize += b.capacity();
            }
            if ( DEBUG ) {
                System.out.println(super.toString() +
                    "put(): add: putting " + b.capacity() +
                    " bytes back into pool");
            }
        } else {
            // Drop it on floor
            drops++;
            if ( DEBUG ) {
                System.out.println(super.toString() +
                    " put(): drop: dropping " + b.capacity() +
                    " bytes onto floor ");
            }
        }
    }

    /**
     * Empty the pool.
     */
    public synchronized void clear() {
        table.clear();
        poolSize = 0;
        bigPoolSize  = 0;
        nBufs = 0;
    }

    public String toString() {
        return super.toString() + ": capacity=" + poolCapacity +
                 ", size=" + poolSize + ", nBufs=" + nBufs +
                 ", bigBufSize=" + bigBufSize +
                 ", bigPoolSize=" + bigPoolSize + ", bigRatio=" + bigRatio +
                 ", utilization=" + getUtilization() +
                 ", directBytes=" + directBytesAllocated +
                 ", directBuffers=" + directBufsAllocated +
                 ", heapBytes=" + heapBytesAllocated +
                 ", heapBuffers=" + heapBufsAllocated;
    }


    public String toDiagString() {
        return toString() + ", hits=" + hits + ", misses=" + misses +
            ", adds=" + adds + ", drops=" + drops;
    }

    public String poolContents() {
        int mapSize = table.size();
        StringBuffer sb = new StringBuffer();

        Set set = table.entrySet();
        Iterator iter = set.iterator();
        int i = 0;
        int totalBytes = 0;
        while (iter.hasNext()) {
            Map.Entry entry = (Map.Entry)iter.next();
            int n = ((Integer)entry.getKey()).intValue();
            ArrayList list = (ArrayList)entry.getValue();
            sb.append(n + ":" + list.size() + " ");
            totalBytes += n * list.size();
        }

        sb.append(" Total Bytes: " + totalBytes);
        return sb.toString();

    }

    public void resetDiagCounters() {
        hits = 0;
        misses = 0;
        adds = 0;
        drops = 0;
    }

    // Methods to support diagnostics
    public synchronized List getDictionary() {
        if (diagDictionary == null) {
            diagDictionary = new ArrayList();

            diagDictionary.add(new DiagDictionaryEntry("poolCapacity", DiagManager.VARIABLE));
            diagDictionary.add(new DiagDictionaryEntry("poolSize", DiagManager.VARIABLE));
            diagDictionary.add(new DiagDictionaryEntry("blockSize", DiagManager.VARIABLE));
            diagDictionary.add(new DiagDictionaryEntry("nBufs", DiagManager.VARIABLE));
            diagDictionary.add(new DiagDictionaryEntry("bigBufSize", DiagManager.VARIABLE));
            diagDictionary.add(new DiagDictionaryEntry("bigPoolSize", DiagManager.VARIABLE));
            diagDictionary.add(new DiagDictionaryEntry("directBytesAllocated", DiagManager.COUNTER));
            diagDictionary.add(new DiagDictionaryEntry("directBufsAllocated", DiagManager.COUNTER));
            diagDictionary.add(new DiagDictionaryEntry("heapBytesAllocated", DiagManager.COUNTER));
            diagDictionary.add(new DiagDictionaryEntry("heapBufsAllocated", DiagManager.COUNTER));
            diagDictionary.add(new DiagDictionaryEntry("hits", DiagManager.COUNTER));
            diagDictionary.add(new DiagDictionaryEntry("misses", DiagManager.COUNTER));
            diagDictionary.add(new DiagDictionaryEntry("adds", DiagManager.COUNTER));
            diagDictionary.add(new DiagDictionaryEntry("drops", DiagManager.COUNTER));
            diagDictionary.add(new DiagDictionaryEntry("utilization", DiagManager.VARIABLE));
            diagDictionary.add(new DiagDictionaryEntry("poolContents", DiagManager.VARIABLE));
        }

        return diagDictionary;
    }

    public void update() {
        poolContents = "[" + poolContents() + "]";
        utilization = getUtilization();
    }

    public String getPrefix() {
        return "bbpool";
    }

    public String getTitle() {
        return "ByteBufferPool";
    }

}

