SweeperPool.java

package org.codehaus.plexus.util;

/*
 * Copyright The Codehaus Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import java.util.ArrayList;

/**
 * Pools a bunch of objects . Runs a sweeper periodically to keep it down to size. The objects in the pool first get
 * disposed first.
 *
 * @author <a href="mailto:bert@tuaworks.co.nz">Bert van Brakel</a>
 *
 */
public class SweeperPool {
    /***/
    private static final boolean DEBUG = false;

    /** Sweeps the pool periodically to trim it's size */
    private transient Sweeper sweeper;

    /** Absolute maximum size of the pool. */
    private transient int maxSize;

    /** The size the pool gets trimmed down to */
    private transient int minSize;

    /**
     * When the sweeper runs and the pool is over this size, then the pool is trimmed
     */
    private int triggerSize;

    /** Holds the pooled objects */
    private ArrayList<Object> pooledObjects;

    /** Flag indicating this pool is shuting down */
    private boolean shuttingDown = false;

    // private Vector used;

    /**
     * There are a number of settings to control how the pool operates.
     * @param maxSize if the pool has reached this size, any objects added are immediately disposed. If the
     *      pool is this size when the sweeper runs, then the pool is also trimmed to <code>minSize</code> irrespective of
     *      the triggerSize.
     * @param minSize - this is the size the pool is trimmed to
     * @param  triggerSize - this determines if the pool is trimmed when the sweeper runs. If the pool size is
     * greater or equal than this value then the pool is trimmed to <code>minSize</code>.
     *
     * @param sweepInterval how often the sweeper runs. Is actually the time since the sweeper last finished
     * a pass. 0 if the sweeper should not run.
     * @param intialCapacity the intial capacity
     * <p>Any value less than 0 is automatically converted to 0</p>
     */
    public SweeperPool(int maxSize, int minSize, int intialCapacity, int sweepInterval, int triggerSize) {
        super();
        this.maxSize = saneConvert(maxSize);
        this.minSize = saneConvert(minSize);
        this.triggerSize = saneConvert(triggerSize);
        pooledObjects = new ArrayList(intialCapacity);

        // only run a sweeper if sweep interval is positive
        if (sweepInterval > 0) {
            sweeper = new Sweeper(this, sweepInterval);
            sweeper.start();
        }
    }

    private int saneConvert(int value) {
        return Math.max(value, 0);
    }

    /**
     * Return the pooled object
     * @return first available object from the pool
     */
    public synchronized Object get() {
        if ((pooledObjects.size() == 0) || shuttingDown) {
            return null;
        } else {
            Object obj = pooledObjects.remove(0);
            objectRetrieved(obj);

            // used.add(obj);
            return obj;
        }
    }

    /**
     * Add an object to the pool
     *
     * @param obj the object to pool. Can be null.
     * @return true if the object was added to the pool, false if it was disposed or null
     */
    public synchronized boolean put(Object obj) {
        objectAdded(obj);

        if ((obj != null) && (pooledObjects.size() < maxSize) && (shuttingDown == false)) {
            pooledObjects.add(obj);

            return true;
        } else if (obj != null) {
            // no longer need the object, so dispose it
            objectDisposed(obj);
        }

        return false;
    }

    /**
     * Return the number of pooled objects. This is never greater than t maximum size of the pool
     *
     * @return the number of pooled objects
     */
    public synchronized int getSize() {
        return pooledObjects.size();
    }

    /**
     * Dispose of this pool. Stops the sweeper and disposes each object in the pool
     */
    public void dispose() {
        shuttingDown = true;

        if (sweeper != null) {
            sweeper.stop();
            try {
                sweeper.join();
            } catch (InterruptedException e) {
                System.err.println("Unexpected exception occurred: ");
                e.printStackTrace();
            }
        }

        synchronized (this) {
            // use an array here as objects may still be being put back in the pool
            // and we don't want to throw a ConcurrentModificationException
            Object[] objects = pooledObjects.toArray();

            for (Object object : objects) {
                objectDisposed(object);
            }

            pooledObjects.clear();
        }
    }

    /**
     * A pool has been disposed if has been shutdown and the sweeper has completed running.
     *
     * @return true if the pool has been disposed, false otherwise
     */
    boolean isDisposed() {
        if (!shuttingDown) {
            return false;
        }

        // A null sweeper means one was never started.
        if (sweeper == null) {
            return true;
        }

        return sweeper.hasStopped();
    }

    /**
     * Trim the pool down to min size
     */
    public synchronized void trim() {
        if (((triggerSize > 0) && (pooledObjects.size() >= triggerSize))
                || ((maxSize > 0) && (pooledObjects.size() >= maxSize))) {
            while (pooledObjects.size() > minSize) {
                objectDisposed(pooledObjects.remove(0));
            }
        }
    }

    /**
     * Override this to be notified of object disposal. Called after the object has been removed. Occurs when the pool
     * is trimmed.
     *
     * @param obj the Object
     */
    public void objectDisposed(Object obj) {}

    /**
     * Override this to be notified of object addition. Called before object is to be added.
     *
     * @param obj the Object
     */
    public void objectAdded(Object obj) {}

    /**
     * Override this to be notified of object retrieval. Called after object removed from the pool, but before returned
     * to the client.
     *
     * @param obj the Object
     */
    public void objectRetrieved(Object obj) {}

    /**
     * Periodically at <code>sweepInterval</code> goes through and tests if the pool should be trimmed.
     *
     * @author bert
     */
    private static class Sweeper implements Runnable {
        private final transient SweeperPool pool;

        private transient boolean service = false;

        private final transient int sweepInterval;

        private transient Thread t = null;

        /**
         *
         */
        public Sweeper(SweeperPool pool, int sweepInterval) {
            super();
            this.sweepInterval = sweepInterval;
            this.pool = pool;
        }

        /**
         * Run the sweeper.
         *
         * @see java.lang.Runnable#run()
         */
        @Override
        public void run() {
            debug("started");

            if (sweepInterval > 0) {
                synchronized (this) {
                    while (service) {
                        try {
                            // wait specified number of seconds
                            // before running next sweep
                            wait(sweepInterval * 1000);
                        } catch (InterruptedException e) {
                        }
                        runSweep();
                    }
                }
            }

            debug("stopped");
        }

        public void start() {
            if (!service) {
                service = true;
                t = new Thread(this);
                t.setName("Sweeper");
                t.start();
            }
        }

        public synchronized void stop() {
            service = false;
            notifyAll();
        }

        void join() throws InterruptedException {
            t.join();
        }

        boolean hasStopped() {
            return !service && !t.isAlive();
        }

        private final void debug(String msg) {
            if (DEBUG) {
                System.err.println(this + ":" + msg);
            }
        }

        private void runSweep() {
            debug("runningSweep. time=" + System.currentTimeMillis());
            pool.trim();
        }
    }
}