This is an automated email from the ASF dual-hosted git repository.

ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-pool.git

commit 7a203b4975d09f38a738e53d163ae7ac52ea2b01
Author: Gary Gregory <garydgreg...@gmail.com>
AuthorDate: Mon Feb 15 16:09:25 2021 -0500

    Sort members.
---
 .../pool2/BaseKeyedPooledObjectFactory.java        |   70 +-
 .../org/apache/commons/pool2/BaseObjectPool.java   |   84 +-
 .../commons/pool2/BasePooledObjectFactory.java     |   50 +-
 .../commons/pool2/KeyedPooledObjectFactory.java    |   50 +-
 .../java/org/apache/commons/pool2/PoolUtils.java   | 2324 ++++++++++----------
 .../org/apache/commons/pool2/PooledObject.java     |  154 +-
 .../apache/commons/pool2/PooledObjectFactory.java  |   44 +-
 7 files changed, 1388 insertions(+), 1388 deletions(-)

diff --git 
a/src/main/java/org/apache/commons/pool2/BaseKeyedPooledObjectFactory.java 
b/src/main/java/org/apache/commons/pool2/BaseKeyedPooledObjectFactory.java
index 0c7dfd0..b375ce2 100644
--- a/src/main/java/org/apache/commons/pool2/BaseKeyedPooledObjectFactory.java
+++ b/src/main/java/org/apache/commons/pool2/BaseKeyedPooledObjectFactory.java
@@ -36,6 +36,21 @@ public abstract class BaseKeyedPooledObjectFactory<K, V> 
extends BaseObject
         implements KeyedPooledObjectFactory<K, V> {
 
     /**
+     * Reinitialize an instance to be returned by the pool.
+     * <p>
+     * The default implementation is a no-op.
+     * </p>
+     *
+     * @param key the key used when selecting the object
+     * @param p a {@code PooledObject} wrapping the instance to be activated
+     */
+    @Override
+    public void activateObject(final K key, final PooledObject<V> p)
+        throws Exception {
+        // The default implementation is a no-op.
+    }
+
+    /**
      * Create an instance that can be served by the pool.
      *
      * @param key the key used when constructing the object
@@ -48,14 +63,19 @@ public abstract class BaseKeyedPooledObjectFactory<K, V> 
extends BaseObject
         throws Exception;
 
     /**
-     * Wrap the provided instance with an implementation of
-     * {@link PooledObject}.
-     *
-     * @param value the instance to wrap
+     * Destroy an instance no longer needed by the pool.
+     * <p>
+     * The default implementation is a no-op.
+     * </p>
      *
-     * @return The provided instance, wrapped by a {@link PooledObject}
+     * @param key the key used when selecting the instance
+     * @param p a {@code PooledObject} wrapping the instance to be destroyed
      */
-    public abstract PooledObject<V> wrap(V value);
+    @Override
+    public void destroyObject(final K key, final PooledObject<V> p)
+        throws Exception {
+        // The default implementation is a no-op.
+    }
 
     @Override
     public PooledObject<V> makeObject(final K key) throws Exception {
@@ -63,16 +83,16 @@ public abstract class BaseKeyedPooledObjectFactory<K, V> 
extends BaseObject
     }
 
     /**
-     * Destroy an instance no longer needed by the pool.
+     * Uninitialize an instance to be returned to the idle object pool.
      * <p>
      * The default implementation is a no-op.
      * </p>
      *
-     * @param key the key used when selecting the instance
-     * @param p a {@code PooledObject} wrapping the instance to be destroyed
+     * @param key the key used when selecting the object
+     * @param p a {@code PooledObject} wrapping the instance to be passivated
      */
     @Override
-    public void destroyObject(final K key, final PooledObject<V> p)
+    public void passivateObject(final K key, final PooledObject<V> p)
         throws Exception {
         // The default implementation is a no-op.
     }
@@ -93,32 +113,12 @@ public abstract class BaseKeyedPooledObjectFactory<K, V> 
extends BaseObject
     }
 
     /**
-     * Reinitialize an instance to be returned by the pool.
-     * <p>
-     * The default implementation is a no-op.
-     * </p>
+     * Wrap the provided instance with an implementation of
+     * {@link PooledObject}.
      *
-     * @param key the key used when selecting the object
-     * @param p a {@code PooledObject} wrapping the instance to be activated
-     */
-    @Override
-    public void activateObject(final K key, final PooledObject<V> p)
-        throws Exception {
-        // The default implementation is a no-op.
-    }
-
-    /**
-     * Uninitialize an instance to be returned to the idle object pool.
-     * <p>
-     * The default implementation is a no-op.
-     * </p>
+     * @param value the instance to wrap
      *
-     * @param key the key used when selecting the object
-     * @param p a {@code PooledObject} wrapping the instance to be passivated
+     * @return The provided instance, wrapped by a {@link PooledObject}
      */
-    @Override
-    public void passivateObject(final K key, final PooledObject<V> p)
-        throws Exception {
-        // The default implementation is a no-op.
-    }
+    public abstract PooledObject<V> wrap(V value);
 }
diff --git a/src/main/java/org/apache/commons/pool2/BaseObjectPool.java 
b/src/main/java/org/apache/commons/pool2/BaseObjectPool.java
index c20b1d7..291173d 100644
--- a/src/main/java/org/apache/commons/pool2/BaseObjectPool.java
+++ b/src/main/java/org/apache/commons/pool2/BaseObjectPool.java
@@ -30,55 +30,45 @@ package org.apache.commons.pool2;
  */
 public abstract class BaseObjectPool<T> extends BaseObject implements 
