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();
        }
    }
}
