/*
* This file is part of NucleusFramework for Bukkit, licensed under the MIT License (MIT).
*
* Copyright (c) JCThePants (www.jcwhatever.com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.jcwhatever.nucleus.utils.performance.pool;
import com.jcwhatever.nucleus.utils.ArrayUtils;
import com.jcwhatever.nucleus.utils.PreCon;
import javax.annotation.Nullable;
/**
* Abstract implementation of an array based object pool.
*/
public abstract class AbstractPool<E> {
private final IPoolElementFactory<E> _elementFactory;
private final IPoolRecycleHandler<E> _recycleHandler;
private Object[] _pool;
private int _poolIndex = -1;
private int _maxSize = -1;
/**
* Constructor.
*
* @param capacity The initial capacity of the pool.
*/
public AbstractPool(int capacity) {
this(capacity, null, null);
}
/**
* Constructor.
*
* @param capacity The initial capacity of the pool.
* @param elementFactory The element factory used to create new elements when
* the pool is empty.
*/
public AbstractPool(int capacity,
@Nullable IPoolElementFactory<E> elementFactory) {
this(capacity, elementFactory, null);
}
/**
* Constructor.
*
* @param capacity The initial capacity of the pool.
* @param elementFactory The element factory used to create new elements when
* the pool is empty.
* @param recycleHandler The handler to give a recycled element to for object teardown.
*/
public AbstractPool(int capacity,
@Nullable IPoolElementFactory<E> elementFactory,
@Nullable IPoolRecycleHandler<E> recycleHandler) {
_pool = new Object[capacity];
_elementFactory = elementFactory;
_recycleHandler = recycleHandler;
}
/**
* Get the number of pooled elements.
*/
public int size() {
return _poolIndex + 1;
}
/**
* Get the maximum size of the pool.
*
* @return The maximum size or -1 to indicate "infinite" capacity.
*/
public int maxSize() {
return _maxSize;
}
/**
* Set the maximum size of the pool.
*
* <p>Unless the new size is -1, the pool is resized to the max size.</p>
*
* @param maxSize The maximum number of elements to hold in the pool. -1 to
* indicate "infinite" capacity.
*/
public void setMaxSize(int maxSize) {
PreCon.greaterThan(maxSize, -2);
_maxSize = maxSize;
if (_maxSize > -1 && _maxSize < pool().length)
resizePool(maxSize);
}
/**
* Determine if the pool contains the specified element.
*
* @param element The element.
*/
public boolean contains(@Nullable Object element) {
if (element == null)
return false;
for (int i=0; i < size(); i++) {
if (element.equals(_pool[i]))
return true;
}
return false;
}
/**
* Clear all elements from the pool.
*/
public void clear() {
ArrayUtils.reset(_pool);
_poolIndex = -1;
}
/**
* Resize the pool to the number of pooled elements.
*/
public void fitToSize() {
resizePool(size());
}
/**
* Resize the pool to the number of elements
*
* @param padding The number of extra pool slots to pad the size with.
*/
public void fitToSize(int padding) {
PreCon.positiveNumber(padding);
resizePool(size() + padding);
}
/**
* Retrieve an element from the pool.
*
* <p>If the pool is empty, a new element is created.</p>
*
* @return An element or null if the pool is empty and a pooled element factory
* is not being used.
*/
@Nullable
protected E retrieve() {
if (_poolIndex == -1)
return _elementFactory == null ? null : _elementFactory.create();
@SuppressWarnings("unchecked")
E element = (E)_pool[_poolIndex];
_pool[_poolIndex] = null;
_poolIndex--;
return element;
}
/**
* Recycle an element back into the pool.
*
* @param element The element to recycle.
*
* @return True if the element was added into the pool, false if there is
* no room in the pool.
*/
protected boolean recycle(E element) {
PreCon.notNull(element);
if (_maxSize > -1 && _poolIndex + 1 >= _maxSize)
return false;
if (_poolIndex + 1 >= _pool.length)
expandPool();
_poolIndex++;
_pool[_poolIndex] = element;
if (_recycleHandler != null)
_recycleHandler.onRecycle(element);
return true;
}
/**
* Recycle an array of elements.
*
* @param elements The elements to recycle.
* @param start The start index to of the elements to recycle.
* @param length The number of elements to recycle.
*
* @return The number of elements recycled.
*/
protected <T> int recycleAll(T[] elements, int start, int length) {
PreCon.notNull(elements, "elements");
PreCon.positiveNumber(start, "start");
PreCon.lessThan(start, elements.length, "start");
PreCon.lessThanEqual(length, elements.length - start, "length");
int room = _pool.length - size();
if (maxSize() < 0 && room < length)
resizePool(_pool.length - room + length);
int recycleCount = 0;
for (int i = 0; i < length; i++) {
if (maxSize() > -1 && _poolIndex + i + 2 > maxSize())
break;
@SuppressWarnings("unchecked")
E element = (E)elements[start + i];
if (element == null)
continue;
_pool[i + _poolIndex + 1] = element;
if (_recycleHandler != null)
_recycleHandler.onRecycle(element);
recycleCount++;
}
_poolIndex += recycleCount;
return recycleCount;
}
/**
* Expand the size of the pool.
*/
protected void expandPool() {
_pool = expand(_pool, _maxSize);
}
/**
* Resize the pool to a specific size.
*/
protected void resizePool(int size) {
_pool = resize(_pool, size, _maxSize);
}
/**
* Determine if a resize is possible given the current size of
* an array and its maximum size.
*/
protected boolean canResize(int size, int maxSize) {
return maxSize < 0 || size <= _maxSize;
}
/**
* Limit the resize size given the desired size and the maximum
* size of an array.
*/
protected int limitResize(int desiredSize, int maxSize) {
if (maxSize > -1 && desiredSize > _maxSize)
return maxSize;
return desiredSize;
}
/**
* Expand the size of a pool.
*/
protected Object[] expand(Object[] array, int maxSize) {
int newSize = array.length + (int)Math.max(10, Math.min(100, Math.ceil(array.length * 0.15D)));
return resize(array, newSize, maxSize);
}
/**
* Resize a pool to a specific size.
*/
protected Object[] resize(Object[] array, int size, int maxSize) {
if (!canResize(array.length, maxSize))
return array;
size = limitResize(size, maxSize);
Object[] newArray = new Object[size];
return ArrayUtils.copyFromStart(array, newArray);
}
/**
* Get the pool array.
*/
protected Object[] pool() {
return _pool;
}
/**
* Get the index of the most recent pool element.
*/
protected int getPoolIndex() {
return _poolIndex;
}
/**
* Get the element factory.
*/
@Nullable
protected IPoolElementFactory<E> getElementFactory() {
return _elementFactory;
}
/**
* Get the element recycle handler.
*/
@Nullable
protected IPoolRecycleHandler<E> getRecycler() {
return _recycleHandler;
}
}