ObjectPool<T> {
 
-    @Override
-    public abstract T borrowObject() throws Exception;
-
-    @Override
-    public abstract void returnObject(T obj) throws Exception;
-
-    @Override
-    public abstract void invalidateObject(T obj) throws Exception;
+    private volatile boolean closed = false;
 
     /**
-     * Not supported in this base implementation.
+     * Not supported in this base implementation. Subclasses should override
+     * this behavior.
      *
-     * @return a negative value.
+     * @throws UnsupportedOperationException if the pool does not implement 
this
+     *          method
      */
     @Override
-    public int getNumIdle() {
-        return -1;
+    public void addObject() throws Exception, UnsupportedOperationException {
+        throw new UnsupportedOperationException();
     }
 
     /**
-     * Not supported in this base implementation.
+     * Throws an {@code IllegalStateException} when this pool has been
+     * closed.
      *
-     * @return a negative value.
+     * @throws IllegalStateException when this pool has been closed.
+     *
+     * @see #isClosed()
      */
-    @Override
-    public int getNumActive() {
-        return -1;
+    protected final void assertOpen() throws IllegalStateException {
+        if (isClosed()) {
+            throw new IllegalStateException("Pool not open");
+        }
     }
 
-    /**
-     * Not supported in this base implementation.
-     *
-     * @throws UnsupportedOperationException if the pool does not implement 
this
-     *          method
-     */
     @Override
-    public void clear() throws Exception, UnsupportedOperationException {
-        throw new UnsupportedOperationException();
-    }
+    public abstract T borrowObject() throws Exception;
 
     /**
-     * Not supported in this base implementation. Subclasses should override
-     * this behavior.
+     * Not supported in this base implementation.
      *
      * @throws UnsupportedOperationException if the pool does not implement 
this
      *          method
      */
     @Override
-    public void addObject() throws Exception, UnsupportedOperationException {
+    public void clear() throws Exception, UnsupportedOperationException {
         throw new UnsupportedOperationException();
     }
 
@@ -95,29 +85,39 @@ public abstract class BaseObjectPool<T> extends BaseObject 
implements ObjectPool
     }
 
     /**
-     * Has this pool instance been closed.
+     * Not supported in this base implementation.
      *
-     * @return {@code true} when this pool has been closed.
+     * @return a negative value.
      */
-    public final boolean isClosed() {
-        return closed;
+    @Override
+    public int getNumActive() {
+        return -1;
     }
 
     /**
-     * Throws an {@code IllegalStateException} when this pool has been
-     * closed.
+     * Not supported in this base implementation.
      *
-     * @throws IllegalStateException when this pool has been closed.
+     * @return a negative value.
+     */
+    @Override
+    public int getNumIdle() {
+        return -1;
+    }
+
+    @Override
+    public abstract void invalidateObject(T obj) throws Exception;
+
+    /**
+     * Has this pool instance been closed.
      *
-     * @see #isClosed()
+     * @return {@code true} when this pool has been closed.
      */
-    protected final void assertOpen() throws IllegalStateException {
-        if (isClosed()) {
-            throw new IllegalStateException("Pool not open");
-        }
+    public final boolean isClosed() {
+        return closed;
     }
 
-    private volatile boolean closed = false;
+    @Override
+    public abstract void returnObject(T obj) throws Exception;
 
     @Override
     protected void toStringAppendFields(final StringBuilder builder) {
diff --git 
a/src/main/java/org/apache/commons/pool2/BasePooledObjectFactory.java 
b/src/main/java/org/apache/commons/pool2/BasePooledObjectFactory.java
index 18338f6..d65f01c 100644
--- a/src/main/java/org/apache/commons/pool2/BasePooledObjectFactory.java
+++ b/src/main/java/org/apache/commons/pool2/BasePooledObjectFactory.java
@@ -32,6 +32,16 @@ package org.apache.commons.pool2;
  */
 public abstract class BasePooledObjectFactory<T> extends BaseObject implements 
PooledObjectFactory<T> {
     /**
+     *  No-op.
+     *
+     *  @param p ignored
+     */
+    @Override
+    public void activateObject(final PooledObject<T> p) throws Exception {
+        // The default implementation is a no-op.
+    }
+
+    /**
      * Creates an object instance, to be wrapped in a {@link PooledObject}.
      * <p>This method <strong>must</strong> support concurrent, multi-threaded
      * activation.</p>
@@ -44,14 +54,15 @@ public abstract class BasePooledObjectFactory<T> extends 
BaseObject implements P
     public abstract T create() throws Exception;
 
     /**
-     * Wrap the provided instance with an implementation of
-     * {@link PooledObject}.
-     *
-     * @param obj the instance to wrap
+     *  No-op.
      *
-     * @return The provided instance, wrapped by a {@link PooledObject}
+     *  @param p ignored
      */
-    public abstract PooledObject<T> wrap(T obj);
+    @Override
+    public void destroyObject(final PooledObject<T> p)
+        throws Exception  {
+        // The default implementation is a no-op.
+    }
 
     @Override
     public PooledObject<T> makeObject() throws Exception {
@@ -61,11 +72,11 @@ public abstract class BasePooledObjectFactory<T> extends 
BaseObject implements P
     /**
      *  No-op.
      *
-     *  @param p ignored
+     * @param p ignored
      */
     @Override
-    public void destroyObject(final PooledObject<T> p)
-        throws Exception  {
+    public void passivateObject(final PooledObject<T> p)
+        throws Exception {
         // The default implementation is a no-op.
     }
 
@@ -82,23 +93,12 @@ public abstract class BasePooledObjectFactory<T> extends 
BaseObject implements P
     }
 
     /**
-     *  No-op.
+     * Wrap the provided instance with an implementation of
+     * {@link PooledObject}.
      *
-     *  @param p ignored
-     */
-    @Override
-    public void activateObject(final PooledObject<T> p) throws Exception {
-        // The default implementation is a no-op.
-    }
-
-    /**
-     *  No-op.
+     * @param obj the instance to wrap
      *
-     * @param p ignored
+     * @return The provided instance, wrapped by a {@link PooledObject}
      */
-    @Override
-    public void passivateObject(final PooledObject<T> p)
-        throws Exception {
-        // The default implementation is a no-op.
-    }
+    public abstract PooledObject<T> wrap(T obj);
 }
diff --git 
a/src/main/java/org/apache/commons/pool2/KeyedPooledObjectFactory.java 
b/src/main/java/org/apache/commons/pool2/KeyedPooledObjectFactory.java
index ee822ab..9f4d734 100644
--- a/src/main/java/org/apache/commons/pool2/KeyedPooledObjectFactory.java
+++ b/src/main/java/org/apache/commons/pool2/KeyedPooledObjectFactory.java
@@ -77,18 +77,17 @@ package org.apache.commons.pool2;
 public interface KeyedPooledObjectFactory<K, V> {
 
     /**
-     * Create an instance that can be served by the pool and
-     * wrap it in a {@link PooledObject} to be managed by the pool.
+     * Reinitialize an instance to be returned by the pool.
      *
-     * @param key the key used when constructing the object
+     * @param key the key used when selecting the object
+     * @param p a {@code PooledObject} wrapping the instance to be activated
      *
-     * @return a {@code PooledObject} wrapping an instance that can
-     * be served by the pool.
+     * @throws Exception if there is a problem activating {@code obj},
+     *    this exception may be swallowed by the pool.
      *
-     * @throws Exception if there is a problem creating a new instance,
-     *    this will be propagated to the code requesting an object.
+     * @see #destroyObject
      */
-    PooledObject<V> makeObject(K key) throws Exception;
+    void activateObject(K key, PooledObject<V> p) throws Exception;
 
     /**
      * Destroy an instance no longer needed by the pool.
@@ -134,40 +133,41 @@ public interface KeyedPooledObjectFactory<K, V> {
     }
 
     /**
-     * Ensures that the instance is safe to be returned by the pool.
+     * Create an instance that can be served by the pool and
+     * wrap it in a {@link PooledObject} to be managed by the pool.
      *
-     * @param key the key used when selecting the object
-     * @param p a {@code PooledObject} wrapping the instance to be validated
+     * @param key the key used when constructing the object
      *
-     * @return {@code false} if {@code obj} is not valid and should
-     *         be dropped from the pool, {@code true} otherwise.
+     * @return a {@code PooledObject} wrapping an instance that can
+     * be served by the pool.
+     *
+     * @throws Exception if there is a problem creating a new instance,
+     *    this will be propagated to the code requesting an object.
      */
-    boolean validateObject(K key, PooledObject<V> p);
+    PooledObject<V> makeObject(K key) throws Exception;
 
     /**
-     * Reinitialize an instance to be returned by the pool.
+     * Uninitialize an instance to be returned to the idle object pool.
      *
      * @param key the key used when selecting the object
-     * @param p a {@code PooledObject} wrapping the instance to be activated
+     * @param p a {@code PooledObject} wrapping the instance to be passivated
      *
-     * @throws Exception if there is a problem activating {@code obj},
+     * @throws Exception if there is a problem passivating {@code obj},
      *    this exception may be swallowed by the pool.
      *
      * @see #destroyObject
      */
-    void activateObject(K key, PooledObject<V> p) throws Exception;
+    void passivateObject(K key, PooledObject<V> p) throws Exception;
 
     /**
-     * Uninitialize an instance to be returned to the idle object pool.
+     * Ensures that the instance is safe to be returned by the pool.
      *
      * @param key the key used when selecting the object
-     * @param p a {@code PooledObject} wrapping the instance to be passivated
-     *
-     * @throws Exception if there is a problem passivating {@code obj},
-     *    this exception may be swallowed by the pool.
+     * @param p a {@code PooledObject} wrapping the instance to be validated
      *
-     * @see #destroyObject
+     * @return {@code false} if {@code obj} is not valid and should
+     *         be dropped from the pool, {@code true} otherwise.
      */
-    void passivateObject(K key, PooledObject<V> p) throws Exception;
+    boolean validateObject(K key, PooledObject<V> p);
 }
 
diff --git a/src/main/java/org/apache/commons/pool2/PoolUtils.java 
b/src/main/java/org/apache/commons/pool2/PoolUtils.java
index b4e614f..70206b8 100644
--- a/src/main/java/org/apache/commons/pool2/PoolUtils.java
+++ b/src/main/java/org/apache/commons/pool2/PoolUtils.java
@@ -36,597 +36,422 @@ import 
java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
  */
 public final class PoolUtils {
 
-    private static final String MSG_FACTOR_NEGATIVE = "factor must be 
positive.";
-    private static final String MSG_MIN_IDLE = "minIdle must be non-negative.";
-    static final String MSG_NULL_KEY = "key must not be null.";
-    private static final String MSG_NULL_KEYED_POOL = "keyedPool must not be 
null.";
-    static final String MSG_NULL_KEYS = "keys must not be null.";
-    private static final String MSG_NULL_POOL = "pool must not be null.";
-
     /**
-     * Timer used to periodically check pools idle object count. Because a
-     * {@link Timer} creates a {@link Thread}, an IODH is used.
+     * Encapsulate the logic for when the next poolable object should be
+     * discarded. Each time update is called, the next time to shrink is
+     * recomputed, based on the float factor, number of idle instances in the
+     * pool and high water mark. Float factor is assumed to be between 0 and 1.
+     * Values closer to 1 cause less frequent erosion events. Erosion event
+     * timing also depends on numIdle. When this value is relatively high 
(close
+     * to previously established high water mark), erosion occurs more
+     * frequently.
      */
-    static class TimerHolder {
-        static final Timer MIN_IDLE_TIMER = new Timer(true);
-    }
+    private static final class ErodingFactor {
+        /** Determines frequency of "erosion" events */
+        private final float factor;
 
-    /**
-     * PoolUtils instances should NOT be constructed in standard programming.
-     * Instead, the class should be used procedurally: PoolUtils.adapt(aPool);.
-     * This constructor is public to permit tools that require a JavaBean
-     * instance to operate.
-     */
-    public PoolUtils() {
-    }
+        /** Time of next shrink event */
+        private transient volatile long nextShrinkMillis;
 
-    /**
-     * Should the supplied Throwable be re-thrown (eg if it is an instance of
-     * one of the Throwables that should never be swallowed). Used by the pool
-     * error handling for operations that throw exceptions that normally need 
to
-     * be ignored.
-     *
-     * @param t
-     *            The Throwable to check
-     * @throws ThreadDeath
-     *             if that is passed in
-     * @throws VirtualMachineError
-     *             if that is passed in
-     */
-    public static void checkRethrow(final Throwable t) {
-        if (t instanceof ThreadDeath) {
-            throw (ThreadDeath) t;
+        /** High water mark - largest numIdle encountered */
+        private transient volatile int idleHighWaterMark;
+
+        /**
+         * Creates a new ErodingFactor with the given erosion factor.
+         *
+         * @param factor
+         *            erosion factor
+         */
+        public ErodingFactor(final float factor) {
+            this.factor = factor;
+            nextShrinkMillis = System.currentTimeMillis() + (long) (900000 * 
factor); // now
+                                                                               
 // +
+                                                                               
 // 15
+                                                                               
 // min
+                                                                               
 // *
+                                                                               
 // factor
+            idleHighWaterMark = 1;
         }
-        if (t instanceof VirtualMachineError) {
-            throw (VirtualMachineError) t;
+
+        /**
+         * Returns the time of the next erosion event.
+         *
+         * @return next shrink time
+         */
+        public long getNextShrink() {
+            return nextShrinkMillis;
         }
-        // All other instances of Throwable will be silently swallowed
-    }
 
-    /**
-     * Periodically check the idle object count for the pool. At most one idle
-     * object will be added per period. If there is an exception when calling
-     * {@link ObjectPool#addObject()} then no more checks will be performed.
-     *
-     * @param pool
-     *            the pool to check periodically.
-     * @param minIdle
-     *            if the {@link ObjectPool#getNumIdle()} is less than this then
-     *            add an idle object.
-     * @param period
-     *            the frequency to check the number of idle objects in a pool,
-     *            see {@link Timer#schedule(TimerTask, long, long)}.
-     * @param <T> the type of objects in the pool
-     * @return the {@link TimerTask} that will periodically check the pools 
idle
-     *         object count.
-     * @throws IllegalArgumentException
-     *             when {@code pool} is {@code null} or when {@code minIdle} is
-     *             negative or when {@code period} isn't valid for
-     *             {@link Timer#schedule(TimerTask, long, long)}
-     */
-    public static <T> TimerTask checkMinIdle(final ObjectPool<T> pool,
-            final int minIdle, final long period)
-            throws IllegalArgumentException {
-        if (pool == null) {
-            throw new IllegalArgumentException(MSG_NULL_KEYED_POOL);
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public String toString() {
+            return "ErodingFactor{" + "factor=" + factor +
+                    ", idleHighWaterMark=" + idleHighWaterMark + '}';
         }
-        if (minIdle < 0) {
-            throw new IllegalArgumentException(MSG_MIN_IDLE);
+
+        /**
+         * Updates internal state using the supplied time and numIdle.
+         *
+         * @param nowMillis
+         *            current time
+         * @param numIdle
+         *            number of idle elements in the pool
+         */
+        public void update(final long nowMillis, final int numIdle) {
+            final int idle = Math.max(0, numIdle);
+            idleHighWaterMark = Math.max(idle, idleHighWaterMark);
+            final float maxInterval = 15f;
+            final float minutes = maxInterval +
+                    ((1f - maxInterval) / idleHighWaterMark) * idle;
+            nextShrinkMillis = nowMillis + (long) (minutes * 60000f * factor);
         }
-        final TimerTask task = new ObjectPoolMinIdleTimerTask<>(pool, minIdle);
-        getMinIdleTimer().schedule(task, 0L, period);
-        return task;
     }
-
     /**
-     * Periodically check the idle object count for the key in the keyedPool. 
At
-     * most one idle object will be added per period. If there is an exception
-     * when calling {@link KeyedObjectPool#addObject(Object)} then no more
-     * checks for that key will be performed.
+     * Decorates a keyed object pool, adding "eroding" behavior. Based on the
+     * configured erosion factor, objects returning to the pool
+     * may be invalidated instead of being added to idle capacity.
      *
-     * @param keyedPool
-     *            the keyedPool to check periodically.
-     * @param key
-     *            the key to check the idle count of.
-     * @param minIdle
-     *            if the {@link KeyedObjectPool#getNumIdle(Object)} is less 
than
-     *            this then add an idle object.
-     * @param period
-     *            the frequency to check the number of idle objects in a
-     *            keyedPool, see {@link Timer#schedule(TimerTask, long, long)}.
-     * @param <K> the type of the pool key
-     * @param <V> the type of pool entries
-     * @return the {@link TimerTask} that will periodically check the pools 
idle
-     *         object count.
-     * @throws IllegalArgumentException
-     *             when {@code keyedPool}, {@code key} is {@code null} or
-     *             when {@code minIdle} is negative or when {@code period} 
isn't
-     *             valid for {@link Timer#schedule(TimerTask, long, long)}.
+     * @param <K> object pool key type
+     * @param <V> object pool value type
      */
-    public static <K, V> TimerTask checkMinIdle(
-            final KeyedObjectPool<K, V> keyedPool, final K key,
-            final int minIdle, final long period)
-            throws IllegalArgumentException {
-        if (keyedPool == null) {
-            throw new IllegalArgumentException(MSG_NULL_KEYED_POOL);
+    private static class ErodingKeyedObjectPool<K, V> implements
+            KeyedObjectPool<K, V> {
+
+        /** Underlying pool */
+        private final KeyedObjectPool<K, V> keyedPool;
+
+        /** Erosion factor */
+        private final ErodingFactor erodingFactor;
+
+        /**
+         * Creates an ErodingObjectPool wrapping the given pool using the
+         * specified erosion factor.
+         *
+         * @param keyedPool
+         *            underlying pool - must not be null
+         * @param erodingFactor
+         *            erosion factor - determines the frequency of erosion
+         *            events
+         * @see #erodingFactor
+         */
+        protected ErodingKeyedObjectPool(final KeyedObjectPool<K, V> keyedPool,
+                final ErodingFactor erodingFactor) {
+            if (keyedPool == null) {
+                throw new IllegalArgumentException(
+                        MSG_NULL_KEYED_POOL);
+            }
+            this.keyedPool = keyedPool;
+            this.erodingFactor = erodingFactor;
         }
-        if (key == null) {
-            throw new IllegalArgumentException(MSG_NULL_KEY);
+
+        /**
+         * Creates an ErodingObjectPool wrapping the given pool using the
+         * specified erosion factor.
+         *
+         * @param keyedPool
+         *            underlying pool
+         * @param factor
+         *            erosion factor - determines the frequency of erosion
+         *            events
+         * @see #erodingFactor
+         */
+        public ErodingKeyedObjectPool(final KeyedObjectPool<K, V> keyedPool,
+                final float factor) {
+            this(keyedPool, new ErodingFactor(factor));
         }
-        if (minIdle < 0) {
-            throw new IllegalArgumentException(MSG_MIN_IDLE);
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public void addObject(final K key) throws Exception,
+                IllegalStateException, UnsupportedOperationException {
+            keyedPool.addObject(key);
         }
-        final TimerTask task = new KeyedObjectPoolMinIdleTimerTask<>(
-                keyedPool, key, minIdle);
-        getMinIdleTimer().schedule(task, 0L, period);
-        return task;
-    }
 
-    /**
-     * Periodically check the idle object count for each key in the
-     * {@code Collection keys} in the keyedPool. At most one idle object will 
be
-     * added per period.
-     *
-     * @param keyedPool
-     *            the keyedPool to check periodically.
-     * @param keys
-     *            a collection of keys to check the idle object count.
-     * @param minIdle
-     *            if the {@link KeyedObjectPool#getNumIdle(Object)} is less 
than
-     *            this then add an idle object.
-     * @param period
-     *            the frequency to check the number of idle objects in a
-     *            keyedPool, see {@link Timer#schedule(TimerTask, long, long)}.
-     * @param <K> the type of the pool key
-     * @param <V> the type of pool entries
-     * @return a {@link Map} of key and {@link TimerTask} pairs that will
-     *         periodically check the pools idle object count.
-     * @throws IllegalArgumentException
-     *             when {@code keyedPool}, {@code keys}, or any of the values 
in
-     *             the collection is {@code null} or when {@code minIdle} is
-     *             negative or when {@code period} isn't valid for
-     *             {@link Timer#schedule(TimerTask, long, long)}.
-     * @see #checkMinIdle(KeyedObjectPool, Object, int, long)
-     */
-    public static <K, V> Map<K, TimerTask> checkMinIdle(
-            final KeyedObjectPool<K, V> keyedPool, final Collection<K> keys,
-            final int minIdle, final long period)
-            throws IllegalArgumentException {
-        if (keys == null) {
-            throw new IllegalArgumentException(MSG_NULL_KEYS);
-        }
-        final Map<K, TimerTask> tasks = new HashMap<>(keys.size());
-        final Iterator<K> iter = keys.iterator();
-        while (iter.hasNext()) {
-            final K key = iter.next();
-            final TimerTask task = checkMinIdle(keyedPool, key, minIdle, 
period);
-            tasks.put(key, task);
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public V borrowObject(final K key) throws Exception,
+                NoSuchElementException, IllegalStateException {
+            return keyedPool.borrowObject(key);
         }
-        return tasks;
-    }
 
-    /**
-     * Calls {@link ObjectPool#addObject()} on {@code pool} {@code count} 
number
-     * of times.
-     *
-     * @param pool
-     *            the pool to prefill.
-     * @param count
-     *            the number of idle objects to add.
-     * @param <T> the type of objects in the pool
-     * @throws Exception
-     *             when {@link ObjectPool#addObject()} fails.
-     * @throws IllegalArgumentException
-     *             when {@code pool} is {@code null}.
-     * @deprecated Use {@link ObjectPool#addObjects(int)}.
-     */
-    @Deprecated
-    public static <T> void prefill(final ObjectPool<T> pool, final int count)
-            throws Exception, IllegalArgumentException {
-        if (pool == null) {
-            throw new IllegalArgumentException(MSG_NULL_POOL);
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public void clear() throws Exception, UnsupportedOperationException {
+            keyedPool.clear();
         }
-        pool.addObjects(count);
-    }
 
-    /**
-     * Calls {@link KeyedObjectPool#addObject(Object)} on {@code keyedPool} 
with
-     * {@code key} {@code count} number of times.
-     *
-     * @param keyedPool
-     *            the keyedPool to prefill.
-     * @param key
-     *            the key to add objects for.
-     * @param count
-     *            the number of idle objects to add for {@code key}.
-     * @param <K> the type of the pool key
-     * @param <V> the type of pool entries
-     * @throws Exception
-     *             when {@link KeyedObjectPool#addObject(Object)} fails.
-     * @throws IllegalArgumentException
-     *             when {@code keyedPool} or {@code key} is {@code null}.
-     * @deprecated Use {@link KeyedObjectPool#addObjects(Object, int)}.
-     */
-    @Deprecated
-    public static <K, V> void prefill(final KeyedObjectPool<K, V> keyedPool,
-            final K key, final int count) throws Exception,
-            IllegalArgumentException {
-        if (keyedPool == null) {
-            throw new IllegalArgumentException(MSG_NULL_KEYED_POOL);
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public void clear(final K key) throws Exception,
+                UnsupportedOperationException {
+            keyedPool.clear(key);
         }
-        keyedPool.addObjects(key, count);
-    }
 
-    /**
-     * Calls {@link KeyedObjectPool#addObject(Object)} on {@code keyedPool} 
with
-     * each key in {@code keys} for {@code count} number of times. This has
-     * the same effect as calling {@link #prefill(KeyedObjectPool, Object, 
int)}
-     * for each key in the {@code keys} collection.
-     *
-     * @param keyedPool
-     *            the keyedPool to prefill.
-     * @param keys
-     *            {@link Collection} of keys to add objects for.
-     * @param count
-     *            the number of idle objects to add for each {@code key}.
-     * @param <K> the type of the pool key
-     * @param <V> the type of pool entries
-     * @throws Exception
-     *             when {@link KeyedObjectPool#addObject(Object)} fails.
-     * @throws IllegalArgumentException
-     *             when {@code keyedPool}, {@code keys}, or any value in
-     *             {@code keys} is {@code null}.
-     * @see #prefill(KeyedObjectPool, Object, int)
-     * @deprecated Use {@link KeyedObjectPool#addObjects(Collection, int)}.
-     */
-    @Deprecated
-    public static <K, V> void prefill(final KeyedObjectPool<K, V> keyedPool,
-            final Collection<K> keys, final int count) throws Exception,
-            IllegalArgumentException {
-        if (keys == null) {
-            throw new IllegalArgumentException(MSG_NULL_KEYS);
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public void close() {
+            try {
+                keyedPool.close();
+            } catch (final Exception e) {
+                // swallowed
+            }
         }
-        keyedPool.addObjects(keys, count);
-    }
 
-    /**
-     * Returns a synchronized (thread-safe) ObjectPool backed by the specified
-     * ObjectPool.
-     * <p>
-     * <b>Note:</b> This should not be used on pool implementations that 
already
-     * provide proper synchronization such as the pools provided in the Commons
-     * Pool library. Wrapping a pool that {@link #wait() waits} for poolable
-     * objects to be returned before allowing another one to be borrowed with
-     * another layer of synchronization will cause liveliness issues or a
-     * deadlock.
-     * </p>
-     *
-     * @param pool
-     *            the ObjectPool to be "wrapped" in a synchronized ObjectPool.
-     * @param <T> the type of objects in the pool
-     * @throws IllegalArgumentException
-     *             when {@code pool} is {@code null}.
-     * @return a synchronized view of the specified ObjectPool.
-     */
-    public static <T> ObjectPool<T> synchronizedPool(final ObjectPool<T> pool) 
{
-        if (pool == null) {
-            throw new IllegalArgumentException(MSG_NULL_POOL);
+        /**
+         * Returns the eroding factor for the given key
+         *
+         * @param key
+         *            key
+         * @return eroding factor for the given keyed pool
+         */
+        protected ErodingFactor getErodingFactor(final K key) {
+            return erodingFactor;
         }
-        /*
-         * assert !(pool instanceof GenericObjectPool) :
-         * "GenericObjectPool is already thread-safe"; assert !(pool instanceof
-         * SoftReferenceObjectPool) :
-         * "SoftReferenceObjectPool is already thread-safe"; assert !(pool
-         * instanceof StackObjectPool) :
-         * "StackObjectPool is already thread-safe"; assert
-         * !"org.apache.commons.pool.composite.CompositeObjectPool"
-         * .equals(pool.getClass().getName()) :
-         * "CompositeObjectPools are already thread-safe";
+
+        /**
+         * Returns the underlying pool
+         *
+         * @return the keyed pool that this ErodingKeyedObjectPool wraps
          */
-        return new SynchronizedObjectPool<>(pool);
-    }
+        protected KeyedObjectPool<K, V> getKeyedPool() {
+            return keyedPool;
+        }
 
-    /**
-     * Returns a synchronized (thread-safe) KeyedObjectPool backed by the
-     * specified KeyedObjectPool.
-     * <p>
-     * <b>Note:</b> This should not be used on pool implementations that 
already
-     * provide proper synchronization such as the pools provided in the Commons
-     * Pool library. Wrapping a pool that {@link #wait() waits} for poolable
-     * objects to be returned before allowing another one to be borrowed with
-     * another layer of synchronization will cause liveliness issues or a
-     * deadlock.
-     * </p>
-     *
-     * @param keyedPool
-     *            the KeyedObjectPool to be "wrapped" in a synchronized
-     *            KeyedObjectPool.
-     * @param <K> the type of the pool key
-     * @param <V> the type of pool entries
-     * @return a synchronized view of the specified KeyedObjectPool.
-     */
-    public static <K, V> KeyedObjectPool<K, V> synchronizedPool(
-            final KeyedObjectPool<K, V> keyedPool) {
-        /*
-         * assert !(keyedPool instanceof GenericKeyedObjectPool) :
-         * "GenericKeyedObjectPool is already thread-safe"; assert !(keyedPool
-         * instanceof StackKeyedObjectPool) :
-         * "StackKeyedObjectPool is already thread-safe"; assert
-         * !"org.apache.commons.pool.composite.CompositeKeyedObjectPool"
-         * .equals(keyedPool.getClass().getName()) :
-         * "CompositeKeyedObjectPools are already thread-safe";
+        /**
+         * {@inheritDoc}
          */
-        return new SynchronizedKeyedObjectPool<>(keyedPool);
-    }
+        @Override
+        public int getNumActive() {
+            return keyedPool.getNumActive();
+        }
 
-    /**
-     * Returns a synchronized (thread-safe) PooledObjectFactory backed by the
-     * specified PooledObjectFactory.
-     *
-     * @param factory
-     *            the PooledObjectFactory to be "wrapped" in a synchronized
-     *            PooledObjectFactory.
-     * @param <T> the type of objects in the pool
-     * @return a synchronized view of the specified PooledObjectFactory.
-     */
-    public static <T> PooledObjectFactory<T> synchronizedPooledFactory(
-            final PooledObjectFactory<T> factory) {
-        return new SynchronizedPooledObjectFactory<>(factory);
-    }
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public int getNumActive(final K key) {
+            return keyedPool.getNumActive(key);
+        }
 
-    /**
-     * Returns a synchronized (thread-safe) KeyedPooledObjectFactory backed by
-     * the specified KeyedPoolableObjectFactory.
-     *
-     * @param keyedFactory
-     *            the KeyedPooledObjectFactory to be "wrapped" in a
-     *            synchronized KeyedPooledObjectFactory.
-     * @param <K> the type of the pool key
-     * @param <V> the type of pool entries
-     * @return a synchronized view of the specified KeyedPooledObjectFactory.
-     */
-    public static <K, V> KeyedPooledObjectFactory<K, V> 
synchronizedKeyedPooledFactory(
-            final KeyedPooledObjectFactory<K, V> keyedFactory) {
-        return new SynchronizedKeyedPooledObjectFactory<>(keyedFactory);
-    }
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public int getNumIdle() {
+            return keyedPool.getNumIdle();
+        }
 
-    /**
-     * Returns a pool that adaptively decreases its size when idle objects are
-     * no longer needed. This is intended as an always thread-safe alternative
-     * to using an idle object evictor provided by many pool implementations.
-     * This is also an effective way to shrink FIFO ordered pools that
-     * experience load spikes.
-     *
-     * @param pool
-     *            the ObjectPool to be decorated so it shrinks its idle count
-     *            when possible.
-     * @param <T> the type of objects in the pool
-     * @throws IllegalArgumentException
-     *             when {@code pool} is {@code null}.
-     * @return a pool that adaptively decreases its size when idle objects are
-     *         no longer needed.
-     * @see #erodingPool(ObjectPool, float)
-     */
-    public static <T> ObjectPool<T> erodingPool(final ObjectPool<T> pool) {
-        return erodingPool(pool, 1f);
-    }
-
-    /**
-     * Returns a pool that adaptively decreases its size when idle objects are
-     * no longer needed. This is intended as an always thread-safe alternative
-     * to using an idle object evictor provided by many pool implementations.
-     * This is also an effective way to shrink FIFO ordered pools that
-     * experience load spikes.
-     * <p>
-     * The factor parameter provides a mechanism to tweak the rate at which the
-     * pool tries to shrink its size. Values between 0 and 1 cause the pool to
-     * try to shrink its size more often. Values greater than 1 cause the pool
-     * to less frequently try to shrink its size.
-     * </p>
-     *
-     * @param pool
-     *            the ObjectPool to be decorated so it shrinks its idle count
-     *            when possible.
-     * @param factor
-     *            a positive value to scale the rate at which the pool tries to
-     *            reduce its size. If 0 &lt; factor &lt; 1 then the pool
-     *            shrinks more aggressively. If 1 &lt; factor then the pool
-     *            shrinks less aggressively.
-     * @param <T> the type of objects in the pool
-     * @throws IllegalArgumentException
-     *             when {@code pool} is {@code null} or when {@code factor} is
-     *             not positive.
-     * @return a pool that adaptively decreases its size when idle objects are
-     *         no longer needed.
-     * @see #erodingPool(ObjectPool)
-     */
-    public static <T> ObjectPool<T> erodingPool(final ObjectPool<T> pool,
-            final float factor) {
-        if (pool == null) {
-            throw new IllegalArgumentException(MSG_NULL_POOL);
-        }
-        if (factor <= 0f) {
-            throw new IllegalArgumentException(MSG_FACTOR_NEGATIVE);
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public int getNumIdle(final K key) {
+            return keyedPool.getNumIdle(key);
         }
-        return new ErodingObjectPool<>(pool, factor);
-    }
 
-    /**
-     * Returns a pool that adaptively decreases its size when idle objects are
-     * no longer needed. This is intended as an always thread-safe alternative
-     * to using an idle object evictor provided by many pool implementations.
-     * This is also an effective way to shrink FIFO ordered pools that
-     * experience load spikes.
-     *
-     * @param keyedPool
-     *            the KeyedObjectPool to be decorated so it shrinks its idle
-     *            count when possible.
-     * @param <K> the type of the pool key
-     * @param <V> the type of pool entries
-     * @throws IllegalArgumentException
-     *             when {@code keyedPool} is {@code null}.
-     * @return a pool that adaptively decreases its size when idle objects are
-     *         no longer needed.
-     * @see #erodingPool(KeyedObjectPool, float)
-     * @see #erodingPool(KeyedObjectPool, float, boolean)
-     */
-    public static <K, V> KeyedObjectPool<K, V> erodingPool(
-            final KeyedObjectPool<K, V> keyedPool) {
-        return erodingPool(keyedPool, 1f);
-    }
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public void invalidateObject(final K key, final V obj) {
+            try {
+                keyedPool.invalidateObject(key, obj);
+            } catch (final Exception e) {
+                // swallowed
+            }
+        }
 
-    /**
-     * Returns a pool that adaptively decreases its size when idle objects are
-     * no longer needed. This is intended as an always thread-safe alternative
-     * to using an idle object evictor provided by many pool implementations.
-     * This is also an effective way to shrink FIFO ordered pools that
-     * experience load spikes.
-     * <p>
-     * The factor parameter provides a mechanism to tweak the rate at which the
-     * pool tries to shrink its size. Values between 0 and 1 cause the pool to
-     * try to shrink its size more often. Values greater than 1 cause the pool
-     * to less frequently try to shrink its size.
-     * </p>
-     *
-     * @param keyedPool
-     *            the KeyedObjectPool to be decorated so it shrinks its idle
-     *            count when possible.
-     * @param factor
-     *            a positive value to scale the rate at which the pool tries to
-     *            reduce its size. If 0 &lt; factor &lt; 1 then the pool
-     *            shrinks more aggressively. If 1 &lt; factor then the pool
-     *            shrinks less aggressively.
-     * @param <K> the type of the pool key
-     * @param <V> the type of pool entries
-     * @throws IllegalArgumentException
-     *             when {@code keyedPool} is {@code null} or when {@code 
factor}
-     *             is not positive.
-     * @return a pool that adaptively decreases its size when idle objects are
-     *         no longer needed.
-     * @see #erodingPool(KeyedObjectPool, float, boolean)
-     */
-    public static <K, V> KeyedObjectPool<K, V> erodingPool(
-            final KeyedObjectPool<K, V> keyedPool, final float factor) {
-        return erodingPool(keyedPool, factor, false);
-    }
+        /**
+         * Returns obj to the pool, unless erosion is triggered, in which case
+         * obj is invalidated. Erosion is triggered when there are idle
+         * instances in the pool associated with the given key and more than 
the
+         * configured {@link #erodingFactor erosion factor} time has elapsed
+         * since the last returnObject activation.
+         *
+         * @param obj
+         *            object to return or invalidate
+         * @param key
+         *            key
+         * @see #erodingFactor
+         */
+        @Override
+        public void returnObject(final K key, final V obj) throws Exception {
+            boolean discard = false;
+            final long nowMillis = System.currentTimeMillis();
+            final ErodingFactor factor = getErodingFactor(key);
+            synchronized (keyedPool) {
+                if (factor.getNextShrink() < nowMillis) {
+                    final int numIdle = getNumIdle(key);
+                    if (numIdle > 0) {
+                        discard = true;
+                    }
 
-    /**
-     * Returns a pool that adaptively decreases its size when idle objects are
-     * no longer needed. This is intended as an always thread-safe alternative
-     * to using an idle object evictor provided by many pool implementations.
-     * This is also an effective way to shrink FIFO ordered pools that
-     * experience load spikes.
-     * <p>
-     * The factor parameter provides a mechanism to tweak the rate at which the
-     * pool tries to shrink its size. Values between 0 and 1 cause the pool to
-     * try to shrink its size more often. Values greater than 1 cause the pool
-     * to less frequently try to shrink its size.
-     * </p>
-     * <p>
-     * The perKey parameter determines if the pool shrinks on a whole pool 
basis
-     * or a per key basis. When perKey is false, the keys do not have an effect
-     * on the rate at which the pool tries to shrink its size. When perKey is
-     * true, each key is shrunk independently.
-     * </p>
-     *
-     * @param keyedPool
-     *            the KeyedObjectPool to be decorated so it shrinks its idle
-     *            count when possible.
-     * @param factor
-     *            a positive value to scale the rate at which the pool tries to
-     *            reduce its size. If 0 &lt; factor &lt; 1 then the pool
-     *            shrinks more aggressively. If 1 &lt; factor then the pool
-     *            shrinks less aggressively.
-     * @param perKey
-     *            when true, each key is treated independently.
-     * @param <K> the type of the pool key
-     * @param <V> the type of pool entries
-     * @throws IllegalArgumentException
-     *             when {@code keyedPool} is {@code null} or when {@code 
factor}
-     *             is not positive.
-     * @return a pool that adaptively decreases its size when idle objects are
-     *         no longer needed.
-     * @see #erodingPool(KeyedObjectPool)
-     * @see #erodingPool(KeyedObjectPool, float)
-     */
-    public static <K, V> KeyedObjectPool<K, V> erodingPool(
-            final KeyedObjectPool<K, V> keyedPool, final float factor,
-            final boolean perKey) {
-        if (keyedPool == null) {
-            throw new IllegalArgumentException(MSG_NULL_KEYED_POOL);
-        }
-        if (factor <= 0f) {
-            throw new IllegalArgumentException(MSG_FACTOR_NEGATIVE);
-        }
-        if (perKey) {
-            return new ErodingPerKeyKeyedObjectPool<>(keyedPool, factor);
+                    factor.update(nowMillis, numIdle);
+                }
+            }
+            try {
+                if (discard) {
+                    keyedPool.invalidateObject(key, obj);
+                } else {
+                    keyedPool.returnObject(key, obj);
+                }
+            } catch (final Exception e) {
+                // swallowed
+            }
         }
-        return new ErodingKeyedObjectPool<>(keyedPool, factor);
-    }
 
-    /**
-     * Gets the {@code Timer} for checking keyedPool's idle count.
-     *
-     * @return the {@link Timer} for checking keyedPool's idle count.
-     */
-    private static Timer getMinIdleTimer() {
-        return TimerHolder.MIN_IDLE_TIMER;
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public String toString() {
+            return "ErodingKeyedObjectPool{" + "factor=" +
+                    erodingFactor + ", keyedPool=" + keyedPool + '}';
+        }
     }
-
     /**
-     * Timer task that adds objects to the pool until the number of idle
-     * instances reaches the configured minIdle. Note that this is not the same
-     * as the pool's minIdle setting.
+     * Decorates an object pool, adding "eroding" behavior. Based on the
+     * configured {@link #factor erosion factor}, objects returning to the pool
+     * may be invalidated instead of being added to idle capacity.
      *
      * @param <T> type of objects in the pool
      */
-    private static final class ObjectPoolMinIdleTimerTask<T> extends TimerTask 
{
-
-        /** Minimum number of idle instances. Not the same as 
pool.getMinIdle(). */
-        private final int minIdle;
+    private static class ErodingObjectPool<T> implements ObjectPool<T> {
 
-        /** Object pool */
+        /** Underlying object pool */
         private final ObjectPool<T> pool;
 
+        /** Erosion factor */
+        private final ErodingFactor factor;
+
         /**
-         * Create a new ObjectPoolMinIdleTimerTask for the given pool with the
-         * given minIdle setting.
+         * Creates an ErodingObjectPool wrapping the given pool using the
+         * specified erosion factor.
          *
          * @param pool
-         *            object pool
-         * @param minIdle
-         *            number of idle instances to maintain
-         * @throws IllegalArgumentException
-         *             if the pool is null
+         *            underlying pool
+         * @param factor
+         *            erosion factor - determines the frequency of erosion
+         *            events
+         * @see #factor
          */
-        ObjectPoolMinIdleTimerTask(final ObjectPool<T> pool, final int minIdle)
-                throws IllegalArgumentException {
-            if (pool == null) {
-                throw new IllegalArgumentException(MSG_NULL_POOL);
-            }
+        public ErodingObjectPool(final ObjectPool<T> pool, final float factor) 
{
             this.pool = pool;
-            this.minIdle = minIdle;
+            this.factor = new ErodingFactor(factor);
         }
 
         /**
          * {@inheritDoc}
          */
         @Override
-        public void run() {
-            boolean success = false;
+        public void addObject() throws Exception, IllegalStateException,
+                UnsupportedOperationException {
+            pool.addObject();
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public T borrowObject() throws Exception, NoSuchElementException,
+                IllegalStateException {
+            return pool.borrowObject();
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public void clear() throws Exception, UnsupportedOperationException {
+            pool.clear();
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public void close() {
             try {
-                if (pool.getNumIdle() < minIdle) {
-                    pool.addObject();
-                }
-                success = true;
+                pool.close();
+            } catch (final Exception e) {
+                // swallowed
+            }
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public int getNumActive() {
+            return pool.getNumActive();
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public int getNumIdle() {
+            return pool.getNumIdle();
+        }
 
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public void invalidateObject(final T obj) {
+            try {
+                pool.invalidateObject(obj);
             } catch (final Exception e) {
-                cancel();
-            } finally {
-                // detect other types of Throwable and cancel this Timer
-                if (!success) {
-                    cancel();
+                // swallowed
+            }
+        }
+
+        /**
+         * Returns obj to the pool, unless erosion is triggered, in which case
+         * obj is invalidated. Erosion is triggered when there are idle
+         * instances in the pool and more than the {@link #factor erosion
+         * factor}-determined time has elapsed since the last returnObject
+         * activation.
+         *
+         * @param obj
+         *            object to return or invalidate
+         * @see #factor
+         */
+        @Override
+        public void returnObject(final T obj) {
+            boolean discard = false;
+            final long nowMillis = System.currentTimeMillis();
+            synchronized (pool) {
+                if (factor.getNextShrink() < nowMillis) { // XXX: Pool 3: move 
test
+                                                    // out of sync block
+                    final int numIdle = pool.getNumIdle();
+                    if (numIdle > 0) {
+                        discard = true;
+                    }
+
+                    factor.update(nowMillis, numIdle);
+                }
+            }
+            try {
+                if (discard) {
+                    pool.invalidateObject(obj);
+                } else {
+                    pool.returnObject(obj);
                 }
+            } catch (final Exception e) {
+                // swallowed
             }
         }
 
@@ -635,15 +460,67 @@ public final class PoolUtils {
          */
         @Override
         public String toString() {
-            final StringBuilder sb = new StringBuilder();
-            sb.append("ObjectPoolMinIdleTimerTask");
-            sb.append("{minIdle=").append(minIdle);
-            sb.append(", pool=").append(pool);
-            sb.append('}');
-            return sb.toString();
+            return "ErodingObjectPool{" + "factor=" + factor + ", pool=" +
+                    pool + '}';
         }
     }
+    /**
+     * Extends ErodingKeyedObjectPool to allow erosion to take place on a
+     * per-key basis. Timing of erosion events is tracked separately for
+     * separate keyed pools.
+     *
+     * @param <K> object pool key type
+     * @param <V> object pool value type
+     */
+    private static final class ErodingPerKeyKeyedObjectPool<K, V> extends
+            ErodingKeyedObjectPool<K, V> {
+
+        /** Erosion factor - same for all pools */
+        private final float factor;
+
+        /** Map of ErodingFactor instances keyed on pool keys */
+        private final Map<K, ErodingFactor> factors = 
Collections.synchronizedMap(new HashMap<K, ErodingFactor>());
+
+        /**
+         * Creates a new ErordingPerKeyKeyedObjectPool decorating the given 
keyed
+         * pool with the specified erosion factor.
+         *
+         * @param keyedPool
+         *            underlying keyed pool
+         * @param factor
+         *            erosion factor
+         */
+        public ErodingPerKeyKeyedObjectPool(
+                final KeyedObjectPool<K, V> keyedPool, final float factor) {
+            super(keyedPool, null);
+            this.factor = factor;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        protected ErodingFactor getErodingFactor(final K key) {
+            ErodingFactor eFactor = factors.get(key);
+            // this may result in two ErodingFactors being created for a key
+            // since they are small and cheap this is okay.
+            if (eFactor == null) {
+                eFactor = new ErodingFactor(this.factor);
+                factors.put(key, eFactor);
+            }
+            return eFactor;
+        }
 
+        /**
+         * {@inheritDoc}
+         */
+        @SuppressWarnings("resource") // getKeyedPool(): ivar access
+        @Override
+        public String toString() {
+            return "ErodingPerKeyKeyedObjectPool{" + "factor=" + factor +
+                    ", keyedPool=" + getKeyedPool() + '}';
+        }
+    }
     /**
      * Timer task that adds objects to the pool until the number of idle
      * instances for the given key reaches the configured minIdle. Note that
@@ -724,166 +601,60 @@ public final class PoolUtils {
             return sb.toString();
         }
     }
-
     /**
-     * A synchronized (thread-safe) ObjectPool backed by the specified
-     * ObjectPool.
-     * <p>
-     * <b>Note:</b> This should not be used on pool implementations that 
already
-     * provide proper synchronization such as the pools provided in the Commons
-     * Pool library. Wrapping a pool that {@link #wait() waits} for poolable
-     * objects to be returned before allowing another one to be borrowed with
-     * another layer of synchronization will cause liveliness issues or a
-     * deadlock.
-     * </p>
+     * Timer task that adds objects to the pool until the number of idle
+     * instances reaches the configured minIdle. Note that this is not the same
+     * as the pool's minIdle setting.
      *
      * @param <T> type of objects in the pool
      */
-    private static final class SynchronizedObjectPool<T> implements 
ObjectPool<T> {
+    private static final class ObjectPoolMinIdleTimerTask<T> extends TimerTask 
{
 
-        /**
-         * Object whose monitor is used to synchronize methods on the wrapped
-         * pool.
-         */
-        private final ReentrantReadWriteLock readWriteLock = new 
ReentrantReadWriteLock();
+        /** Minimum number of idle instances. Not the same as 
pool.getMinIdle(). */
+        private final int minIdle;
 
-        /** the underlying object pool */
+        /** Object pool */
         private final ObjectPool<T> pool;
 
         /**
-         * Creates a new SynchronizedObjectPool wrapping the given pool.
+         * Create a new ObjectPoolMinIdleTimerTask for the given pool with the
+         * given minIdle setting.
          *
          * @param pool
-         *            the ObjectPool to be "wrapped" in a synchronized
-         *            ObjectPool.
+         *            object pool
+         * @param minIdle
+         *            number of idle instances to maintain
          * @throws IllegalArgumentException
          *             if the pool is null
          */
-        SynchronizedObjectPool(final ObjectPool<T> pool)
+        ObjectPoolMinIdleTimerTask(final ObjectPool<T> pool, final int minIdle)
                 throws IllegalArgumentException {
             if (pool == null) {
                 throw new IllegalArgumentException(MSG_NULL_POOL);
             }
             this.pool = pool;
+            this.minIdle = minIdle;
         }
 
         /**
          * {@inheritDoc}
          */
         @Override
-        public T borrowObject() throws Exception, NoSuchElementException,
-                IllegalStateException {
-            final WriteLock writeLock = readWriteLock.writeLock();
-            writeLock.lock();
+        public void run() {
+            boolean success = false;
             try {
-                return pool.borrowObject();
-            } finally {
-                writeLock.unlock();
-            }
-        }
+                if (pool.getNumIdle() < minIdle) {
+                    pool.addObject();
+                }
+                success = true;
 
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        public void returnObject(final T obj) {
-            final WriteLock writeLock = readWriteLock.writeLock();
-            writeLock.lock();
-            try {
-                pool.returnObject(obj);
             } catch (final Exception e) {
-                // swallowed as of Pool 2
+                cancel();
             } finally {
-                writeLock.unlock();
-            }
-        }
-
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        public void invalidateObject(final T obj) {
-            final WriteLock writeLock = readWriteLock.writeLock();
-            writeLock.lock();
-            try {
-                pool.invalidateObject(obj);
-            } catch (final Exception e) {
-                // swallowed as of Pool 2
-            } finally {
-                writeLock.unlock();
-            }
-        }
-
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        public void addObject() throws Exception, IllegalStateException,
-                UnsupportedOperationException {
-            final WriteLock writeLock = readWriteLock.writeLock();
-            writeLock.lock();
-            try {
-                pool.addObject();
-            } finally {
-                writeLock.unlock();
-            }
-        }
-
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        public int getNumIdle() {
-            final ReadLock readLock = readWriteLock.readLock();
-            readLock.lock();
-            try {
-                return pool.getNumIdle();
-            } finally {
-                readLock.unlock();
-            }
-        }
-
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        public int getNumActive() {
-            final ReadLock readLock = readWriteLock.readLock();
-            readLock.lock();
-            try {
-                return pool.getNumActive();
-            } finally {
-                readLock.unlock();
-            }
-        }
-
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        public void clear() throws Exception, UnsupportedOperationException {
-            final WriteLock writeLock = readWriteLock.writeLock();
-            writeLock.lock();
-            try {
-                pool.clear();
-            } finally {
-                writeLock.unlock();
-            }
-        }
-
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        public void close() {
-            final WriteLock writeLock = readWriteLock.writeLock();
-            writeLock.lock();
-            try {
-                pool.close();
-            } catch (final Exception e) {
-                // swallowed as of Pool 2
-            } finally {
-                writeLock.unlock();
+                // detect other types of Throwable and cancel this Timer
+                if (!success) {
+                    cancel();
+                }
             }
         }
 
@@ -893,8 +664,9 @@ public final class PoolUtils {
         @Override
         public String toString() {
             final StringBuilder sb = new StringBuilder();
-            sb.append("SynchronizedObjectPool");
-            sb.append("{pool=").append(pool);
+            sb.append("ObjectPoolMinIdleTimerTask");
+            sb.append("{minIdle=").append(minIdle);
+            sb.append(", pool=").append(pool);
             sb.append('}');
             return sb.toString();
         }
@@ -948,6 +720,21 @@ public final class PoolUtils {
          * {@inheritDoc}
          */
         @Override
+        public void addObject(final K key) throws Exception,
+                IllegalStateException, UnsupportedOperationException {
+            final WriteLock writeLock = readWriteLock.writeLock();
+            writeLock.lock();
+            try {
+                keyedPool.addObject(key);
+            } finally {
+                writeLock.unlock();
+            }
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
         public V borrowObject(final K key) throws Exception,
                 NoSuchElementException, IllegalStateException {
             final WriteLock writeLock = readWriteLock.writeLock();
@@ -963,13 +750,11 @@ public final class PoolUtils {
          * {@inheritDoc}
          */
         @Override
-        public void returnObject(final K key, final V obj) {
+        public void clear() throws Exception, UnsupportedOperationException {
             final WriteLock writeLock = readWriteLock.writeLock();
             writeLock.lock();
             try {
-                keyedPool.returnObject(key, obj);
-            } catch (final Exception e) {
-                // swallowed
+                keyedPool.clear();
             } finally {
                 writeLock.unlock();
             }
@@ -979,13 +764,12 @@ public final class PoolUtils {
          * {@inheritDoc}
          */
         @Override
-        public void invalidateObject(final K key, final V obj) {
+        public void clear(final K key) throws Exception,
+                UnsupportedOperationException {
             final WriteLock writeLock = readWriteLock.writeLock();
             writeLock.lock();
             try {
-                keyedPool.invalidateObject(key, obj);
-            } catch (final Exception e) {
-                // swallowed as of Pool 2
+                keyedPool.clear(key);
             } finally {
                 writeLock.unlock();
             }
@@ -995,12 +779,13 @@ public final class PoolUtils {
          * {@inheritDoc}
          */
         @Override
-        public void addObject(final K key) throws Exception,
-                IllegalStateException, UnsupportedOperationException {
+        public void close() {
             final WriteLock writeLock = readWriteLock.writeLock();
             writeLock.lock();
             try {
-                keyedPool.addObject(key);
+                keyedPool.close();
+            } catch (final Exception e) {
+                // swallowed as of Pool 2
             } finally {
                 writeLock.unlock();
             }
@@ -1010,11 +795,11 @@ public final class PoolUtils {
          * {@inheritDoc}
          */
         @Override
-        public int getNumIdle(final K key) {
+        public int getNumActive() {
             final ReadLock readLock = readWriteLock.readLock();
             readLock.lock();
             try {
-                return keyedPool.getNumIdle(key);
+                return keyedPool.getNumActive();
             } finally {
                 readLock.unlock();
             }
@@ -1052,11 +837,11 @@ public final class PoolUtils {
          * {@inheritDoc}
          */
         @Override
-        public int getNumActive() {
+        public int getNumIdle(final K key) {
             final ReadLock readLock = readWriteLock.readLock();
             readLock.lock();
             try {
-                return keyedPool.getNumActive();
+                return keyedPool.getNumIdle(key);
             } finally {
                 readLock.unlock();
             }
@@ -1066,26 +851,13 @@ public final class PoolUtils {
          * {@inheritDoc}
          */
         @Override
-        public void clear() throws Exception, UnsupportedOperationException {
-            final WriteLock writeLock = readWriteLock.writeLock();
-            writeLock.lock();
-            try {
-                keyedPool.clear();
-            } finally {
-                writeLock.unlock();
-            }
-        }
-
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        public void clear(final K key) throws Exception,
-                UnsupportedOperationException {
+        public void invalidateObject(final K key, final V obj) {
             final WriteLock writeLock = readWriteLock.writeLock();
             writeLock.lock();
             try {
-                keyedPool.clear(key);
+                keyedPool.invalidateObject(key, obj);
+            } catch (final Exception e) {
+                // swallowed as of Pool 2
             } finally {
                 writeLock.unlock();
             }
@@ -1095,13 +867,13 @@ public final class PoolUtils {
          * {@inheritDoc}
          */
         @Override
-        public void close() {
+        public void returnObject(final K key, final V obj) {
             final WriteLock writeLock = readWriteLock.writeLock();
             writeLock.lock();
             try {
-                keyedPool.close();
+                keyedPool.returnObject(key, obj);
             } catch (final Exception e) {
-                // swallowed as of Pool 2
+                // swallowed
             } finally {
                 writeLock.unlock();
             }
@@ -1121,8 +893,8 @@ public final class PoolUtils {
     }
 
     /**
-     * A fully synchronized PooledObjectFactory that wraps a
-     * PooledObjectFactory and synchronizes access to the wrapped factory
+     * A fully synchronized KeyedPooledObjectFactory that wraps a
+     * KeyedPooledObjectFactory and synchronizes access to the wrapped factory
      * methods.
      * <p>
      * <b>Note:</b> This should not be used on pool implementations that 
already
@@ -1130,42 +902,45 @@ public final class PoolUtils {
      * Pool library.
      * </p>
      *
-     * @param <T> pooled object factory type
+     * @param <K> pooled object factory key type
+     * @param <V> pooled object factory key value
      */
-    private static final class SynchronizedPooledObjectFactory<T> implements
-            PooledObjectFactory<T> {
+    private static final class SynchronizedKeyedPooledObjectFactory<K, V>
+            implements KeyedPooledObjectFactory<K, V> {
 
         /** Synchronization lock */
         private final WriteLock writeLock = new 
ReentrantReadWriteLock().writeLock();
 
         /** Wrapped factory */
-        private final PooledObjectFactory<T> factory;
+        private final KeyedPooledObjectFactory<K, V> keyedFactory;
 
         /**
-         * Creates a SynchronizedPoolableObjectFactory wrapping the given
+         * Creates a SynchronizedKeyedPoolableObjectFactory wrapping the given
          * factory.
          *
-         * @param factory
+         * @param keyedFactory
          *            underlying factory to wrap
          * @throws IllegalArgumentException
          *             if the factory is null
          */
-        SynchronizedPooledObjectFactory(final PooledObjectFactory<T> factory)
+        SynchronizedKeyedPooledObjectFactory(
+                final KeyedPooledObjectFactory<K, V> keyedFactory)
                 throws IllegalArgumentException {
-            if (factory == null) {
-                throw new IllegalArgumentException("factory must not be 
null.");
+            if (keyedFactory == null) {
+                throw new IllegalArgumentException(
+                        "keyedFactory must not be null.");
             }
-            this.factory = factory;
+            this.keyedFactory = keyedFactory;
         }
 
         /**
          * {@inheritDoc}
          */
         @Override
-        public PooledObject<T> makeObject() throws Exception {
+        public void activateObject(final K key, final PooledObject<V> p) 
throws Exception {
             writeLock.lock();
             try {
-                return factory.makeObject();
+                keyedFactory.activateObject(key, p);
             } finally {
                 writeLock.unlock();
             }
@@ -1175,10 +950,10 @@ public final class PoolUtils {
          * {@inheritDoc}
          */
         @Override
-        public void destroyObject(final PooledObject<T> p) throws Exception {
+        public void destroyObject(final K key, final PooledObject<V> p) throws 
Exception {
             writeLock.lock();
             try {
-                factory.destroyObject(p);
+                keyedFactory.destroyObject(key, p);
             } finally {
                 writeLock.unlock();
             }
@@ -1188,10 +963,10 @@ public final class PoolUtils {
          * {@inheritDoc}
          */
         @Override
-        public boolean validateObject(final PooledObject<T> p) {
+        public PooledObject<V> makeObject(final K key) throws Exception {
             writeLock.lock();
             try {
-                return factory.validateObject(p);
+                return keyedFactory.makeObject(key);
             } finally {
                 writeLock.unlock();
             }
@@ -1201,10 +976,10 @@ public final class PoolUtils {
          * {@inheritDoc}
          */
         @Override
-        public void activateObject(final PooledObject<T> p) throws Exception {
+        public void passivateObject(final K key, final PooledObject<V> p) 
throws Exception {
             writeLock.lock();
             try {
-                factory.activateObject(p);
+                keyedFactory.passivateObject(key, p);
             } finally {
                 writeLock.unlock();
             }
@@ -1214,77 +989,80 @@ public final class PoolUtils {
          * {@inheritDoc}
          */
         @Override
-        public void passivateObject(final PooledObject<T> p) throws Exception {
-            writeLock.lock();
-            try {
-                factory.passivateObject(p);
-            } finally {
-                writeLock.unlock();
-            }
+        public String toString() {
+            final StringBuilder sb = new StringBuilder();
+            sb.append("SynchronizedKeyedPoolableObjectFactory");
+            sb.append("{keyedFactory=").append(keyedFactory);
+            sb.append('}');
+            return sb.toString();
         }
 
         /**
          * {@inheritDoc}
          */
         @Override
-        public String toString() {
-            final StringBuilder sb = new StringBuilder();
-            sb.append("SynchronizedPoolableObjectFactory");
-            sb.append("{factory=").append(factory);
-            sb.append('}');
-            return sb.toString();
+        public boolean validateObject(final K key, final PooledObject<V> p) {
+            writeLock.lock();
+            try {
+                return keyedFactory.validateObject(key, p);
+            } finally {
+                writeLock.unlock();
+            }
         }
     }
 
     /**
-     * A fully synchronized KeyedPooledObjectFactory that wraps a
-     * KeyedPooledObjectFactory and synchronizes access to the wrapped factory
-     * methods.
+     * A synchronized (thread-safe) ObjectPool backed by the specified
+     * ObjectPool.
      * <p>
      * <b>Note:</b> This should not be used on pool implementations that 
already
      * provide proper synchronization such as the pools provided in the Commons
-     * Pool library.
+     * Pool library. Wrapping a pool that {@link #wait() waits} for poolable
+     * objects to be returned before allowing another one to be borrowed with
+     * another layer of synchronization will cause liveliness issues or a
+     * deadlock.
      * </p>
      *
-     * @param <K> pooled object factory key type
-     * @param <V> pooled object factory key value
+     * @param <T> type of objects in the pool
      */
-    private static final class SynchronizedKeyedPooledObjectFactory<K, V>
-            implements KeyedPooledObjectFactory<K, V> {
+    private static final class SynchronizedObjectPool<T> implements 
ObjectPool<T> {
 
-        /** Synchronization lock */
-        private final WriteLock writeLock = new 
ReentrantReadWriteLock().writeLock();
+        /**
+         * Object whose monitor is used to synchronize methods on the wrapped
+         * pool.
+         */
+        private final ReentrantReadWriteLock readWriteLock = new 
ReentrantReadWriteLock();
 
-        /** Wrapped factory */
-        private final KeyedPooledObjectFactory<K, V> keyedFactory;
+        /** the underlying object pool */
+        private final ObjectPool<T> pool;
 
         /**
-         * Creates a SynchronizedKeyedPoolableObjectFactory wrapping the given
-         * factory.
+         * Creates a new SynchronizedObjectPool wrapping the given pool.
          *
-         * @param keyedFactory
-         *            underlying factory to wrap
+         * @param pool
+         *            the ObjectPool to be "wrapped" in a synchronized
+         *            ObjectPool.
          * @throws IllegalArgumentException
-         *             if the factory is null
+         *             if the pool is null
          */
-        SynchronizedKeyedPooledObjectFactory(
-                final KeyedPooledObjectFactory<K, V> keyedFactory)
+        SynchronizedObjectPool(final ObjectPool<T> pool)
                 throws IllegalArgumentException {
-            if (keyedFactory == null) {
-                throw new IllegalArgumentException(
-                        "keyedFactory must not be null.");
+            if (pool == null) {
+                throw new IllegalArgumentException(MSG_NULL_POOL);
             }
-            this.keyedFactory = keyedFactory;
+            this.pool = pool;
         }
 
         /**
          * {@inheritDoc}
          */
         @Override
-        public PooledObject<V> makeObject(final K key) throws Exception {
+        public void addObject() throws Exception, IllegalStateException,
+                UnsupportedOperationException {
+            final WriteLock writeLock = readWriteLock.writeLock();
             writeLock.lock();
             try {
-                return keyedFactory.makeObject(key);
+                pool.addObject();
             } finally {
                 writeLock.unlock();
             }
@@ -1294,10 +1072,12 @@ public final class PoolUtils {
          * {@inheritDoc}
          */
         @Override
-        public void destroyObject(final K key, final PooledObject<V> p) throws 
Exception {
+        public T borrowObject() throws Exception, NoSuchElementException,
+                IllegalStateException {
+            final WriteLock writeLock = readWriteLock.writeLock();
             writeLock.lock();
             try {
-                keyedFactory.destroyObject(key, p);
+                return pool.borrowObject();
             } finally {
                 writeLock.unlock();
             }
@@ -1307,10 +1087,11 @@ public final class PoolUtils {
          * {@inheritDoc}
          */
         @Override
-        public boolean validateObject(final K key, final PooledObject<V> p) {
+        public void clear() throws Exception, UnsupportedOperationException {
+            final WriteLock writeLock = readWriteLock.writeLock();
             writeLock.lock();
             try {
-                return keyedFactory.validateObject(key, p);
+                pool.clear();
             } finally {
                 writeLock.unlock();
             }
@@ -1320,10 +1101,13 @@ public final class PoolUtils {
          * {@inheritDoc}
          */
         @Override
-        public void activateObject(final K key, final PooledObject<V> p) 
throws Exception {
+        public void close() {
+            final WriteLock writeLock = readWriteLock.writeLock();
             writeLock.lock();
             try {
-                keyedFactory.activateObject(key, p);
+                pool.close();
+            } catch (final Exception e) {
+                // swallowed as of Pool 2
             } finally {
                 writeLock.unlock();
             }
@@ -1333,12 +1117,13 @@ public final class PoolUtils {
          * {@inheritDoc}
          */
         @Override
-        public void passivateObject(final K key, final PooledObject<V> p) 
throws Exception {
-            writeLock.lock();
+        public int getNumActive() {
+            final ReadLock readLock = readWriteLock.readLock();
+            readLock.lock();
             try {
-                keyedFactory.passivateObject(key, p);
+                return pool.getNumActive();
             } finally {
-                writeLock.unlock();
+                readLock.unlock();
             }
         }
 
@@ -1346,76 +1131,46 @@ public final class PoolUtils {
          * {@inheritDoc}
          */
         @Override
-        public String toString() {
-            final StringBuilder sb = new StringBuilder();
-            sb.append("SynchronizedKeyedPoolableObjectFactory");
-            sb.append("{keyedFactory=").append(keyedFactory);
-            sb.append('}');
-            return sb.toString();
-        }
-    }
-
-    /**
-     * Encapsulate the logic for when the next poolable object should be
-     * discarded. Each time update is called, the next time to shrink is
-     * recomputed, based on the float factor, number of idle instances in the
-     * pool and high water mark. Float factor is assumed to be between 0 and 1.
-     * Values closer to 1 cause less frequent erosion events. Erosion event
-     * timing also depends on numIdle. When this value is relatively high 
(close
-     * to previously established high water mark), erosion occurs more
-     * frequently.
-     */
-    private static final class ErodingFactor {
-        /** Determines frequency of "erosion" events */
-        private final float factor;
-
-        /** Time of next shrink event */
-        private transient volatile long nextShrinkMillis;
-
-        /** High water mark - largest numIdle encountered */
-        private transient volatile int idleHighWaterMark;
-
-        /**
-         * Creates a new ErodingFactor with the given erosion factor.
-         *
-         * @param factor
-         *            erosion factor
-         */
-        public ErodingFactor(final float factor) {
-            this.factor = factor;
-            nextShrinkMillis = System.currentTimeMillis() + (long) (900000 * 
factor); // now
-                                                                               
 // +
-                                                                               
 // 15
-                                                                               
 // min
-                                                                               
 // *
-                                                                               
 // factor
-            idleHighWaterMark = 1;
+        public int getNumIdle() {
+            final ReadLock readLock = readWriteLock.readLock();
+            readLock.lock();
+            try {
+                return pool.getNumIdle();
+            } finally {
+                readLock.unlock();
+            }
         }
 
         /**
-         * Updates internal state using the supplied time and numIdle.
-         *
-         * @param nowMillis
-         *            current time
-         * @param numIdle
-         *            number of idle elements in the pool
+         * {@inheritDoc}
          */
-        public void update(final long nowMillis, final int numIdle) {
-            final int idle = Math.max(0, numIdle);
-            idleHighWaterMark = Math.max(idle, idleHighWaterMark);
-            final float maxInterval = 15f;
-            final float minutes = maxInterval +
-                    ((1f - maxInterval) / idleHighWaterMark) * idle;
-            nextShrinkMillis = nowMillis + (long) (minutes * 60000f * factor);
+        @Override
+        public void invalidateObject(final T obj) {
+            final WriteLock writeLock = readWriteLock.writeLock();
+            writeLock.lock();
+            try {
+                pool.invalidateObject(obj);
+            } catch (final Exception e) {
+                // swallowed as of Pool 2
+            } finally {
+                writeLock.unlock();
+            }
         }
 
         /**
-         * Returns the time of the next erosion event.
-         *
-         * @return next shrink time
+         * {@inheritDoc}
          */
-        public long getNextShrink() {
-            return nextShrinkMillis;
+        @Override
+        public void returnObject(final T obj) {
+            final WriteLock writeLock = readWriteLock.writeLock();
+            writeLock.lock();
+            try {
+                pool.returnObject(obj);
+            } catch (final Exception e) {
+                // swallowed as of Pool 2
+            } finally {
+                writeLock.unlock();
+            }
         }
 
         /**
@@ -1423,85 +1178,75 @@ public final class PoolUtils {
          */
         @Override
         public String toString() {
-            return "ErodingFactor{" + "factor=" + factor +
-                    ", idleHighWaterMark=" + idleHighWaterMark + '}';
+            final StringBuilder sb = new StringBuilder();
+            sb.append("SynchronizedObjectPool");
+            sb.append("{pool=").append(pool);
+            sb.append('}');
+            return sb.toString();
         }
     }
 
     /**
-     * Decorates an object pool, adding "eroding" behavior. Based on the
-     * configured {@link #factor erosion factor}, objects returning to the pool
-     * may be invalidated instead of being added to idle capacity.
+     * A fully synchronized PooledObjectFactory that wraps a
+     * PooledObjectFactory and synchronizes access to the wrapped factory
+     * methods.
+     * <p>
+     * <b>Note:</b> This should not be used on pool implementations that 
already
+     * provide proper synchronization such as the pools provided in the Commons
+     * Pool library.
+     * </p>
      *
-     * @param <T> type of objects in the pool
+     * @param <T> pooled object factory type
      */
-    private static class ErodingObjectPool<T> implements ObjectPool<T> {
+    private static final class SynchronizedPooledObjectFactory<T> implements
+            PooledObjectFactory<T> {
 
-        /** Underlying object pool */
-        private final ObjectPool<T> pool;
+        /** Synchronization lock */
+        private final WriteLock writeLock = new 
ReentrantReadWriteLock().writeLock();
 
-        /** Erosion factor */
-        private final ErodingFactor factor;
+        /** Wrapped factory */
+        private final PooledObjectFactory<T> factory;
 
         /**
-         * Creates an ErodingObjectPool wrapping the given pool using the
-         * specified erosion factor.
+         * Creates a SynchronizedPoolableObjectFactory wrapping the given
+         * factory.
          *
-         * @param pool
-         *            underlying pool
-         * @param factor
-         *            erosion factor - determines the frequency of erosion
-         *            events
-         * @see #factor
+         * @param factory
+         *            underlying factory to wrap
+         * @throws IllegalArgumentException
+         *             if the factory is null
          */
-        public ErodingObjectPool(final ObjectPool<T> pool, final float factor) 
{
-            this.pool = pool;
-            this.factor = new ErodingFactor(factor);
+        SynchronizedPooledObjectFactory(final PooledObjectFactory<T> factory)
+                throws IllegalArgumentException {
+            if (factory == null) {
+                throw new IllegalArgumentException("factory must not be 
null.");
+            }
+            this.factory = factory;
         }
 
         /**
          * {@inheritDoc}
          */
         @Override
-        public T borrowObject() throws Exception, NoSuchElementException,
-                IllegalStateException {
-            return pool.borrowObject();
+        public void activateObject(final PooledObject<T> p) throws Exception {
+            writeLock.lock();
+            try {
+                factory.activateObject(p);
+            } finally {
+                writeLock.unlock();
+            }
         }
 
         /**
-         * Returns obj to the pool, unless erosion is triggered, in which case
-         * obj is invalidated. Erosion is triggered when there are idle
-         * instances in the pool and more than the {@link #factor erosion
-         * factor}-determined time has elapsed since the last returnObject
-         * activation.
-         *
-         * @param obj
-         *            object to return or invalidate
-         * @see #factor
+         * {@inheritDoc}
          */
         @Override
-        public void returnObject(final T obj) {
-            boolean discard = false;
-            final long nowMillis = System.currentTimeMillis();
-            synchronized (pool) {
-                if (factor.getNextShrink() < nowMillis) { // XXX: Pool 3: move 
test
-                                                    // out of sync block
-                    final int numIdle = pool.getNumIdle();
-                    if (numIdle > 0) {
-                        discard = true;
-                    }
-
-                    factor.update(nowMillis, numIdle);
-                }
-            }
+        public void destroyObject(final PooledObject<T> p) throws Exception {
+            writeLock.lock();
             try {
-                if (discard) {
-                    pool.invalidateObject(obj);
-                } else {
-                    pool.returnObject(obj);
-                }
-            } catch (final Exception e) {
-                // swallowed
+                factory.destroyObject(p);
+            } finally {
+                writeLock.unlock();
             }
         }
 
@@ -1509,11 +1254,12 @@ public final class PoolUtils {
          * {@inheritDoc}
          */
         @Override
-        public void invalidateObject(final T obj) {
+        public PooledObject<T> makeObject() throws Exception {
+            writeLock.lock();
             try {
-                pool.invalidateObject(obj);
-            } catch (final Exception e) {
-                // swallowed
+                return factory.makeObject();
+            } finally {
+                writeLock.unlock();
             }
         }
 
@@ -1521,326 +1267,580 @@ public final class PoolUtils {
          * {@inheritDoc}
          */
         @Override
-        public void addObject() throws Exception, IllegalStateException,
-                UnsupportedOperationException {
-            pool.addObject();
-        }
-
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        public int getNumIdle() {
-            return pool.getNumIdle();
-        }
-
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        public int getNumActive() {
-            return pool.getNumActive();
+        public void passivateObject(final PooledObject<T> p) throws Exception {
+            writeLock.lock();
+            try {
+                factory.passivateObject(p);
+            } finally {
+                writeLock.unlock();
+            }
         }
 
         /**
          * {@inheritDoc}
          */
         @Override
-        public void clear() throws Exception, UnsupportedOperationException {
-            pool.clear();
+        public String toString() {
+            final StringBuilder sb = new StringBuilder();
+            sb.append("SynchronizedPoolableObjectFactory");
+            sb.append("{factory=").append(factory);
+            sb.append('}');
+            return sb.toString();
         }
 
         /**
          * {@inheritDoc}
          */
         @Override
-        public void close() {
+        public boolean validateObject(final PooledObject<T> p) {
+            writeLock.lock();
             try {
-                pool.close();
-            } catch (final Exception e) {
-                // swallowed
+                return factory.validateObject(p);
+            } finally {
+                writeLock.unlock();
             }
         }
-
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        public String toString() {
-            return "ErodingObjectPool{" + "factor=" + factor + ", pool=" +
-                    pool + '}';
-        }
     }
 
     /**
-     * Decorates a keyed object pool, adding "eroding" behavior. Based on the
-     * configured erosion factor, objects returning to the pool
-     * may be invalidated instead of being added to idle capacity.
-     *
-     * @param <K> object pool key type
-     * @param <V> object pool value type
+     * Timer used to periodically check pools idle object count. Because a
+     * {@link Timer} creates a {@link Thread}, an IODH is used.
      */
-    private static class ErodingKeyedObjectPool<K, V> implements
-            KeyedObjectPool<K, V> {
+    static class TimerHolder {
+        static final Timer MIN_IDLE_TIMER = new Timer(true);
+    }
 
-        /** Underlying pool */
-        private final KeyedObjectPool<K, V> keyedPool;
+    private static final String MSG_FACTOR_NEGATIVE = "factor must be 
positive.";
 
-        /** Erosion factor */
-        private final ErodingFactor erodingFactor;
+    private static final String MSG_MIN_IDLE = "minIdle must be non-negative.";
 
-        /**
-         * Creates an ErodingObjectPool wrapping the given pool using the
-         * specified erosion factor.
-         *
-         * @param keyedPool
-         *            underlying pool
-         * @param factor
-         *            erosion factor - determines the frequency of erosion
-         *            events
-         * @see #erodingFactor
-         */
-        public ErodingKeyedObjectPool(final KeyedObjectPool<K, V> keyedPool,
-                final float factor) {
-            this(keyedPool, new ErodingFactor(factor));
-        }
+    static final String MSG_NULL_KEY = "key must not be null.";
 
-        /**
-         * Creates an ErodingObjectPool wrapping the given pool using the
-         * specified erosion factor.
-         *
-         * @param keyedPool
-         *            underlying pool - must not be null
-         * @param erodingFactor
-         *            erosion factor - determines the frequency of erosion
-         *            events
-         * @see #erodingFactor
-         */
-        protected ErodingKeyedObjectPool(final KeyedObjectPool<K, V> keyedPool,
-                final ErodingFactor erodingFactor) {
-            if (keyedPool == null) {
-                throw new IllegalArgumentException(
-                        MSG_NULL_KEYED_POOL);
-            }
-            this.keyedPool = keyedPool;
-            this.erodingFactor = erodingFactor;
-        }
+    private static final String MSG_NULL_KEYED_POOL = "keyedPool must not be 
null.";
 
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        public V borrowObject(final K key) throws Exception,
-                NoSuchElementException, IllegalStateException {
-            return keyedPool.borrowObject(key);
-        }
+    static final String MSG_NULL_KEYS = "keys must not be null.";
 
-        /**
-         * Returns obj to the pool, unless erosion is triggered, in which case
-         * obj is invalidated. Erosion is triggered when there are idle
-         * instances in the pool associated with the given key and more than 
the
-         * configured {@link #erodingFactor erosion factor} time has elapsed
-         * since the last returnObject activation.
-         *
-         * @param obj
-         *            object to return or invalidate
-         * @param key
-         *            key
-         * @see #erodingFactor
-         */
-        @Override
-        public void returnObject(final K key, final V obj) throws Exception {
-            boolean discard = false;
-            final long nowMillis = System.currentTimeMillis();
-            final ErodingFactor factor = getErodingFactor(key);
-            synchronized (keyedPool) {
-                if (factor.getNextShrink() < nowMillis) {
-                    final int numIdle = getNumIdle(key);
-                    if (numIdle > 0) {
-                        discard = true;
-                    }
+    private static final String MSG_NULL_POOL = "pool must not be null.";
 
-                    factor.update(nowMillis, numIdle);
-                }
-            }
-            try {
-                if (discard) {
-                    keyedPool.invalidateObject(key, obj);
-                } else {
-                    keyedPool.returnObject(key, obj);
-                }
-            } catch (final Exception e) {
-                // swallowed
-            }
+    /**
+     * Periodically check the idle object count for each key in the
+     * {@code Collection keys} in the keyedPool. At most one idle object will 
be
+     * added per period.
+     *
+     * @param keyedPool
+     *            the keyedPool to check periodically.
+     * @param keys
+     *            a collection of keys to check the idle object count.
+     * @param minIdle
+     *            if the {@link KeyedObjectPool#getNumIdle(Object)} is less 
than
+     *            this then add an idle object.
+     * @param period
+     *            the frequency to check the number of idle objects in a
+     *            keyedPool, see {@link Timer#schedule(TimerTask, long, long)}.
+     * @param <K> the type of the pool key
+     * @param <V> the type of pool entries
+     * @return a {@link Map} of key and {@link TimerTask} pairs that will
+     *         periodically check the pools idle object count.
+     * @throws IllegalArgumentException
+     *             when {@code keyedPool}, {@code keys}, or any of the values 
in
+     *             the collection is {@code null} or when {@code minIdle} is
+     *             negative or when {@code period} isn't valid for
+     *             {@link Timer#schedule(TimerTask, long, long)}.
+     * @see #checkMinIdle(KeyedObjectPool, Object, int, long)
+     */
+    public static <K, V> Map<K, TimerTask> checkMinIdle(
+            final KeyedObjectPool<K, V> keyedPool, final Collection<K> keys,
+            final int minIdle, final long period)
+            throws IllegalArgumentException {
+        if (keys == null) {
+            throw new IllegalArgumentException(MSG_NULL_KEYS);
         }
-
-        /**
-         * Returns the eroding factor for the given key
-         *
-         * @param key
-         *            key
-         * @return eroding factor for the given keyed pool
-         */
-        protected ErodingFactor getErodingFactor(final K key) {
-            return erodingFactor;
+        final Map<K, TimerTask> tasks = new HashMap<>(keys.size());
+        final Iterator<K> iter = keys.iterator();
+        while (iter.hasNext()) {
+            final K key = iter.next();
+            final TimerTask task = checkMinIdle(keyedPool, key, minIdle, 
period);
+            tasks.put(key, task);
         }
+        return tasks;
+    }
 
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        public void invalidateObject(final K key, final V obj) {
-            try {
-                keyedPool.invalidateObject(key, obj);
-            } catch (final Exception e) {
-                // swallowed
-            }
+    /**
+     * Periodically check the idle object count for the key in the keyedPool. 
At
+     * most one idle object will be added per period. If there is an exception
+     * when calling {@link KeyedObjectPool#addObject(Object)} then no more
+     * checks for that key will be performed.
+     *
+     * @param keyedPool
+     *            the keyedPool to check periodically.
+     * @param key
+     *            the key to check the idle count of.
+     * @param minIdle
+     *            if the {@link KeyedObjectPool#getNumIdle(Object)} is less 
than
+     *            this then add an idle object.
+     * @param period
+     *            the frequency to check the number of idle objects in a
+     *            keyedPool, see {@link Timer#schedule(TimerTask, long, long)}.
+     * @param <K> the type of the pool key
+     * @param <V> the type of pool entries
+     * @return the {@link TimerTask} that will periodically check the pools 
idle
+     *         object count.
+     * @throws IllegalArgumentException
+     *             when {@code keyedPool}, {@code key} is {@code null} or
+     *             when {@code minIdle} is negative or when {@code period} 
isn't
+     *             valid for {@link Timer#schedule(TimerTask, long, long)}.
+     */
+    public static <K, V> TimerTask checkMinIdle(
+            final KeyedObjectPool<K, V> keyedPool, final K key,
+            final int minIdle, final long period)
+            throws IllegalArgumentException {
+        if (keyedPool == null) {
+            throw new IllegalArgumentException(MSG_NULL_KEYED_POOL);
         }
-
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        public void addObject(final K key) throws Exception,
-                IllegalStateException, UnsupportedOperationException {
-            keyedPool.addObject(key);
+        if (key == null) {
+            throw new IllegalArgumentException(MSG_NULL_KEY);
         }
-
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        public int getNumIdle() {
-            return keyedPool.getNumIdle();
+        if (minIdle < 0) {
+            throw new IllegalArgumentException(MSG_MIN_IDLE);
         }
+        final TimerTask task = new KeyedObjectPoolMinIdleTimerTask<>(
+                keyedPool, key, minIdle);
+        getMinIdleTimer().schedule(task, 0L, period);
+        return task;
+    }
 
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        public int getNumIdle(final K key) {
-            return keyedPool.getNumIdle(key);
+    /**
+     * Periodically check the idle object count for the pool. At most one idle
+     * object will be added per period. If there is an exception when calling
+     * {@link ObjectPool#addObject()} then no more checks will be performed.
+     *
+     * @param pool
+     *            the pool to check periodically.
+     * @param minIdle
+     *            if the {@link ObjectPool#getNumIdle()} is less than this then
+     *            add an idle object.
+     * @param period
+     *            the frequency to check the number of idle objects in a pool,
+     *            see {@link Timer#schedule(TimerTask, long, long)}.
+     * @param <T> the type of objects in the pool
+     * @return the {@link TimerTask} that will periodically check the pools 
idle
+     *         object count.
+     * @throws IllegalArgumentException
+     *             when {@code pool} is {@code null} or when {@code minIdle} is
+     *             negative or when {@code period} isn't valid for
+     *             {@link Timer#schedule(TimerTask, long, long)}
+     */
+    public static <T> TimerTask checkMinIdle(final ObjectPool<T> pool,
+            final int minIdle, final long period)
+            throws IllegalArgumentException {
+        if (pool == null) {
+            throw new IllegalArgumentException(MSG_NULL_KEYED_POOL);
+        }
+        if (minIdle < 0) {
+            throw new IllegalArgumentException(MSG_MIN_IDLE);
         }
+        final TimerTask task = new ObjectPoolMinIdleTimerTask<>(pool, minIdle);
+        getMinIdleTimer().schedule(task, 0L, period);
+        return task;
+    }
 
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        public int getNumActive() {
-            return keyedPool.getNumActive();
+    /**
+     * Should the supplied Throwable be re-thrown (eg if it is an instance of
+     * one of the Throwables that should never be swallowed). Used by the pool
+     * error handling for operations that throw exceptions that normally need 
to
+     * be ignored.
+     *
+     * @param t
+     *            The Throwable to check
+     * @throws ThreadDeath
+     *             if that is passed in
+     * @throws VirtualMachineError
+     *             if that is passed in
+     */
+    public static void checkRethrow(final Throwable t) {
+        if (t instanceof ThreadDeath) {
+            throw (ThreadDeath) t;
+        }
+        if (t instanceof VirtualMachineError) {
+            throw (VirtualMachineError) t;
+        }
+        // All other instances of Throwable will be silently swallowed
+    }
+
+    /**
+     * Returns a pool that adaptively decreases its size when idle objects are
+     * no longer needed. This is intended as an always thread-safe alternative
+     * to using an idle object evictor provided by many pool implementations.
+     * This is also an effective way to shrink FIFO ordered pools that
+     * experience load spikes.
+     *
+     * @param keyedPool
+     *            the KeyedObjectPool to be decorated so it shrinks its idle
+     *            count when possible.
+     * @param <K> the type of the pool key
+     * @param <V> the type of pool entries
+     * @throws IllegalArgumentException
+     *             when {@code keyedPool} is {@code null}.
+     * @return a pool that adaptively decreases its size when idle objects are
+     *         no longer needed.
+     * @see #erodingPool(KeyedObjectPool, float)
+     * @see #erodingPool(KeyedObjectPool, float, boolean)
+     */
+    public static <K, V> KeyedObjectPool<K, V> erodingPool(
+            final KeyedObjectPool<K, V> keyedPool) {
+        return erodingPool(keyedPool, 1f);
+    }
+
+    /**
+     * Returns a pool that adaptively decreases its size when idle objects are
+     * no longer needed. This is intended as an always thread-safe alternative
+     * to using an idle object evictor provided by many pool implementations.
+     * This is also an effective way to shrink FIFO ordered pools that
+     * experience load spikes.
+     * <p>
+     * The factor parameter provides a mechanism to tweak the rate at which the
+     * pool tries to shrink its size. Values between 0 and 1 cause the pool to
+     * try to shrink its size more often. Values greater than 1 cause the pool
+     * to less frequently try to shrink its size.
+     * </p>
+     *
+     * @param keyedPool
+     *            the KeyedObjectPool to be decorated so it shrinks its idle
+     *            count when possible.
+     * @param factor
+     *            a positive value to scale the rate at which the pool tries to
+     *            reduce its size. If 0 &lt; factor &lt; 1 then the pool
+     *            shrinks more aggressively. If 1 &lt; factor then the pool
+     *            shrinks less aggressively.
+     * @param <K> the type of the pool key
+     * @param <V> the type of pool entries
+     * @throws IllegalArgumentException
+     *             when {@code keyedPool} is {@code null} or when {@code 
factor}
+     *             is not positive.
+     * @return a pool that adaptively decreases its size when idle objects are
+     *         no longer needed.
+     * @see #erodingPool(KeyedObjectPool, float, boolean)
+     */
+    public static <K, V> KeyedObjectPool<K, V> erodingPool(
+            final KeyedObjectPool<K, V> keyedPool, final float factor) {
+        return erodingPool(keyedPool, factor, false);
+    }
+
+    /**
+     * Returns a pool that adaptively decreases its size when idle objects are
+     * no longer needed. This is intended as an always thread-safe alternative
+     * to using an idle object evictor provided by many pool implementations.
+     * This is also an effective way to shrink FIFO ordered pools that
+     * experience load spikes.
+     * <p>
+     * The factor parameter provides a mechanism to tweak the rate at which the
+     * pool tries to shrink its size. Values between 0 and 1 cause the pool to
+     * try to shrink its size more often. Values greater than 1 cause the pool
+     * to less frequently try to shrink its size.
+     * </p>
+     * <p>
+     * The perKey parameter determines if the pool shrinks on a whole pool 
basis
+     * or a per key basis. When perKey is false, the keys do not have an effect
+     * on the rate at which the pool tries to shrink its size. When perKey is
+     * true, each key is shrunk independently.
+     * </p>
+     *
+     * @param keyedPool
+     *            the KeyedObjectPool to be decorated so it shrinks its idle
+     *            count when possible.
+     * @param factor
+     *            a positive value to scale the rate at which the pool tries to
+     *            reduce its size. If 0 &lt; factor &lt; 1 then the pool
+     *            shrinks more aggressively. If 1 &lt; factor then the pool
+     *            shrinks less aggressively.
+     * @param perKey
+     *            when true, each key is treated independently.
+     * @param <K> the type of the pool key
+     * @param <V> the type of pool entries
+     * @throws IllegalArgumentException
+     *             when {@code keyedPool} is {@code null} or when {@code 
factor}
+     *             is not positive.
+     * @return a pool that adaptively decreases its size when idle objects are
+     *         no longer needed.
+     * @see #erodingPool(KeyedObjectPool)
+     * @see #erodingPool(KeyedObjectPool, float)
+     */
+    public static <K, V> KeyedObjectPool<K, V> erodingPool(
+            final KeyedObjectPool<K, V> keyedPool, final float factor,
+            final boolean perKey) {
+        if (keyedPool == null) {
+            throw new IllegalArgumentException(MSG_NULL_KEYED_POOL);
+        }
+        if (factor <= 0f) {
+            throw new IllegalArgumentException(MSG_FACTOR_NEGATIVE);
+        }
+        if (perKey) {
+            return new ErodingPerKeyKeyedObjectPool<>(keyedPool, factor);
+        }
+        return new ErodingKeyedObjectPool<>(keyedPool, factor);
+    }
+
+    /**
+     * Returns a pool that adaptively decreases its size when idle objects are
+     * no longer needed. This is intended as an always thread-safe alternative
+     * to using an idle object evictor provided by many pool implementations.
+     * This is also an effective way to shrink FIFO ordered pools that
+     * experience load spikes.
+     *
+     * @param pool
+     *            the ObjectPool to be decorated so it shrinks its idle count
+     *            when possible.
+     * @param <T> the type of objects in the pool
+     * @throws IllegalArgumentException
+     *             when {@code pool} is {@code null}.
+     * @return a pool that adaptively decreases its size when idle objects are
+     *         no longer needed.
+     * @see #erodingPool(ObjectPool, float)
+     */
+    public static <T> ObjectPool<T> erodingPool(final ObjectPool<T> pool) {
+        return erodingPool(pool, 1f);
+    }
+
+    /**
+     * Returns a pool that adaptively decreases its size when idle objects are
+     * no longer needed. This is intended as an always thread-safe alternative
+     * to using an idle object evictor provided by many pool implementations.
+     * This is also an effective way to shrink FIFO ordered pools that
+     * experience load spikes.
+     * <p>
+     * The factor parameter provides a mechanism to tweak the rate at which the
+     * pool tries to shrink its size. Values between 0 and 1 cause the pool to
+     * try to shrink its size more often. Values greater than 1 cause the pool
+     * to less frequently try to shrink its size.
+     * </p>
+     *
+     * @param pool
+     *            the ObjectPool to be decorated so it shrinks its idle count
+     *            when possible.
+     * @param factor
+     *            a positive value to scale the rate at which the pool tries to
+     *            reduce its size. If 0 &lt; factor &lt; 1 then the pool
+     *            shrinks more aggressively. If 1 &lt; factor then the pool
+     *            shrinks less aggressively.
+     * @param <T> the type of objects in the pool
+     * @throws IllegalArgumentException
+     *             when {@code pool} is {@code null} or when {@code factor} is
+     *             not positive.
+     * @return a pool that adaptively decreases its size when idle objects are
+     *         no longer needed.
+     * @see #erodingPool(ObjectPool)
+     */
+    public static <T> ObjectPool<T> erodingPool(final ObjectPool<T> pool,
+            final float factor) {
+        if (pool == null) {
+            throw new IllegalArgumentException(MSG_NULL_POOL);
+        }
+        if (factor <= 0f) {
+            throw new IllegalArgumentException(MSG_FACTOR_NEGATIVE);
+        }
+        return new ErodingObjectPool<>(pool, factor);
+    }
+
+    /**
+     * Gets the {@code Timer} for checking keyedPool's idle count.
+     *
+     * @return the {@link Timer} for checking keyedPool's idle count.
+     */
+    private static Timer getMinIdleTimer() {
+        return TimerHolder.MIN_IDLE_TIMER;
+    }
+
+    /**
+     * Calls {@link KeyedObjectPool#addObject(Object)} on {@code keyedPool} 
with
+     * each key in {@code keys} for {@code count} number of times. This has
+     * the same effect as calling {@link #prefill(KeyedObjectPool, Object, 
int)}
+     * for each key in the {@code keys} collection.
+     *
+     * @param keyedPool
+     *            the keyedPool to prefill.
+     * @param keys
+     *            {@link Collection} of keys to add objects for.
+     * @param count
+     *            the number of idle objects to add for each {@code key}.
+     * @param <K> the type of the pool key
+     * @param <V> the type of pool entries
+     * @throws Exception
+     *             when {@link KeyedObjectPool#addObject(Object)} fails.
+     * @throws IllegalArgumentException
+     *             when {@code keyedPool}, {@code keys}, or any value in
+     *             {@code keys} is {@code null}.
+     * @see #prefill(KeyedObjectPool, Object, int)
+     * @deprecated Use {@link KeyedObjectPool#addObjects(Collection, int)}.
+     */
+    @Deprecated
+    public static <K, V> void prefill(final KeyedObjectPool<K, V> keyedPool,
+            final Collection<K> keys, final int count) throws Exception,
+            IllegalArgumentException {
+        if (keys == null) {
+            throw new IllegalArgumentException(MSG_NULL_KEYS);
         }
+        keyedPool.addObjects(keys, count);
+    }
 
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        public int getNumActive(final K key) {
-            return keyedPool.getNumActive(key);
+    /**
+     * Calls {@link KeyedObjectPool#addObject(Object)} on {@code keyedPool} 
with
+     * {@code key} {@code count} number of times.
+     *
+     * @param keyedPool
+     *            the keyedPool to prefill.
+     * @param key
+     *            the key to add objects for.
+     * @param count
+     *            the number of idle objects to add for {@code key}.
+     * @param <K> the type of the pool key
+     * @param <V> the type of pool entries
+     * @throws Exception
+     *             when {@link KeyedObjectPool#addObject(Object)} fails.
+     * @throws IllegalArgumentException
+     *             when {@code keyedPool} or {@code key} is {@code null}.
+     * @deprecated Use {@link KeyedObjectPool#addObjects(Object, int)}.
+     */
+    @Deprecated
+    public static <K, V> void prefill(final KeyedObjectPool<K, V> keyedPool,
+            final K key, final int count) throws Exception,
+            IllegalArgumentException {
+        if (keyedPool == null) {
+            throw new IllegalArgumentException(MSG_NULL_KEYED_POOL);
         }
+        keyedPool.addObjects(key, count);
+    }
 
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        public void clear() throws Exception, UnsupportedOperationException {
-            keyedPool.clear();
+    /**
+     * Calls {@link ObjectPool#addObject()} on {@code pool} {@code count} 
number
+     * of times.
+     *
+     * @param pool
+     *            the pool to prefill.
+     * @param count
+     *            the number of idle objects to add.
+     * @param <T> the type of objects in the pool
+     * @throws Exception
+     *             when {@link ObjectPool#addObject()} fails.
+     * @throws IllegalArgumentException
+     *             when {@code pool} is {@code null}.
+     * @deprecated Use {@link ObjectPool#addObjects(int)}.
+     */
+    @Deprecated
+    public static <T> void prefill(final ObjectPool<T> pool, final int count)
+            throws Exception, IllegalArgumentException {
+        if (pool == null) {
+            throw new IllegalArgumentException(MSG_NULL_POOL);
         }
+        pool.addObjects(count);
+    }
 
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        public void clear(final K key) throws Exception,
-                UnsupportedOperationException {
-            keyedPool.clear(key);
-        }
+    /**
+     * Returns a synchronized (thread-safe) KeyedPooledObjectFactory backed by
+     * the specified KeyedPoolableObjectFactory.
+     *
+     * @param keyedFactory
+     *            the KeyedPooledObjectFactory to be "wrapped" in a
+     *            synchronized KeyedPooledObjectFactory.
+     * @param <K> the type of the pool key
+     * @param <V> the type of pool entries
+     * @return a synchronized view of the specified KeyedPooledObjectFactory.
+     */
+    public static <K, V> KeyedPooledObjectFactory<K, V> 
synchronizedKeyedPooledFactory(
+            final KeyedPooledObjectFactory<K, V> keyedFactory) {
+        return new SynchronizedKeyedPooledObjectFactory<>(keyedFactory);
+    }
 
-        /**
-         * {@inheritDoc}
+    /**
+     * Returns a synchronized (thread-safe) KeyedObjectPool backed by the
+     * specified KeyedObjectPool.
+     * <p>
+     * <b>Note:</b> This should not be used on pool implementations that 
already
+     * provide proper synchronization such as the pools provided in the Commons
+     * Pool library. Wrapping a pool that {@link #wait() waits} for poolable
+     * objects to be returned before allowing another one to be borrowed with
+     * another layer of synchronization will cause liveliness issues or a
+     * deadlock.
+     * </p>
+     *
+     * @param keyedPool
+     *            the KeyedObjectPool to be "wrapped" in a synchronized
+     *            KeyedObjectPool.
+     * @param <K> the type of the pool key
+     * @param <V> the type of pool entries
+     * @return a synchronized view of the specified KeyedObjectPool.
+     */
+    public static <K, V> KeyedObjectPool<K, V> synchronizedPool(
+            final KeyedObjectPool<K, V> keyedPool) {
+        /*
+         * assert !(keyedPool instanceof GenericKeyedObjectPool) :
+         * "GenericKeyedObjectPool is already thread-safe"; assert !(keyedPool
+         * instanceof StackKeyedObjectPool) :
+         * "StackKeyedObjectPool is already thread-safe"; assert
+         * !"org.apache.commons.pool.composite.CompositeKeyedObjectPool"
+         * .equals(keyedPool.getClass().getName()) :
+         * "CompositeKeyedObjectPools are already thread-safe";
          */
-        @Override
-        public void close() {
-            try {
-                keyedPool.close();
-            } catch (final Exception e) {
-                // swallowed
-            }
-        }
+        return new SynchronizedKeyedObjectPool<>(keyedPool);
+    }
 
-        /**
-         * Returns the underlying pool
-         *
-         * @return the keyed pool that this ErodingKeyedObjectPool wraps
-         */
-        protected KeyedObjectPool<K, V> getKeyedPool() {
-            return keyedPool;
+    /**
+     * Returns a synchronized (thread-safe) ObjectPool backed by the specified
+     * ObjectPool.
+     * <p>
+     * <b>Note:</b> This should not be used on pool implementations that 
already
+     * provide proper synchronization such as the pools provided in the Commons
+     * Pool library. Wrapping a pool that {@link #wait() waits} for poolable
+     * objects to be returned before allowing another one to be borrowed with
+     * another layer of synchronization will cause liveliness issues or a
+     * deadlock.
+     * </p>
+     *
+     * @param pool
+     *            the ObjectPool to be "wrapped" in a synchronized ObjectPool.
+     * @param <T> the type of objects in the pool
+     * @throws IllegalArgumentException
+     *             when {@code pool} is {@code null}.
+     * @return a synchronized view of the specified ObjectPool.
+     */
+    public static <T> ObjectPool<T> synchronizedPool(final ObjectPool<T> pool) 
{
+        if (pool == null) {
+            throw new IllegalArgumentException(MSG_NULL_POOL);
         }
-
-        /**
-         * {@inheritDoc}
+        /*
+         * assert !(pool instanceof GenericObjectPool) :
+         * "GenericObjectPool is already thread-safe"; assert !(pool instanceof
+         * SoftReferenceObjectPool) :
+         * "SoftReferenceObjectPool is already thread-safe"; assert !(pool
+         * instanceof StackObjectPool) :
+         * "StackObjectPool is already thread-safe"; assert
+         * !"org.apache.commons.pool.composite.CompositeObjectPool"
+         * .equals(pool.getClass().getName()) :
+         * "CompositeObjectPools are already thread-safe";
          */
-        @Override
-        public String toString() {
-            return "ErodingKeyedObjectPool{" + "factor=" +
-                    erodingFactor + ", keyedPool=" + keyedPool + '}';
-        }
+        return new SynchronizedObjectPool<>(pool);
     }
 
     /**
-     * Extends ErodingKeyedObjectPool to allow erosion to take place on a
-     * per-key basis. Timing of erosion events is tracked separately for
-     * separate keyed pools.
+     * Returns a synchronized (thread-safe) PooledObjectFactory backed by the
+     * specified PooledObjectFactory.
      *
-     * @param <K> object pool key type
-     * @param <V> object pool value type
+     * @param factory
+     *            the PooledObjectFactory to be "wrapped" in a synchronized
+     *            PooledObjectFactory.
+     * @param <T> the type of objects in the pool
+     * @return a synchronized view of the specified PooledObjectFactory.
      */
-    private static final class ErodingPerKeyKeyedObjectPool<K, V> extends
-            ErodingKeyedObjectPool<K, V> {
-
-        /** Erosion factor - same for all pools */
-        private final float factor;
-
-        /** Map of ErodingFactor instances keyed on pool keys */
-        private final Map<K, ErodingFactor> factors = 
Collections.synchronizedMap(new HashMap<K, ErodingFactor>());
-
-        /**
-         * Creates a new ErordingPerKeyKeyedObjectPool decorating the given 
keyed
-         * pool with the specified erosion factor.
-         *
-         * @param keyedPool
-         *            underlying keyed pool
-         * @param factor
-         *            erosion factor
-         */
-        public ErodingPerKeyKeyedObjectPool(
-                final KeyedObjectPool<K, V> keyedPool, final float factor) {
-            super(keyedPool, null);
-            this.factor = factor;
-        }
-
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        protected ErodingFactor getErodingFactor(final K key) {
-            ErodingFactor eFactor = factors.get(key);
-            // this may result in two ErodingFactors being created for a key
-            // since they are small and cheap this is okay.
-            if (eFactor == null) {
-                eFactor = new ErodingFactor(this.factor);
-                factors.put(key, eFactor);
-            }
-            return eFactor;
-        }
+    public static <T> PooledObjectFactory<T> synchronizedPooledFactory(
+            final PooledObjectFactory<T> factory) {
+        return new SynchronizedPooledObjectFactory<>(factory);
+    }
 
-        /**
-         * {@inheritDoc}
-         */
-        @SuppressWarnings("resource") // getKeyedPool(): ivar access
-        @Override
-        public String toString() {
-            return "ErodingPerKeyKeyedObjectPool{" + "factor=" + factor +
-                    ", keyedPool=" + getKeyedPool() + '}';
-        }
+    /**
+     * PoolUtils instances should NOT be constructed in standard programming.
+     * Instead, the class should be used procedurally: PoolUtils.adapt(aPool);.
+     * This constructor is public to permit tools that require a JavaBean
+     * instance to operate.
+     */
+    public PoolUtils() {
     }
 }
diff --git a/src/main/java/org/apache/commons/pool2/PooledObject.java 
b/src/main/java/org/apache/commons/pool2/PooledObject.java
index 3ee8219..1e5fc06 100644
--- a/src/main/java/org/apache/commons/pool2/PooledObject.java
+++ b/src/main/java/org/apache/commons/pool2/PooledObject.java
@@ -33,20 +33,46 @@ import java.util.Deque;
 public interface PooledObject<T> extends Comparable<PooledObject<T>> {
 
     /**
-     * Obtains the underlying object that is wrapped by this instance of
-     * {@link PooledObject}.
+     * Allocates the object.
      *
-     * @return The wrapped object
+     * @return {@code true} if the original state was {@link 
PooledObjectState#IDLE IDLE}
      */
-    T getObject();
+    boolean allocate();
 
     /**
-     * Obtains the time (using the same basis as
-     * {@link System#currentTimeMillis()}) that this object was created.
+     * Orders instances based on idle time - i.e. the length of time since the
+     * instance was returned to the pool. Used by the GKOP idle object evictor.
+     *<p>
+     * Note: This class has a natural ordering that is inconsistent with
+     *       equals if distinct objects have the same identity hash code.
+     * </p>
+     * <p>
+     * {@inheritDoc}
+     * </p>
+     */
+    @Override
+    int compareTo(PooledObject<T> other);
+
+    /**
+     * Deallocates the object and sets it {@link PooledObjectState#IDLE IDLE}
+     * if it is currently {@link PooledObjectState#ALLOCATED ALLOCATED}.
      *
-     * @return The creation time for the wrapped object
+     * @return {@code true} if the state was {@link 
PooledObjectState#ALLOCATED ALLOCATED}
      */
-    long getCreateTime();
+    boolean deallocate();
+
+    /**
+     * Called to inform the object that the eviction test has ended.
+     *
+     * @param idleQueue The queue of idle objects to which the object should be
+     *                  returned
+     *
+     * @return  Currently not used
+     */
+    boolean endEvictionTest(Deque<PooledObject<T>> idleQueue);
+
+    @Override
+    boolean equals(Object obj);
 
     /**
      * Obtains the time in milliseconds that this object last spent in the
@@ -68,6 +94,14 @@ public interface PooledObject<T> extends 
Comparable<PooledObject<T>> {
     }
 
     /**
+     * Obtains the time (using the same basis as
+     * {@link System#currentTimeMillis()}) that this object was created.
+     *
+     * @return The creation time for the wrapped object
+     */
+    long getCreateTime();
+
+    /**
      * Obtains the time in milliseconds that this object last spend in the
      * idle state (it may still be idle in which case subsequent calls will
      * return an increased value).
@@ -102,73 +136,45 @@ public interface PooledObject<T> extends 
Comparable<PooledObject<T>> {
     long getLastUsedTime();
 
     /**
-     * Orders instances based on idle time - i.e. the length of time since the
-     * instance was returned to the pool. Used by the GKOP idle object evictor.
-     *<p>
-     * Note: This class has a natural ordering that is inconsistent with
-     *       equals if distinct objects have the same identity hash code.
-     * </p>
-     * <p>
-     * {@inheritDoc}
-     * </p>
+     * Obtains the underlying object that is wrapped by this instance of
+     * {@link PooledObject}.
+     *
+     * @return The wrapped object
      */
-    @Override
-    int compareTo(PooledObject<T> other);
-
-    @Override
-    boolean equals(Object obj);
-
-    @Override
-    int hashCode();
+    T getObject();
 
     /**
-     * Provides a String form of the wrapper for debug purposes. The format is
-     * not fixed and may change at any time.
-     * <p>
-     * {@inheritDoc}
+     * Returns the state of this object.
+     * @return state
      */
+    PooledObjectState getState();
+
     @Override
-    String toString();
+    int hashCode();
 
     /**
-     * Attempts to place the pooled object in the
-     * {@link PooledObjectState#EVICTION} state.
-     *
-     * @return {@code true} if the object was placed in the
-     *         {@link PooledObjectState#EVICTION} state otherwise
-     *         {@code false}
+     * Sets the state to {@link PooledObjectState#INVALID INVALID}
      */
-    boolean startEvictionTest();
+    void invalidate();
 
     /**
-     * Called to inform the object that the eviction test has ended.
-     *
-     * @param idleQueue The queue of idle objects to which the object should be
-     *                  returned
-     *
-     * @return  Currently not used
+     * Marks the pooled object as abandoned.
      */
-    boolean endEvictionTest(Deque<PooledObject<T>> idleQueue);
+    void markAbandoned();
 
     /**
-     * Allocates the object.
-     *
-     * @return {@code true} if the original state was {@link 
PooledObjectState#IDLE IDLE}
+     * Marks the object as returning to the pool.
      */
-    boolean allocate();
+    void markReturning();
 
     /**
-     * Deallocates the object and sets it {@link PooledObjectState#IDLE IDLE}
-     * if it is currently {@link PooledObjectState#ALLOCATED ALLOCATED}.
+     * Prints the stack trace of the code that borrowed this pooled object and
+     * the stack trace of the last code to use this object (if available) to
+     * the supplied writer.
      *
-     * @return {@code true} if the state was {@link 
PooledObjectState#ALLOCATED ALLOCATED}
-     */
-    boolean deallocate();
-
-    /**
-     * Sets the state to {@link PooledObjectState#INVALID INVALID}
+     * @param   writer  The destination for the debug output
      */
-    void invalidate();
+    void printStackTrace(PrintWriter writer);
 
     /**
      * Is abandoned object tracking being used? If this is true the
@@ -193,33 +199,27 @@ public interface PooledObject<T> extends 
Comparable<PooledObject<T>> {
     }
 
     /**
-     * Record the current stack trace as the last time the object was used.
-     */
-    void use();
-
-    /**
-     * Prints the stack trace of the code that borrowed this pooled object and
-     * the stack trace of the last code to use this object (if available) to
-     * the supplied writer.
+     * Attempts to place the pooled object in the
+     * {@link PooledObjectState#EVICTION} state.
      *
-     * @param   writer  The destination for the debug output
-     */
-    void printStackTrace(PrintWriter writer);
-
-    /**
-     * Returns the state of this object.
-     * @return state
+     * @return {@code true} if the object was placed in the
+     *         {@link PooledObjectState#EVICTION} state otherwise
+     *         {@code false}
      */
-    PooledObjectState getState();
+    boolean startEvictionTest();
 
     /**
-     * Marks the pooled object as abandoned.
+     * Provides a String form of the wrapper for debug purposes. The format is
+     * not fixed and may change at any time.
+     * <p>
+     * {@inheritDoc}
      */
-    void markAbandoned();
+    @Override
+    String toString();
 
     /**
-     * Marks the object as returning to the pool.
+     * Record the current stack trace as the last time the object was used.
      */
-    void markReturning();
+    void use();
 
 }
diff --git a/src/main/java/org/apache/commons/pool2/PooledObjectFactory.java 
b/src/main/java/org/apache/commons/pool2/PooledObjectFactory.java
index 08adf18..009f341 100644
--- a/src/main/java/org/apache/commons/pool2/PooledObjectFactory.java
+++ b/src/main/java/org/apache/commons/pool2/PooledObjectFactory.java
@@ -73,15 +73,16 @@ package org.apache.commons.pool2;
 public interface PooledObjectFactory<T> {
 
   /**
-   * Creates an instance that can be served by the pool and wrap it in a
-   * {@link PooledObject} to be managed by the pool.
+   * Reinitializes an instance to be returned by the pool.
    *
-   * @return a {@code PooledObject} wrapping an instance that can be served by 
the pool
+   * @param p a {@code PooledObject} wrapping the instance to be activated
    *
-   * @throws Exception if there is a problem creating a new instance,
-   *    this will be propagated to the code requesting an object.
+   * @throws Exception if there is a problem activating {@code obj},
+   *    this exception may be swallowed by the pool.
+   *
+   * @see #destroyObject
    */
-  PooledObject<T> makeObject() throws Exception;
+  void activateObject(PooledObject<T> p) throws Exception;
 
   /**
    * Destroys an instance no longer needed by the pool, using the default 
(NORMAL)
@@ -127,36 +128,35 @@ public interface PooledObjectFactory<T> {
   }
 
   /**
-   * Ensures that the instance is safe to be returned by the pool.
+   * Creates an instance that can be served by the pool and wrap it in a
+   * {@link PooledObject} to be managed by the pool.
    *
-   * @param p a {@code PooledObject} wrapping the instance to be validated
+   * @return a {@code PooledObject} wrapping an instance that can be served by 
the pool
    *
-   * @return {@code false} if {@code obj} is not valid and should
-   *         be dropped from the pool, {@code true} otherwise.
+   * @throws Exception if there is a problem creating a new instance,
+   *    this will be propagated to the code requesting an object.
    */
-  boolean validateObject(PooledObject<T> p);
+  PooledObject<T> makeObject() throws Exception;
 
   /**
-   * Reinitializes an instance to be returned by the pool.
+   * Uninitializes an instance to be returned to the idle object pool.
    *
-   * @param p a {@code PooledObject} wrapping the instance to be activated
+   * @param p a {@code PooledObject} wrapping the instance to be passivated
    *
-   * @throws Exception if there is a problem activating {@code obj},
+   * @throws Exception if there is a problem passivating {@code obj},
    *    this exception may be swallowed by the pool.
    *
    * @see #destroyObject
    */
-  void activateObject(PooledObject<T> p) throws Exception;
+  void passivateObject(PooledObject<T> p) throws Exception;
 
   /**
-   * Uninitializes an instance to be returned to the idle object pool.
-   *
-   * @param p a {@code PooledObject} wrapping the instance to be passivated
+   * Ensures that the instance is safe to be returned by the pool.
    *
-   * @throws Exception if there is a problem passivating {@code obj},
-   *    this exception may be swallowed by the pool.
+   * @param p a {@code PooledObject} wrapping the instance to be validated
    *
-   * @see #destroyObject
+   * @return {@code false} if {@code obj} is not valid and should
+   *         be dropped from the pool, {@code true} otherwise.
    */
-  void passivateObject(PooledObject<T> p) throws Exception;
+  boolean validateObject(PooledObject<T> p);
 }

Reply via email to