package com.github.mikephil.charting.utils; import java.util.List; /** * An object pool for recycling of object instances extending Poolable. * * * Cost/Benefit : * Cost - The pool can only contain objects extending Poolable. * Benefit - The pool can very quickly determine if an object is elligable for storage without iteration. * Benefit - The pool can also know if an instance of Poolable is already stored in a different pool instance. * Benefit - The pool can grow as needed, if it is empty * Cost - However, refilling the pool when it is empty might incur a time cost with sufficiently large capacity. Set the replenishPercentage to a lower number if this is a concern. * * Created by Tony Patino on 6/20/16. */ public class ObjectPool<T extends ObjectPool.Poolable> { private static int ids = 0; private int poolId; private int desiredCapacity; private Object[] objects; private int objectsPointer; private T modelObject; private float replenishPercentage; /** * Returns the id of the given pool instance. * * @return an integer ID belonging to this pool instance. */ public int getPoolId(){ return poolId; } /** * Returns an ObjectPool instance, of a given starting capacity, that recycles instances of a given Poolable object. * * @param withCapacity A positive integer value. * @param object An instance of the object that the pool should recycle. * @return */ public static synchronized ObjectPool create(int withCapacity, Poolable object){ ObjectPool result = new ObjectPool(withCapacity, object); result.poolId = ids; ids++; return result; } private ObjectPool(int withCapacity, T object){ if(withCapacity <= 0){ throw new IllegalArgumentException("Object Pool must be instantiated with a capacity greater than 0!"); } this.desiredCapacity = withCapacity; this.objects = new Object[this.desiredCapacity]; this.objectsPointer = 0; this.modelObject = object; this.replenishPercentage = 1.0f; this.refillPool(); } /** * Set the percentage of the pool to replenish on empty. Valid values are between * 0.00f and 1.00f * * @param percentage a value between 0 and 1, representing the percentage of the pool to replenish. */ public void setReplenishPercentage(float percentage){ float p = percentage; if(p > 1){ p = 1; } else if(p < 0f){ p = 0f; } this.replenishPercentage = p; } public float getReplenishPercentage(){ return replenishPercentage; } private void refillPool(){ this.refillPool(this.replenishPercentage); } private void refillPool(float percentage){ int portionOfCapacity = (int) (desiredCapacity * percentage); if(portionOfCapacity < 1){ portionOfCapacity = 1; }else if(portionOfCapacity > desiredCapacity){ portionOfCapacity = desiredCapacity; } for(int i = 0 ; i < portionOfCapacity ; i++){ this.objects[i] = modelObject.instantiate(); } objectsPointer = portionOfCapacity - 1; } /** * Returns an instance of Poolable. If get() is called with an empty pool, the pool will be * replenished. If the pool capacity is sufficiently large, this could come at a performance * cost. * * @return An instance of Poolable object T */ public synchronized T get(){ if(this.objectsPointer == -1 && this.replenishPercentage > 0.0f){ this.refillPool(); } T result = (T)objects[this.objectsPointer]; result.currentOwnerId = Poolable.NO_OWNER; this.objectsPointer--; return result; } /** * Recycle an instance of Poolable that this pool is capable of generating. * The T instance passed must not already exist inside this or any other ObjectPool instance. * * @param object An object of type T to recycle */ public synchronized void recycle(T object){ if(object.currentOwnerId != Poolable.NO_OWNER){ if(object.currentOwnerId == this.poolId){ throw new IllegalArgumentException("The object passed is already stored in this pool!"); }else { throw new IllegalArgumentException("The object to recycle already belongs to poolId " + object.currentOwnerId + ". Object cannot belong to two different pool instances simultaneously!"); } } this.objectsPointer++; if(this.objectsPointer >= objects.length){ this.resizePool(); } object.currentOwnerId = this.poolId; objects[this.objectsPointer] = object; } /** * Recycle a List of Poolables that this pool is capable of generating. * The T instances passed must not already exist inside this or any other ObjectPool instance. * * @param objects A list of objects of type T to recycle */ public synchronized void recycle(List<T> objects){ while(objects.size() + this.objectsPointer + 1 > this.desiredCapacity){ this.resizePool(); } final int objectsListSize = objects.size(); // Not relying on recycle(T object) because this is more performant. for(int i = 0 ; i < objectsListSize ; i++){ T object = objects.get(i); if(object.currentOwnerId != Poolable.NO_OWNER){ if(object.currentOwnerId == this.poolId){ throw new IllegalArgumentException("The object passed is already stored in this pool!"); }else { throw new IllegalArgumentException("The object to recycle already belongs to poolId " + object.currentOwnerId + ". Object cannot belong to two different pool instances simultaneously!"); } } object.currentOwnerId = this.poolId; this.objects[this.objectsPointer + 1 + i] = object; } this.objectsPointer += objectsListSize; } private void resizePool() { final int oldCapacity = this.desiredCapacity; this.desiredCapacity *= 2; Object[] temp = new Object[this.desiredCapacity]; for(int i = 0 ; i < oldCapacity ; i++){ temp[i] = this.objects[i]; } this.objects = temp; } /** * Returns the capacity of this object pool. Note : The pool will automatically resize * to contain additional objects if the user tries to add more objects than the pool's * capacity allows, but this comes at a performance cost. * * @return The capacity of the pool. */ public int getPoolCapacity(){ return this.objects.length; } /** * Returns the number of objects remaining in the pool, for diagnostic purposes. * * @return The number of objects remaining in the pool. */ public int getPoolCount(){ return this.objectsPointer + 1; } public static abstract class Poolable{ public static int NO_OWNER = -1; int currentOwnerId = NO_OWNER; protected abstract Poolable instantiate(); } }