You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by gg...@apache.org on 2022/07/03 00:09:08 UTC

[commons-pool] branch master updated (fa324c76 -> 4c86763f)

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

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


    from fa324c76 Suppress PMD EmptyCatchBlock from ruleset Error Prone 429
     new 09227e8f PMD: Avoid empty if statements
     new 4163d8a6 Clean ups.
     new eeaddae4 Bump pmd from 6.44.0 to 6.47.0
     new 4c86763f Add PMD check to default Maven goal

The 4 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 pom.xml                                            |    4 +-
 src/changes/changes.xml                            |    3 +
 .../java/org/apache/commons/pool2/PoolUtils.java   |    2 +-
 .../commons/pool2/impl/DefaultEvictionPolicy.java  |  108 +-
 .../commons/pool2/impl/DefaultPooledObject.java    |  686 ++-
 .../apache/commons/pool2/impl/EvictionTimer.java   |    4 +-
 .../commons/pool2/impl/GenericKeyedObjectPool.java |   21 +-
 .../commons/pool2/impl/GenericObjectPool.java      |   27 +-
 .../pool2/impl/SoftReferenceObjectPool.java        |    3 +-
 .../apache/commons/pool2/TestBaseObjectPool.java   |  562 +-
 .../apache/commons/pool2/TestKeyedObjectPool.java  | 1540 ++---
 .../org/apache/commons/pool2/WaiterFactory.java    |  434 +-
 .../pool2/impl/TestGenericKeyedObjectPool.java     |   92 +-
 .../commons/pool2/impl/TestGenericObjectPool.java  | 6022 ++++++++++----------
 14 files changed, 4754 insertions(+), 4754 deletions(-)


[commons-pool] 01/04: PMD: Avoid empty if statements

Posted by gg...@apache.org.
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 09227e8fc8a2107bb304882d24f0d311618eb504
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Sat Jul 2 19:03:40 2022 -0400

    PMD: Avoid empty if statements
---
 .../commons/pool2/impl/DefaultPooledObject.java    | 686 ++++++++++-----------
 .../commons/pool2/impl/GenericKeyedObjectPool.java |   7 +-
 .../commons/pool2/impl/GenericObjectPool.java      |   7 +-
 3 files changed, 348 insertions(+), 352 deletions(-)

diff --git a/src/main/java/org/apache/commons/pool2/impl/DefaultPooledObject.java b/src/main/java/org/apache/commons/pool2/impl/DefaultPooledObject.java
index b4ddbb59..e71e1b9f 100644
--- a/src/main/java/org/apache/commons/pool2/impl/DefaultPooledObject.java
+++ b/src/main/java/org/apache/commons/pool2/impl/DefaultPooledObject.java
@@ -1,345 +1,343 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.commons.pool2.impl;
-
-import java.io.PrintWriter;
-import java.time.Clock;
-import java.time.Duration;
-import java.time.Instant;
-import java.util.Deque;
-
-import org.apache.commons.pool2.PooledObject;
-import org.apache.commons.pool2.PooledObjectState;
-import org.apache.commons.pool2.TrackedUse;
-
-/**
- * This wrapper is used to track the additional information, such as state, for
- * the pooled objects.
- * <p>
- * This class is intended to be thread-safe.
- * </p>
- *
- * @param <T> the type of object in the pool
- *
- * @since 2.0
- */
-public class DefaultPooledObject<T> implements PooledObject<T> {
-
-    private final T object;
-    private PooledObjectState state = PooledObjectState.IDLE; // @GuardedBy("this") to ensure transitions are valid
-    private final Clock systemClock = Clock.systemUTC();
-    private final Instant createInstant = now();
-
-    private volatile Instant lastBorrowInstant = createInstant;
-    private volatile Instant lastUseInstant = createInstant;
-    private volatile Instant lastReturnInstant = createInstant;
-    private volatile boolean logAbandoned;
-    private volatile CallStack borrowedBy = NoOpCallStack.INSTANCE;
-    private volatile CallStack usedBy = NoOpCallStack.INSTANCE;
-    private volatile long borrowedCount;
-
-    /**
-     * Creates a new instance that wraps the provided object so that the pool can
-     * track the state of the pooled object.
-     *
-     * @param object The object to wrap
-     */
-    public DefaultPooledObject(final T object) {
-        this.object = object;
-    }
-
-    /**
-     * Allocates the object.
-     *
-     * @return {@code true} if the original state was {@link PooledObjectState#IDLE IDLE}
-     */
-    @Override
-    public synchronized boolean allocate() {
-        if (state == PooledObjectState.IDLE) {
-            state = PooledObjectState.ALLOCATED;
-            lastBorrowInstant = now();
-            lastUseInstant = lastBorrowInstant;
-            borrowedCount++;
-            if (logAbandoned) {
-                borrowedBy.fillInStackTrace();
-            }
-            return true;
-        }
-        if (state == PooledObjectState.EVICTION) {
-            // TODO Allocate anyway and ignore eviction test
-            state = PooledObjectState.EVICTION_RETURN_TO_HEAD;
-        }
-        // TODO if validating and testOnBorrow == true then pre-allocate for
-        // performance
-        return false;
-    }
-
-    @Override
-    public int compareTo(final PooledObject<T> other) {
-        final int compareTo = getLastReturnInstant().compareTo(other.getLastReturnInstant());
-        if (compareTo == 0) {
-            // Make sure the natural ordering is broadly consistent with equals
-            // although this will break down if distinct objects have the same
-            // identity hash code.
-            // see java.lang.Comparable Javadocs
-            return System.identityHashCode(this) - System.identityHashCode(other);
-        }
-        return compareTo;
-    }
-
-    /**
-     * Deallocates the object and sets it {@link PooledObjectState#IDLE IDLE}
-     * if it is currently {@link PooledObjectState#ALLOCATED ALLOCATED}
-     * or {@link PooledObjectState#RETURNING RETURNING}.
-     *
-     * @return {@code true} if the state was {@link PooledObjectState#ALLOCATED ALLOCATED}
-     *         or {@link PooledObjectState#RETURNING RETURNING}.
-     */
-    @Override
-    public synchronized boolean deallocate() {
-        if (state == PooledObjectState.ALLOCATED || state == PooledObjectState.RETURNING) {
-            state = PooledObjectState.IDLE;
-            lastReturnInstant = now();
-            borrowedBy.clear();
-            return true;
-        }
-
-        return false;
-    }
-
-    @Override
-    public synchronized boolean endEvictionTest(
-            final Deque<PooledObject<T>> idleQueue) {
-        if (state == PooledObjectState.EVICTION) {
-            state = PooledObjectState.IDLE;
-            return true;
-        }
-        if (state == PooledObjectState.EVICTION_RETURN_TO_HEAD) {
-            state = PooledObjectState.IDLE;
-            if (!idleQueue.offerFirst(this)) {
-                // TODO - Should never happen
-            }
-        }
-
-        return false;
-    }
-
-    @Override
-    public long getActiveTimeMillis() {
-        return getActiveDuration().toMillis();
-    }
-
-    /**
-     * Gets the number of times this object has been borrowed.
-     * @return The number of times this object has been borrowed.
-     * @since 2.1
-     */
-    @Override
-    public long getBorrowedCount() {
-        return borrowedCount;
-    }
-
-    @Override
-    public Instant getCreateInstant() {
-        return createInstant;
-    }
-
-    @Override
-    public long getCreateTime() {
-        return createInstant.toEpochMilli();
-    }
-
-    @Override
-    public Duration getIdleDuration() {
-        // elapsed may be negative if:
-        // - another thread updates lastReturnInstant during the calculation window
-        // - System.currentTimeMillis() is not monotonic (e.g. system time is set back)
-        final Duration elapsed = Duration.between(lastReturnInstant, now());
-        return elapsed.isNegative() ? Duration.ZERO : elapsed;
-    }
-
-    @Override
-    public Duration getIdleTime() {
-        return getIdleDuration();
-    }
-
-    @Override
-    public long getIdleTimeMillis() {
-        return getIdleDuration().toMillis();
-    }
-
-    @Override
-    public Instant getLastBorrowInstant() {
-        return lastBorrowInstant;
-    }
-
-    @Override
-    public long getLastBorrowTime() {
-        return lastBorrowInstant.toEpochMilli();
-    }
-
-    @Override
-    public Instant getLastReturnInstant() {
-        return lastReturnInstant;
-    }
-
-    @Override
-    public long getLastReturnTime() {
-        return lastReturnInstant.toEpochMilli();
-    }
-
-    /**
-     * Gets an estimate of the last time this object was used.  If the class
-     * of the pooled object implements {@link TrackedUse}, what is returned is
-     * the maximum of {@link TrackedUse#getLastUsedInstant()} and
-     * {@link #getLastBorrowTime()}; otherwise this method gives the same
-     * value as {@link #getLastBorrowTime()}.
-     *
-     * @return the last Instant this object was used.
-     */
-    @Override
-    public Instant getLastUsedInstant() {
-        if (object instanceof TrackedUse) {
-            return PoolImplUtils.max(((TrackedUse) object).getLastUsedInstant(), lastUseInstant);
-        }
-        return lastUseInstant;
-    }
-
-    /**
-     * Gets an estimate of the last time this object was used.  If the class
-     * of the pooled object implements {@link TrackedUse}, what is returned is
-     * the maximum of {@link TrackedUse#getLastUsedInstant()} and
-     * {@link #getLastBorrowTime()}; otherwise this method gives the same
-     * value as {@link #getLastBorrowTime()}.
-     *
-     * @return the last time this object was used
-     */
-    @Override
-    public long getLastUsedTime() {
-        return getLastUsedInstant().toEpochMilli();
-    }
-
-    @Override
-    public T getObject() {
-        return object;
-    }
-
-    /**
-     * Gets the state of this object.
-     * @return state
-     */
-    @Override
-    public synchronized PooledObjectState getState() {
-        return state;
-    }
-
-    /**
-     * Sets the state to {@link PooledObjectState#INVALID INVALID}.
-     */
-    @Override
-    public synchronized void invalidate() {
-        state = PooledObjectState.INVALID;
-    }
-
-    /**
-     * Marks the pooled object as {@link PooledObjectState#ABANDONED ABANDONED}.
-     */
-    @Override
-    public synchronized void markAbandoned() {
-        state = PooledObjectState.ABANDONED;
-    }
-
-    /**
-     * Marks the pooled object as {@link PooledObjectState#RETURNING RETURNING}.
-     */
-    @Override
-    public synchronized void markReturning() {
-        state = PooledObjectState.RETURNING;
-    }
-
-    /**
-     * Gets the current instant of the clock.
-     *
-     * @return the current instant of the clock.
-     */
-    private Instant now() {
-        return systemClock.instant();
-    }
-
-    @Override
-    public void printStackTrace(final PrintWriter writer) {
-        boolean written = borrowedBy.printStackTrace(writer);
-        written |= usedBy.printStackTrace(writer);
-        if (written) {
-            writer.flush();
-        }
-    }
-
-    @Override
-    public void setLogAbandoned(final boolean logAbandoned) {
-        this.logAbandoned = logAbandoned;
-    }
-
-    /**
-     * Configures the stack trace generation strategy based on whether or not fully
-     * detailed stack traces are required. When set to false, abandoned logs may
-     * only include caller class information rather than method names, line numbers,
-     * and other normal metadata available in a full stack trace.
-     *
-     * @param requireFullStackTrace the new configuration setting for abandoned object
-     *                              logging
-     * @since 2.5
-     */
-    @Override
-    public void setRequireFullStackTrace(final boolean requireFullStackTrace) {
-        borrowedBy = CallStackUtils.newCallStack("'Pooled object created' " +
-            "yyyy-MM-dd HH:mm:ss Z 'by the following code has not been returned to the pool:'",
-            true, requireFullStackTrace);
-        usedBy = CallStackUtils.newCallStack("The last code to use this object was:",
-            false, requireFullStackTrace);
-    }
-
-    @Override
-    public synchronized boolean startEvictionTest() {
-        if (state == PooledObjectState.IDLE) {
-            state = PooledObjectState.EVICTION;
-            return true;
-        }
-        return false;
-    }
-
-    @Override
-    public String toString() {
-        final StringBuilder result = new StringBuilder();
-        result.append("Object: ");
-        result.append(object.toString());
-        result.append(", State: ");
-        synchronized (this) {
-            result.append(state.toString());
-        }
-        return result.toString();
-        // TODO add other attributes
-    }
-
-    @Override
-    public void use() {
-        lastUseInstant = now();
-        usedBy.fillInStackTrace();
-    }
-
-
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.pool2.impl;
+
+import java.io.PrintWriter;
+import java.time.Clock;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Deque;
+
+import org.apache.commons.pool2.PooledObject;
+import org.apache.commons.pool2.PooledObjectState;
+import org.apache.commons.pool2.TrackedUse;
+
+/**
+ * This wrapper is used to track the additional information, such as state, for
+ * the pooled objects.
+ * <p>
+ * This class is intended to be thread-safe.
+ * </p>
+ *
+ * @param <T> the type of object in the pool
+ *
+ * @since 2.0
+ */
+public class DefaultPooledObject<T> implements PooledObject<T> {
+
+    private final T object;
+    private PooledObjectState state = PooledObjectState.IDLE; // @GuardedBy("this") to ensure transitions are valid
+    private final Clock systemClock = Clock.systemUTC();
+    private final Instant createInstant = now();
+
+    private volatile Instant lastBorrowInstant = createInstant;
+    private volatile Instant lastUseInstant = createInstant;
+    private volatile Instant lastReturnInstant = createInstant;
+    private volatile boolean logAbandoned;
+    private volatile CallStack borrowedBy = NoOpCallStack.INSTANCE;
+    private volatile CallStack usedBy = NoOpCallStack.INSTANCE;
+    private volatile long borrowedCount;
+
+    /**
+     * Creates a new instance that wraps the provided object so that the pool can
+     * track the state of the pooled object.
+     *
+     * @param object The object to wrap
+     */
+    public DefaultPooledObject(final T object) {
+        this.object = object;
+    }
+
+    /**
+     * Allocates the object.
+     *
+     * @return {@code true} if the original state was {@link PooledObjectState#IDLE IDLE}
+     */
+    @Override
+    public synchronized boolean allocate() {
+        if (state == PooledObjectState.IDLE) {
+            state = PooledObjectState.ALLOCATED;
+            lastBorrowInstant = now();
+            lastUseInstant = lastBorrowInstant;
+            borrowedCount++;
+            if (logAbandoned) {
+                borrowedBy.fillInStackTrace();
+            }
+            return true;
+        }
+        if (state == PooledObjectState.EVICTION) {
+            // TODO Allocate anyway and ignore eviction test
+            state = PooledObjectState.EVICTION_RETURN_TO_HEAD;
+        }
+        // TODO if validating and testOnBorrow == true then pre-allocate for
+        // performance
+        return false;
+    }
+
+    @Override
+    public int compareTo(final PooledObject<T> other) {
+        final int compareTo = getLastReturnInstant().compareTo(other.getLastReturnInstant());
+        if (compareTo == 0) {
+            // Make sure the natural ordering is broadly consistent with equals
+            // although this will break down if distinct objects have the same
+            // identity hash code.
+            // see java.lang.Comparable Javadocs
+            return System.identityHashCode(this) - System.identityHashCode(other);
+        }
+        return compareTo;
+    }
+
+    /**
+     * Deallocates the object and sets it {@link PooledObjectState#IDLE IDLE}
+     * if it is currently {@link PooledObjectState#ALLOCATED ALLOCATED}
+     * or {@link PooledObjectState#RETURNING RETURNING}.
+     *
+     * @return {@code true} if the state was {@link PooledObjectState#ALLOCATED ALLOCATED}
+     *         or {@link PooledObjectState#RETURNING RETURNING}.
+     */
+    @Override
+    public synchronized boolean deallocate() {
+        if (state == PooledObjectState.ALLOCATED || state == PooledObjectState.RETURNING) {
+            state = PooledObjectState.IDLE;
+            lastReturnInstant = now();
+            borrowedBy.clear();
+            return true;
+        }
+
+        return false;
+    }
+
+    @Override
+    public synchronized boolean endEvictionTest(
+            final Deque<PooledObject<T>> idleQueue) {
+        if (state == PooledObjectState.EVICTION) {
+            state = PooledObjectState.IDLE;
+            return true;
+        }
+        if (state == PooledObjectState.EVICTION_RETURN_TO_HEAD) {
+            state = PooledObjectState.IDLE;
+            idleQueue.offerFirst(this);
+        }
+
+        return false;
+    }
+
+    @Override
+    public long getActiveTimeMillis() {
+        return getActiveDuration().toMillis();
+    }
+
+    /**
+     * Gets the number of times this object has been borrowed.
+     * @return The number of times this object has been borrowed.
+     * @since 2.1
+     */
+    @Override
+    public long getBorrowedCount() {
+        return borrowedCount;
+    }
+
+    @Override
+    public Instant getCreateInstant() {
+        return createInstant;
+    }
+
+    @Override
+    public long getCreateTime() {
+        return createInstant.toEpochMilli();
+    }
+
+    @Override
+    public Duration getIdleDuration() {
+        // elapsed may be negative if:
+        // - another thread updates lastReturnInstant during the calculation window
+        // - System.currentTimeMillis() is not monotonic (e.g. system time is set back)
+        final Duration elapsed = Duration.between(lastReturnInstant, now());
+        return elapsed.isNegative() ? Duration.ZERO : elapsed;
+    }
+
+    @Override
+    public Duration getIdleTime() {
+        return getIdleDuration();
+    }
+
+    @Override
+    public long getIdleTimeMillis() {
+        return getIdleDuration().toMillis();
+    }
+
+    @Override
+    public Instant getLastBorrowInstant() {
+        return lastBorrowInstant;
+    }
+
+    @Override
+    public long getLastBorrowTime() {
+        return lastBorrowInstant.toEpochMilli();
+    }
+
+    @Override
+    public Instant getLastReturnInstant() {
+        return lastReturnInstant;
+    }
+
+    @Override
+    public long getLastReturnTime() {
+        return lastReturnInstant.toEpochMilli();
+    }
+
+    /**
+     * Gets an estimate of the last time this object was used.  If the class
+     * of the pooled object implements {@link TrackedUse}, what is returned is
+     * the maximum of {@link TrackedUse#getLastUsedInstant()} and
+     * {@link #getLastBorrowTime()}; otherwise this method gives the same
+     * value as {@link #getLastBorrowTime()}.
+     *
+     * @return the last Instant this object was used.
+     */
+    @Override
+    public Instant getLastUsedInstant() {
+        if (object instanceof TrackedUse) {
+            return PoolImplUtils.max(((TrackedUse) object).getLastUsedInstant(), lastUseInstant);
+        }
+        return lastUseInstant;
+    }
+
+    /**
+     * Gets an estimate of the last time this object was used.  If the class
+     * of the pooled object implements {@link TrackedUse}, what is returned is
+     * the maximum of {@link TrackedUse#getLastUsedInstant()} and
+     * {@link #getLastBorrowTime()}; otherwise this method gives the same
+     * value as {@link #getLastBorrowTime()}.
+     *
+     * @return the last time this object was used
+     */
+    @Override
+    public long getLastUsedTime() {
+        return getLastUsedInstant().toEpochMilli();
+    }
+
+    @Override
+    public T getObject() {
+        return object;
+    }
+
+    /**
+     * Gets the state of this object.
+     * @return state
+     */
+    @Override
+    public synchronized PooledObjectState getState() {
+        return state;
+    }
+
+    /**
+     * Sets the state to {@link PooledObjectState#INVALID INVALID}.
+     */
+    @Override
+    public synchronized void invalidate() {
+        state = PooledObjectState.INVALID;
+    }
+
+    /**
+     * Marks the pooled object as {@link PooledObjectState#ABANDONED ABANDONED}.
+     */
+    @Override
+    public synchronized void markAbandoned() {
+        state = PooledObjectState.ABANDONED;
+    }
+
+    /**
+     * Marks the pooled object as {@link PooledObjectState#RETURNING RETURNING}.
+     */
+    @Override
+    public synchronized void markReturning() {
+        state = PooledObjectState.RETURNING;
+    }
+
+    /**
+     * Gets the current instant of the clock.
+     *
+     * @return the current instant of the clock.
+     */
+    private Instant now() {
+        return systemClock.instant();
+    }
+
+    @Override
+    public void printStackTrace(final PrintWriter writer) {
+        boolean written = borrowedBy.printStackTrace(writer);
+        written |= usedBy.printStackTrace(writer);
+        if (written) {
+            writer.flush();
+        }
+    }
+
+    @Override
+    public void setLogAbandoned(final boolean logAbandoned) {
+        this.logAbandoned = logAbandoned;
+    }
+
+    /**
+     * Configures the stack trace generation strategy based on whether or not fully
+     * detailed stack traces are required. When set to false, abandoned logs may
+     * only include caller class information rather than method names, line numbers,
+     * and other normal metadata available in a full stack trace.
+     *
+     * @param requireFullStackTrace the new configuration setting for abandoned object
+     *                              logging
+     * @since 2.5
+     */
+    @Override
+    public void setRequireFullStackTrace(final boolean requireFullStackTrace) {
+        borrowedBy = CallStackUtils.newCallStack("'Pooled object created' " +
+            "yyyy-MM-dd HH:mm:ss Z 'by the following code has not been returned to the pool:'",
+            true, requireFullStackTrace);
+        usedBy = CallStackUtils.newCallStack("The last code to use this object was:",
+            false, requireFullStackTrace);
+    }
+
+    @Override
+    public synchronized boolean startEvictionTest() {
+        if (state == PooledObjectState.IDLE) {
+            state = PooledObjectState.EVICTION;
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder result = new StringBuilder();
+        result.append("Object: ");
+        result.append(object.toString());
+        result.append(", State: ");
+        synchronized (this) {
+            result.append(state.toString());
+        }
+        return result.toString();
+        // TODO add other attributes
+    }
+
+    @Override
+    public void use() {
+        lastUseInstant = now();
+        usedBy.fillInStackTrace();
+    }
+
+
 }
\ No newline at end of file
diff --git a/src/main/java/org/apache/commons/pool2/impl/GenericKeyedObjectPool.java b/src/main/java/org/apache/commons/pool2/impl/GenericKeyedObjectPool.java
index c7433a84..8c4e6646 100644
--- a/src/main/java/org/apache/commons/pool2/impl/GenericKeyedObjectPool.java
+++ b/src/main/java/org/apache/commons/pool2/impl/GenericKeyedObjectPool.java
@@ -1085,10 +1085,9 @@ public class GenericKeyedObjectPool<K, T, E extends Exception> extends BaseGener
                                 }
                             }
                         }
-                        if (!underTest.endEvictionTest(idleObjects)) {
-                            // TODO - May need to add code here once additional
-                            // states are used
-                        }
+                        underTest.endEvictionTest(idleObjects);
+                        // TODO - May need to add code here once additional
+                        // states are used
                     }
                 }
             }
diff --git a/src/main/java/org/apache/commons/pool2/impl/GenericObjectPool.java b/src/main/java/org/apache/commons/pool2/impl/GenericObjectPool.java
index 8f859dda..1eb4bb22 100644
--- a/src/main/java/org/apache/commons/pool2/impl/GenericObjectPool.java
+++ b/src/main/java/org/apache/commons/pool2/impl/GenericObjectPool.java
@@ -757,10 +757,9 @@ public class GenericObjectPool<T, E extends Exception> extends BaseGenericObject
                                 }
                             }
                         }
-                        if (!underTest.endEvictionTest(idleObjects)) {
-                            // TODO - May need to add code here once additional
-                            // states are used
-                        }
+                        underTest.endEvictionTest(idleObjects);
+                        // TODO - May need to add code here once additional
+                        // states are used
                     }
                 }
             }


[commons-pool] 02/04: Clean ups.

Posted by gg...@apache.org.
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 4163d8a6e611329546e7dd2ece2d46a46e090599
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Sat Jul 2 19:18:51 2022 -0400

    Clean ups.
    
    - Remove end of line whitespace
    - PMD UselessParentheses
    - Use final
    - Use diamonds
---
 .../java/org/apache/commons/pool2/PoolUtils.java   |    2 +-
 .../commons/pool2/impl/DefaultEvictionPolicy.java  |  108 +-
 .../apache/commons/pool2/impl/EvictionTimer.java   |    4 +-
 .../commons/pool2/impl/GenericKeyedObjectPool.java |   14 +-
 .../commons/pool2/impl/GenericObjectPool.java      |   20 +-
 .../pool2/impl/SoftReferenceObjectPool.java        |    2 +-
 .../apache/commons/pool2/TestBaseObjectPool.java   |  562 +-
 .../apache/commons/pool2/TestKeyedObjectPool.java  | 1540 ++---
 .../org/apache/commons/pool2/WaiterFactory.java    |  434 +-
 .../pool2/impl/TestGenericKeyedObjectPool.java     |   92 +-
 .../commons/pool2/impl/TestGenericObjectPool.java  | 6022 ++++++++++----------
 11 files changed, 4400 insertions(+), 4400 deletions(-)

diff --git a/src/main/java/org/apache/commons/pool2/PoolUtils.java b/src/main/java/org/apache/commons/pool2/PoolUtils.java
index 4c732579..d94e4849 100644
--- a/src/main/java/org/apache/commons/pool2/PoolUtils.java
+++ b/src/main/java/org/apache/commons/pool2/PoolUtils.java
@@ -99,7 +99,7 @@ public final class PoolUtils {
             idleHighWaterMark = Math.max(idle, idleHighWaterMark);
             final float maxInterval = 15f;
             final float minutes = maxInterval +
-                    ((1f - maxInterval) / idleHighWaterMark) * idle;
+                    (1f - maxInterval) / idleHighWaterMark * idle;
             nextShrinkMillis = nowMillis + (long) (minutes * 60000f * factor);
         }
     }
diff --git a/src/main/java/org/apache/commons/pool2/impl/DefaultEvictionPolicy.java b/src/main/java/org/apache/commons/pool2/impl/DefaultEvictionPolicy.java
index caf5dfea..5af41b18 100644
--- a/src/main/java/org/apache/commons/pool2/impl/DefaultEvictionPolicy.java
+++ b/src/main/java/org/apache/commons/pool2/impl/DefaultEvictionPolicy.java
@@ -1,54 +1,54 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.commons.pool2.impl;
-
-import org.apache.commons.pool2.PooledObject;
-
-/**
- * Provides the default implementation of {@link EvictionPolicy} used by the pools.
- * <p>
- * Objects will be evicted if the following conditions are met:
- * </p>
- * <ul>
- * <li>The object has been idle longer than
- *     {@link GenericObjectPool#getMinEvictableIdleDuration()} /
- *     {@link GenericKeyedObjectPool#getMinEvictableIdleDuration()}</li>
- * <li>There are more than {@link GenericObjectPool#getMinIdle()} /
- *     {@link GenericKeyedObjectPoolConfig#getMinIdlePerKey()} idle objects in
- *     the pool and the object has been idle for longer than
- *     {@link GenericObjectPool#getSoftMinEvictableIdleDuration()} /
- *     {@link GenericKeyedObjectPool#getSoftMinEvictableIdleDuration()}
- * </ul>
- * <p>
- * This class is immutable and thread-safe.
- * </p>
- *
- * @param <T> the type of objects in the pool.
- *
- * @since 2.0
- */
-public class DefaultEvictionPolicy<T> implements EvictionPolicy<T> {
-
-    @Override
-    public boolean evict(final EvictionConfig config, final PooledObject<T> underTest, final int idleCount) {
-        // @formatter:off
-        return (config.getIdleSoftEvictDuration().compareTo(underTest.getIdleDuration()) < 0 &&
-                config.getMinIdle() < idleCount) ||
-                config.getIdleEvictDuration().compareTo(underTest.getIdleDuration()) < 0;
-        // @formatter:on
-    }
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.pool2.impl;
+
+import org.apache.commons.pool2.PooledObject;
+
+/**
+ * Provides the default implementation of {@link EvictionPolicy} used by the pools.
+ * <p>
+ * Objects will be evicted if the following conditions are met:
+ * </p>
+ * <ul>
+ * <li>The object has been idle longer than
+ *     {@link GenericObjectPool#getMinEvictableIdleDuration()} /
+ *     {@link GenericKeyedObjectPool#getMinEvictableIdleDuration()}</li>
+ * <li>There are more than {@link GenericObjectPool#getMinIdle()} /
+ *     {@link GenericKeyedObjectPoolConfig#getMinIdlePerKey()} idle objects in
+ *     the pool and the object has been idle for longer than
+ *     {@link GenericObjectPool#getSoftMinEvictableIdleDuration()} /
+ *     {@link GenericKeyedObjectPool#getSoftMinEvictableIdleDuration()}
+ * </ul>
+ * <p>
+ * This class is immutable and thread-safe.
+ * </p>
+ *
+ * @param <T> the type of objects in the pool.
+ *
+ * @since 2.0
+ */
+public class DefaultEvictionPolicy<T> implements EvictionPolicy<T> {
+
+    @Override
+    public boolean evict(final EvictionConfig config, final PooledObject<T> underTest, final int idleCount) {
+        // @formatter:off
+        return config.getIdleSoftEvictDuration().compareTo(underTest.getIdleDuration()) < 0 &&
+                config.getMinIdle() < idleCount ||
+                config.getIdleEvictDuration().compareTo(underTest.getIdleDuration()) < 0;
+        // @formatter:on
+    }
+}
diff --git a/src/main/java/org/apache/commons/pool2/impl/EvictionTimer.java b/src/main/java/org/apache/commons/pool2/impl/EvictionTimer.java
index 24e162df..b99e47e7 100644
--- a/src/main/java/org/apache/commons/pool2/impl/EvictionTimer.java
+++ b/src/main/java/org/apache/commons/pool2/impl/EvictionTimer.java
@@ -126,7 +126,7 @@ class EvictionTimer {
 
     /** Keys are weak references to tasks, values are runners managed by executor. */
     private static final HashMap<
-        WeakReference<BaseGenericObjectPool<?, ?>.Evictor>, 
+        WeakReference<BaseGenericObjectPool<?, ?>.Evictor>,
         WeakRunner<BaseGenericObjectPool<?, ?>.Evictor>> TASK_MAP = new HashMap<>(); // @GuardedBy("EvictionTimer.class")
 
     /**
@@ -159,7 +159,7 @@ class EvictionTimer {
 
     /**
      * For testing only.
-     * 
+     *
      * @return The executor.
      */
     static ScheduledThreadPoolExecutor getExecutor() {
diff --git a/src/main/java/org/apache/commons/pool2/impl/GenericKeyedObjectPool.java b/src/main/java/org/apache/commons/pool2/impl/GenericKeyedObjectPool.java
index 8c4e6646..55ad8881 100644
--- a/src/main/java/org/apache/commons/pool2/impl/GenericKeyedObjectPool.java
+++ b/src/main/java/org/apache/commons/pool2/impl/GenericKeyedObjectPool.java
@@ -416,8 +416,8 @@ public class GenericKeyedObjectPool<K, T, E extends Exception> extends BaseGener
         assertOpen();
 
         final AbandonedConfig ac = this.abandonedConfig;
-        if (ac != null && ac.getRemoveAbandonedOnBorrow() && (getNumIdle() < 2) &&
-                (getNumActive() > getMaxTotal() - 3)) {
+        if (ac != null && ac.getRemoveAbandonedOnBorrow() && getNumIdle() < 2 &&
+                getNumActive() > getMaxTotal() - 3) {
             removeAbandoned(ac);
         }
 
@@ -444,7 +444,7 @@ public class GenericKeyedObjectPool<K, T, E extends Exception> extends BaseGener
                 if (blockWhenExhausted) {
                     if (p == null) {
                         try {
-                            p = borrowMaxWaitMillis < 0 ? objectDeque.getIdleObjects().takeFirst() : 
+                            p = borrowMaxWaitMillis < 0 ? objectDeque.getIdleObjects().takeFirst() :
                                 objectDeque.getIdleObjects().pollFirst(borrowMaxWaitMillis, TimeUnit.MILLISECONDS);
                         } catch (InterruptedException e) {
                             throw cast(e);
@@ -604,7 +604,7 @@ public class GenericKeyedObjectPool<K, T, E extends Exception> extends BaseGener
      * @param key the key to clear
      * @param reuseCapacity whether or not to reuse freed capacity
      */
-    public void clear(final K key, boolean reuseCapacity) {
+    public void clear(final K key, final boolean reuseCapacity) {
         final ObjectDeque<T> objectDeque = register(key);
         int freedCapacity = 0;
         try {
@@ -653,7 +653,7 @@ public class GenericKeyedObjectPool<K, T, E extends Exception> extends BaseGener
 
         // Now iterate created map and kill the first 15% plus one to account
         // for zero
-        int itemsToRemove = ((int) (map.size() * 0.15)) + 1;
+        int itemsToRemove = (int) (map.size() * 0.15) + 1;
         final Iterator<Entry<PooledObject<T>, K>> iter = map.entrySet().iterator();
 
         while (iter.hasNext() && itemsToRemove > 0) {
@@ -1237,7 +1237,7 @@ public class GenericKeyedObjectPool<K, T, E extends Exception> extends BaseGener
         if (numTests >= 0) {
             return Math.min(numTests, totalIdle);
         }
-        return (int) (Math.ceil(totalIdle / Math.abs((double) numTests)));
+        return (int) Math.ceil(totalIdle / Math.abs((double) numTests));
     }
 
     /**
@@ -1617,7 +1617,7 @@ public class GenericKeyedObjectPool<K, T, E extends Exception> extends BaseGener
      *
      * @param newCapacity number of new instances to attempt to create.
      */
-    private void reuseCapacity(int newCapacity) {
+    private void reuseCapacity(final int newCapacity) {
         for (int i = 0; i < newCapacity; i++) {
             reuseCapacity();
         }
diff --git a/src/main/java/org/apache/commons/pool2/impl/GenericObjectPool.java b/src/main/java/org/apache/commons/pool2/impl/GenericObjectPool.java
index 1eb4bb22..1e105cc2 100644
--- a/src/main/java/org/apache/commons/pool2/impl/GenericObjectPool.java
+++ b/src/main/java/org/apache/commons/pool2/impl/GenericObjectPool.java
@@ -275,8 +275,8 @@ public class GenericObjectPool<T, E extends Exception> extends BaseGenericObject
         assertOpen();
 
         final AbandonedConfig ac = this.abandonedConfig;
-        if (ac != null && ac.getRemoveAbandonedOnBorrow() && (getNumIdle() < 2) &&
-                (getNumActive() > getMaxTotal() - 3)) {
+        if (ac != null && ac.getRemoveAbandonedOnBorrow() && getNumIdle() < 2 &&
+                getNumActive() > getMaxTotal() - 3) {
             removeAbandoned(ac);
         }
 
@@ -305,7 +305,7 @@ public class GenericObjectPool<T, E extends Exception> extends BaseGenericObject
                     } catch (final InterruptedException e) {
                         // Don't surface exception type of internal locking mechanism.
                         throw cast(e);
-                    }  
+                    }
                 }
                 if (p == null) {
                     throw new NoSuchElementException(appendStats(
@@ -547,8 +547,8 @@ public class GenericObjectPool<T, E extends Exception> extends BaseGenericObject
 
             // Do not block more if maxWaitTimeMillis is set.
             if (create == null &&
-                (localMaxWaitTimeMillis > 0 &&
-                 System.currentTimeMillis() - localStartTimeMillis >= localMaxWaitTimeMillis)) {
+                localMaxWaitTimeMillis > 0 &&
+                 System.currentTimeMillis() - localStartTimeMillis >= localMaxWaitTimeMillis) {
                 create = Boolean.FALSE;
             }
         }
@@ -620,7 +620,7 @@ public class GenericObjectPool<T, E extends Exception> extends BaseGenericObject
      * @throws E if the factory's makeObject throws
      */
     private void ensureIdle(final int idleCount, final boolean always) throws E {
-        if (idleCount < 1 || isClosed() || (!always && !idleObjects.hasTakeWaiters())) {
+        if (idleCount < 1 || isClosed() || !always && !idleObjects.hasTakeWaiters()) {
             return;
         }
 
@@ -865,8 +865,8 @@ public class GenericObjectPool<T, E extends Exception> extends BaseGenericObject
         if (numTestsPerEvictionRun >= 0) {
             return Math.min(numTestsPerEvictionRun, idleObjects.size());
         }
-        return (int) (Math.ceil(idleObjects.size() /
-                Math.abs((double) numTestsPerEvictionRun)));
+        return (int) Math.ceil(idleObjects.size() /
+                Math.abs((double) numTestsPerEvictionRun));
     }
 
     /**
@@ -918,7 +918,7 @@ public class GenericObjectPool<T, E extends Exception> extends BaseGenericObject
      * Activation of this method decrements the active count and attempts to destroy the instance, using the provided
      * {@link DestroyMode}.
      * </p>
-     * 
+     *
      * @throws E if an exception occurs destroying the object
      * @throws IllegalStateException if obj does not belong to this pool
      * @since 2.9.0
@@ -1177,5 +1177,5 @@ public class GenericObjectPool<T, E extends Exception> extends BaseGenericObject
             getPooledObject(pooledObject).use();
         }
     }
-    
+
 }
diff --git a/src/main/java/org/apache/commons/pool2/impl/SoftReferenceObjectPool.java b/src/main/java/org/apache/commons/pool2/impl/SoftReferenceObjectPool.java
index 5e72b09a..bc5ad180 100644
--- a/src/main/java/org/apache/commons/pool2/impl/SoftReferenceObjectPool.java
+++ b/src/main/java/org/apache/commons/pool2/impl/SoftReferenceObjectPool.java
@@ -354,7 +354,7 @@ public class SoftReferenceObjectPool<T, E extends Exception> extends BaseObjectP
         removeClearedReferences(idleReferences.iterator());
         removeClearedReferences(allReferences.iterator());
         while (refQueue.poll() != null) {
-            // empty
+            // draining queue
         }
     }
 
diff --git a/src/test/java/org/apache/commons/pool2/TestBaseObjectPool.java b/src/test/java/org/apache/commons/pool2/TestBaseObjectPool.java
index fb20afda..0d034c7e 100644
--- a/src/test/java/org/apache/commons/pool2/TestBaseObjectPool.java
+++ b/src/test/java/org/apache/commons/pool2/TestBaseObjectPool.java
@@ -1,281 +1,281 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.commons.pool2;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-import static org.junit.jupiter.api.Assertions.fail;
-
-import org.junit.jupiter.api.Test;
-
-/**
- */
-public class TestBaseObjectPool extends TestObjectPool {
-    private static class TestObjectPool extends BaseObjectPool<Object, RuntimeException> {
-        
-        @Override
-        public Object borrowObject() {
-            return null;
-        }
-        
-        @Override
-        public void invalidateObject(final Object obj) {
-        }
-        
-        @Override
-        public void returnObject(final Object obj) {
-        }
-    }
-
-    private ObjectPool<String, RuntimeException> pool;
-
-    /**
-     * @param n Ignored by this implemented. Used by sub-classes.
-     *
-     * @return the Nth object (zero indexed)
-     */
-    protected Object getNthObject(final int n) {
-        if (this.getClass() != TestBaseObjectPool.class) {
-            fail("Subclasses of TestBaseObjectPool must reimplement this method.");
-        }
-        throw new UnsupportedOperationException("BaseObjectPool isn't a complete implementation.");
-    }
-
-    protected boolean isFifo() {
-        if (this.getClass() != TestBaseObjectPool.class) {
-            fail("Subclasses of TestBaseObjectPool must reimplement this method.");
-        }
-        return false;
-    }
-
-    protected boolean isLifo() {
-        if (this.getClass() != TestBaseObjectPool.class) {
-            fail("Subclasses of TestBaseObjectPool must reimplement this method.");
-        }
-        return false;
-    }
-
-    /**
-     * @param minCapacity Ignored by this implemented. Used by sub-classes.
-     *
-     * @return A newly created empty pool
-     */
-    protected <E extends Exception> ObjectPool<String, E> makeEmptyPool(final int minCapacity) {
-        if (this.getClass() != TestBaseObjectPool.class) {
-            fail("Subclasses of TestBaseObjectPool must reimplement this method.");
-        }
-        throw new UnsupportedOperationException("BaseObjectPool isn't a complete implementation.");
-    }
-
-    @Override
-    protected <E extends Exception> ObjectPool<Object, E> makeEmptyPool(final PooledObjectFactory<Object, E> factory) {
-        if (this.getClass() != TestBaseObjectPool.class) {
-            fail("Subclasses of TestBaseObjectPool must reimplement this method.");
-        }
-        throw new UnsupportedOperationException("BaseObjectPool isn't a complete implementation.");
-    }
-
-    @Test
-    public void testBaseAddObject() {
-        try {
-            pool = makeEmptyPool(3);
-        } catch(final UnsupportedOperationException e) {
-            return; // skip this test if unsupported
-        }
-        try {
-            assertEquals(0, pool.getNumIdle());
-            assertEquals(0, pool.getNumActive());
-            pool.addObject();
-            assertEquals(1, pool.getNumIdle());
-            assertEquals(0, pool.getNumActive());
-            final String obj = pool.borrowObject();
-            assertEquals(getNthObject(0), obj);
-            assertEquals(0, pool.getNumIdle());
-            assertEquals(1, pool.getNumActive());
-            pool.returnObject(obj);
-            assertEquals(1, pool.getNumIdle());
-            assertEquals(0, pool.getNumActive());
-        } catch(final UnsupportedOperationException e) {
-            return; // skip this test if one of those calls is unsupported
-        } finally {
-            pool.close();
-        }
-    }
-
-    @Test
-    public void testBaseBorrow() {
-        try {
-            pool = makeEmptyPool(3);
-        } catch(final UnsupportedOperationException e) {
-            return; // skip this test if unsupported
-        }
-        assertEquals(getNthObject(0), pool.borrowObject());
-        assertEquals(getNthObject(1), pool.borrowObject());
-        assertEquals(getNthObject(2), pool.borrowObject());
-        pool.close();
-    }
-
-    @Test
-    public void testBaseBorrowReturn() {
-        try {
-            pool = makeEmptyPool(3);
-        } catch(final UnsupportedOperationException e) {
-            return; // skip this test if unsupported
-        }
-        String obj0 = pool.borrowObject();
-        assertEquals(getNthObject(0), obj0);
-        String obj1 = pool.borrowObject();
-        assertEquals(getNthObject(1), obj1);
-        String obj2 = pool.borrowObject();
-        assertEquals(getNthObject(2), obj2);
-        pool.returnObject(obj2);
-        obj2 = pool.borrowObject();
-        assertEquals(getNthObject(2), obj2);
-        pool.returnObject(obj1);
-        obj1 = pool.borrowObject();
-        assertEquals(getNthObject(1), obj1);
-        pool.returnObject(obj0);
-        pool.returnObject(obj2);
-        obj2 = pool.borrowObject();
-        if (isLifo()) {
-            assertEquals(getNthObject(2),obj2);
-        }
-        if (isFifo()) {
-            assertEquals(getNthObject(0),obj2);
-        }
-
-        obj0 = pool.borrowObject();
-        if (isLifo()) {
-            assertEquals(getNthObject(0),obj0);
-        }
-        if (isFifo()) {
-            assertEquals(getNthObject(2),obj0);
-        }
-        pool.close();
-    }
-
-    @Test
-    public void testBaseClear() {
-        try {
-            pool = makeEmptyPool(3);
-        } catch (final UnsupportedOperationException e) {
-            return; // skip this test if unsupported
-        }
-        assertEquals(0, pool.getNumActive());
-        assertEquals(0, pool.getNumIdle());
-        final String obj0 = pool.borrowObject();
-        final String obj1 = pool.borrowObject();
-        assertEquals(2, pool.getNumActive());
-        assertEquals(0, pool.getNumIdle());
-        pool.returnObject(obj1);
-        pool.returnObject(obj0);
-        assertEquals(0, pool.getNumActive());
-        assertEquals(2, pool.getNumIdle());
-        pool.clear();
-        assertEquals(0, pool.getNumActive());
-        assertEquals(0, pool.getNumIdle());
-        final Object obj2 = pool.borrowObject();
-        assertEquals(getNthObject(2), obj2);
-        pool.close();
-    }
-
-    @Test
-    public void testBaseClosePool() {
-        try {
-            pool = makeEmptyPool(3);
-        } catch(final UnsupportedOperationException e) {
-            return; // skip this test if unsupported
-        }
-        final String obj = pool.borrowObject();
-        pool.returnObject(obj);
-
-        pool.close();
-        assertThrows(IllegalStateException.class, pool::borrowObject);
-    }
-
-    @Test
-    public void testBaseInvalidateObject() {
-        try {
-            pool = makeEmptyPool(3);
-        } catch (final UnsupportedOperationException e) {
-            return; // skip this test if unsupported
-        }
-        assertEquals(0, pool.getNumActive());
-        assertEquals(0, pool.getNumIdle());
-        final String obj0 = pool.borrowObject();
-        final String obj1 = pool.borrowObject();
-        assertEquals(2, pool.getNumActive());
-        assertEquals(0, pool.getNumIdle());
-        pool.invalidateObject(obj0);
-        assertEquals(1, pool.getNumActive());
-        assertEquals(0, pool.getNumIdle());
-        pool.invalidateObject(obj1);
-        assertEquals(0, pool.getNumActive());
-        assertEquals(0, pool.getNumIdle());
-        pool.close();
-    }
-
-    @Test
-    public void testBaseNumActiveNumIdle() {
-        try {
-            pool = makeEmptyPool(3);
-        } catch (final UnsupportedOperationException e) {
-            return; // skip this test if unsupported
-        }
-        assertEquals(0, pool.getNumActive());
-        assertEquals(0, pool.getNumIdle());
-        final String obj0 = pool.borrowObject();
-        assertEquals(1, pool.getNumActive());
-        assertEquals(0, pool.getNumIdle());
-        final String obj1 = pool.borrowObject();
-        assertEquals(2, pool.getNumActive());
-        assertEquals(0, pool.getNumIdle());
-        pool.returnObject(obj1);
-        assertEquals(1, pool.getNumActive());
-        assertEquals(1, pool.getNumIdle());
-        pool.returnObject(obj0);
-        assertEquals(0, pool.getNumActive());
-        assertEquals(2, pool.getNumIdle());
-        pool.close();
-    }
-
-    @Test
-    public void testClose() {
-        @SuppressWarnings("resource")
-        final ObjectPool<Object, RuntimeException> pool = new TestObjectPool();
-
-        pool.close();
-        pool.close(); // should not error as of Pool 2.0.
-    }
-
-    // tests
-    @Test
-    public void testUnsupportedOperations() {
-        if (!getClass().equals(TestBaseObjectPool.class)) {
-            return; // skip redundant tests
-        }
-        try (final ObjectPool<Object,RuntimeException> pool = new TestObjectPool()) {
-
-            assertTrue( pool.getNumIdle() < 0,"Negative expected.");
-            assertTrue( pool.getNumActive() < 0,"Negative expected.");
-
-            assertThrows(UnsupportedOperationException.class, pool::clear);
-            assertThrows(UnsupportedOperationException.class, pool::addObject);
-        }
-    }
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.pool2;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ */
+public class TestBaseObjectPool extends TestObjectPool {
+    private static class TestObjectPool extends BaseObjectPool<Object, RuntimeException> {
+
+        @Override
+        public Object borrowObject() {
+            return null;
+        }
+
+        @Override
+        public void invalidateObject(final Object obj) {
+        }
+
+        @Override
+        public void returnObject(final Object obj) {
+        }
+    }
+
+    private ObjectPool<String, RuntimeException> pool;
+
+    /**
+     * @param n Ignored by this implemented. Used by sub-classes.
+     *
+     * @return the Nth object (zero indexed)
+     */
+    protected Object getNthObject(final int n) {
+        if (this.getClass() != TestBaseObjectPool.class) {
+            fail("Subclasses of TestBaseObjectPool must reimplement this method.");
+        }
+        throw new UnsupportedOperationException("BaseObjectPool isn't a complete implementation.");
+    }
+
+    protected boolean isFifo() {
+        if (this.getClass() != TestBaseObjectPool.class) {
+            fail("Subclasses of TestBaseObjectPool must reimplement this method.");
+        }
+        return false;
+    }
+
+    protected boolean isLifo() {
+        if (this.getClass() != TestBaseObjectPool.class) {
+            fail("Subclasses of TestBaseObjectPool must reimplement this method.");
+        }
+        return false;
+    }
+
+    /**
+     * @param minCapacity Ignored by this implemented. Used by sub-classes.
+     *
+     * @return A newly created empty pool
+     */
+    protected <E extends Exception> ObjectPool<String, E> makeEmptyPool(final int minCapacity) {
+        if (this.getClass() != TestBaseObjectPool.class) {
+            fail("Subclasses of TestBaseObjectPool must reimplement this method.");
+        }
+        throw new UnsupportedOperationException("BaseObjectPool isn't a complete implementation.");
+    }
+
+    @Override
+    protected <E extends Exception> ObjectPool<Object, E> makeEmptyPool(final PooledObjectFactory<Object, E> factory) {
+        if (this.getClass() != TestBaseObjectPool.class) {
+            fail("Subclasses of TestBaseObjectPool must reimplement this method.");
+        }
+        throw new UnsupportedOperationException("BaseObjectPool isn't a complete implementation.");
+    }
+
+    @Test
+    public void testBaseAddObject() {
+        try {
+            pool = makeEmptyPool(3);
+        } catch(final UnsupportedOperationException e) {
+            return; // skip this test if unsupported
+        }
+        try {
+            assertEquals(0, pool.getNumIdle());
+            assertEquals(0, pool.getNumActive());
+            pool.addObject();
+            assertEquals(1, pool.getNumIdle());
+            assertEquals(0, pool.getNumActive());
+            final String obj = pool.borrowObject();
+            assertEquals(getNthObject(0), obj);
+            assertEquals(0, pool.getNumIdle());
+            assertEquals(1, pool.getNumActive());
+            pool.returnObject(obj);
+            assertEquals(1, pool.getNumIdle());
+            assertEquals(0, pool.getNumActive());
+        } catch(final UnsupportedOperationException e) {
+            return; // skip this test if one of those calls is unsupported
+        } finally {
+            pool.close();
+        }
+    }
+
+    @Test
+    public void testBaseBorrow() {
+        try {
+            pool = makeEmptyPool(3);
+        } catch(final UnsupportedOperationException e) {
+            return; // skip this test if unsupported
+        }
+        assertEquals(getNthObject(0), pool.borrowObject());
+        assertEquals(getNthObject(1), pool.borrowObject());
+        assertEquals(getNthObject(2), pool.borrowObject());
+        pool.close();
+    }
+
+    @Test
+    public void testBaseBorrowReturn() {
+        try {
+            pool = makeEmptyPool(3);
+        } catch(final UnsupportedOperationException e) {
+            return; // skip this test if unsupported
+        }
+        String obj0 = pool.borrowObject();
+        assertEquals(getNthObject(0), obj0);
+        String obj1 = pool.borrowObject();
+        assertEquals(getNthObject(1), obj1);
+        String obj2 = pool.borrowObject();
+        assertEquals(getNthObject(2), obj2);
+        pool.returnObject(obj2);
+        obj2 = pool.borrowObject();
+        assertEquals(getNthObject(2), obj2);
+        pool.returnObject(obj1);
+        obj1 = pool.borrowObject();
+        assertEquals(getNthObject(1), obj1);
+        pool.returnObject(obj0);
+        pool.returnObject(obj2);
+        obj2 = pool.borrowObject();
+        if (isLifo()) {
+            assertEquals(getNthObject(2),obj2);
+        }
+        if (isFifo()) {
+            assertEquals(getNthObject(0),obj2);
+        }
+
+        obj0 = pool.borrowObject();
+        if (isLifo()) {
+            assertEquals(getNthObject(0),obj0);
+        }
+        if (isFifo()) {
+            assertEquals(getNthObject(2),obj0);
+        }
+        pool.close();
+    }
+
+    @Test
+    public void testBaseClear() {
+        try {
+            pool = makeEmptyPool(3);
+        } catch (final UnsupportedOperationException e) {
+            return; // skip this test if unsupported
+        }
+        assertEquals(0, pool.getNumActive());
+        assertEquals(0, pool.getNumIdle());
+        final String obj0 = pool.borrowObject();
+        final String obj1 = pool.borrowObject();
+        assertEquals(2, pool.getNumActive());
+        assertEquals(0, pool.getNumIdle());
+        pool.returnObject(obj1);
+        pool.returnObject(obj0);
+        assertEquals(0, pool.getNumActive());
+        assertEquals(2, pool.getNumIdle());
+        pool.clear();
+        assertEquals(0, pool.getNumActive());
+        assertEquals(0, pool.getNumIdle());
+        final Object obj2 = pool.borrowObject();
+        assertEquals(getNthObject(2), obj2);
+        pool.close();
+    }
+
+    @Test
+    public void testBaseClosePool() {
+        try {
+            pool = makeEmptyPool(3);
+        } catch(final UnsupportedOperationException e) {
+            return; // skip this test if unsupported
+        }
+        final String obj = pool.borrowObject();
+        pool.returnObject(obj);
+
+        pool.close();
+        assertThrows(IllegalStateException.class, pool::borrowObject);
+    }
+
+    @Test
+    public void testBaseInvalidateObject() {
+        try {
+            pool = makeEmptyPool(3);
+        } catch (final UnsupportedOperationException e) {
+            return; // skip this test if unsupported
+        }
+        assertEquals(0, pool.getNumActive());
+        assertEquals(0, pool.getNumIdle());
+        final String obj0 = pool.borrowObject();
+        final String obj1 = pool.borrowObject();
+        assertEquals(2, pool.getNumActive());
+        assertEquals(0, pool.getNumIdle());
+        pool.invalidateObject(obj0);
+        assertEquals(1, pool.getNumActive());
+        assertEquals(0, pool.getNumIdle());
+        pool.invalidateObject(obj1);
+        assertEquals(0, pool.getNumActive());
+        assertEquals(0, pool.getNumIdle());
+        pool.close();
+    }
+
+    @Test
+    public void testBaseNumActiveNumIdle() {
+        try {
+            pool = makeEmptyPool(3);
+        } catch (final UnsupportedOperationException e) {
+            return; // skip this test if unsupported
+        }
+        assertEquals(0, pool.getNumActive());
+        assertEquals(0, pool.getNumIdle());
+        final String obj0 = pool.borrowObject();
+        assertEquals(1, pool.getNumActive());
+        assertEquals(0, pool.getNumIdle());
+        final String obj1 = pool.borrowObject();
+        assertEquals(2, pool.getNumActive());
+        assertEquals(0, pool.getNumIdle());
+        pool.returnObject(obj1);
+        assertEquals(1, pool.getNumActive());
+        assertEquals(1, pool.getNumIdle());
+        pool.returnObject(obj0);
+        assertEquals(0, pool.getNumActive());
+        assertEquals(2, pool.getNumIdle());
+        pool.close();
+    }
+
+    @Test
+    public void testClose() {
+        @SuppressWarnings("resource")
+        final ObjectPool<Object, RuntimeException> pool = new TestObjectPool();
+
+        pool.close();
+        pool.close(); // should not error as of Pool 2.0.
+    }
+
+    // tests
+    @Test
+    public void testUnsupportedOperations() {
+        if (!getClass().equals(TestBaseObjectPool.class)) {
+            return; // skip redundant tests
+        }
+        try (final ObjectPool<Object,RuntimeException> pool = new TestObjectPool()) {
+
+            assertTrue( pool.getNumIdle() < 0,"Negative expected.");
+            assertTrue( pool.getNumActive() < 0,"Negative expected.");
+
+            assertThrows(UnsupportedOperationException.class, pool::clear);
+            assertThrows(UnsupportedOperationException.class, pool::addObject);
+        }
+    }
+}
diff --git a/src/test/java/org/apache/commons/pool2/TestKeyedObjectPool.java b/src/test/java/org/apache/commons/pool2/TestKeyedObjectPool.java
index 7f558fb5..aec7c0fb 100644
--- a/src/test/java/org/apache/commons/pool2/TestKeyedObjectPool.java
+++ b/src/test/java/org/apache/commons/pool2/TestKeyedObjectPool.java
@@ -1,770 +1,770 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.commons.pool2;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-import static org.junit.jupiter.api.Assertions.fail;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.NoSuchElementException;
-
-import org.apache.commons.pool2.impl.DefaultPooledObject;
-import org.apache.commons.pool2.impl.GenericKeyedObjectPool;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.Test;
-
-/**
- * Abstract test case for {@link ObjectPool} implementations.
- */
-public abstract class TestKeyedObjectPool {
-
-    protected static class FailingKeyedPooledObjectFactory implements KeyedPooledObjectFactory<Object, Object, PrivateException> {
-        private final List<MethodCall> methodCalls = new ArrayList<>();
-        private int count;
-        private boolean makeObjectFail;
-        private boolean activateObjectFail;
-        private boolean validateObjectFail;
-        private boolean passivateObjectFail;
-        private boolean destroyObjectFail;
-
-        public FailingKeyedPooledObjectFactory() {
-        }
-
-        @Override
-        public void activateObject(final Object key, final PooledObject<Object> obj) {
-            methodCalls.add(new MethodCall("activateObject", key, obj.getObject()));
-            if (activateObjectFail) {
-                throw new PrivateException("activateObject");
-            }
-        }
-
-        @Override
-        public void destroyObject(final Object key, final PooledObject<Object> obj) {
-            methodCalls.add(new MethodCall("destroyObject", key, obj.getObject()));
-            if (destroyObjectFail) {
-                throw new PrivateException("destroyObject");
-            }
-        }
-
-        public int getCurrentCount() {
-            return count;
-        }
-
-        public List<MethodCall> getMethodCalls() {
-            return methodCalls;
-        }
-
-        public boolean isActivateObjectFail() {
-            return activateObjectFail;
-        }
-
-        public boolean isDestroyObjectFail() {
-            return destroyObjectFail;
-        }
-
-        public boolean isMakeObjectFail() {
-            return makeObjectFail;
-        }
-
-        public boolean isPassivateObjectFail() {
-            return passivateObjectFail;
-        }
-
-        public boolean isValidateObjectFail() {
-            return validateObjectFail;
-        }
-
-        @Override
-        public PooledObject<Object> makeObject(final Object key) {
-            final MethodCall call = new MethodCall("makeObject", key);
-            methodCalls.add(call);
-            final int originalCount = this.count++;
-            if (makeObjectFail) {
-                throw new PrivateException("makeObject");
-            }
-            // Deliberate choice to create new object in case future unit test
-            // checks for a specific object
-            final Integer obj = Integer.valueOf(originalCount);
-            call.setReturned(obj);
-            return new DefaultPooledObject<>(obj);
-        }
-
-        @Override
-        public void passivateObject(final Object key, final PooledObject<Object> obj) {
-            methodCalls.add(new MethodCall("passivateObject", key, obj.getObject()));
-            if (passivateObjectFail) {
-                throw new PrivateException("passivateObject");
-            }
-        }
-
-        public void reset() {
-            count = 0;
-            getMethodCalls().clear();
-            setMakeObjectFail(false);
-            setActivateObjectFail(false);
-            setValidateObjectFail(false);
-            setPassivateObjectFail(false);
-            setDestroyObjectFail(false);
-        }
-
-        public void setActivateObjectFail(final boolean activateObjectFail) {
-            this.activateObjectFail = activateObjectFail;
-        }
-
-        public void setCurrentCount(final int count) {
-            this.count = count;
-        }
-
-        public void setDestroyObjectFail(final boolean destroyObjectFail) {
-            this.destroyObjectFail = destroyObjectFail;
-        }
-
-        public void setMakeObjectFail(final boolean makeObjectFail) {
-            this.makeObjectFail = makeObjectFail;
-        }
-
-        public void setPassivateObjectFail(final boolean passivateObjectFail) {
-            this.passivateObjectFail = passivateObjectFail;
-        }
-
-        public void setValidateObjectFail(final boolean validateObjectFail) {
-            this.validateObjectFail = validateObjectFail;
-        }
-
-        @Override
-        public boolean validateObject(final Object key, final PooledObject<Object> obj) {
-            final MethodCall call = new MethodCall("validateObject", key, obj.getObject());
-            methodCalls.add(call);
-            if (validateObjectFail) {
-                throw new PrivateException("validateObject");
-            }
-            final boolean r = true;
-            call.returned(Boolean.valueOf(r));
-            return r;
-        }
-    }
-
-    private static class TestFactory extends BaseKeyedPooledObjectFactory<Object, Object, RuntimeException> {
-        @Override
-        public Object create(final Object key) {
-            return new Object();
-        }
-        @Override
-        public PooledObject<Object> wrap(final Object value) {
-            return new DefaultPooledObject<>(value);
-        }
-    }
-
-    protected static final String KEY = "key";
-
-    private KeyedObjectPool<Object, Object, RuntimeException> pool;
-
-    // Deliberate choice to create a new object in case future unit tests check
-    // for a specific object.
-    private final Integer ZERO = Integer.valueOf(0);
-
-    private final Integer ONE = Integer.valueOf(1);
-
-    private void clear(final FailingKeyedPooledObjectFactory factory, final List<MethodCall> expectedMethods) {
-        factory.getMethodCalls().clear();
-        expectedMethods.clear();
-    }
-
-    /**
-     * Return what we expect to be the n<sup>th</sup>
-     * object (zero indexed) created by the pool
-     * for the given key.
-     * @param key Key for the object to be obtained
-     * @param n   index of the object to be obtained
-     *
-     * @return the requested object
-     */
-    protected abstract Object getNthObject(Object key, int n);
-
-    protected abstract boolean isFifo();
-
-    protected abstract boolean isLifo();
-
-    /**
-     * Creates an {@link KeyedObjectPool} instance
-     * that can contain at least <i>minCapacity</i>
-     * idle and active objects, or
-     * throw {@link IllegalArgumentException}
-     * if such a pool cannot be created.
-     * @param minCapacity Minimum capacity of the pool to create
-     *
-     * @return the newly created keyed object pool
-     */
-    protected abstract <E extends Exception> KeyedObjectPool<Object, Object, E> makeEmptyPool(int minCapacity);
-
-    /**
-     * Creates an {@code KeyedObjectPool} with the specified factory.
-     * The pool should be in a default configuration and conform to the expected
-     * behaviors described in {@link KeyedObjectPool}.
-     * Generally speaking there should be no limits on the various object counts.
-     *
-     * @param <E> The type of exception thrown by the pool 
-     * @param factory Factory to use to associate with the pool
-     * @return The newly created empty pool
-     */
-    protected abstract <E extends Exception> KeyedObjectPool<Object, Object, E> makeEmptyPool(KeyedPooledObjectFactory<Object, Object, E> factory);
-
-    protected abstract Object makeKey(int n);
-
-    private <E extends Exception> void reset(final KeyedObjectPool<Object, Object, E> pool, final FailingKeyedPooledObjectFactory factory,
-        final List<MethodCall> expectedMethods) throws E {
-        pool.clear();
-        clear(factory, expectedMethods);
-        factory.reset();
-    }
-
-    @AfterEach
-    public void tearDown() {
-        pool = null;
-    }
-
-    @Test
-    public void testBaseAddObject() {
-        try {
-            pool = makeEmptyPool(3);
-        } catch(final UnsupportedOperationException uoe) {
-            return; // skip this test if unsupported
-        }
-        final Object key = makeKey(0);
-        try {
-            assertEquals(0,pool.getNumIdle());
-            assertEquals(0,pool.getNumActive());
-            assertEquals(0,pool.getNumIdle(key));
-            assertEquals(0,pool.getNumActive(key));
-            pool.addObject(key);
-            assertEquals(1,pool.getNumIdle());
-            assertEquals(0,pool.getNumActive());
-            assertEquals(1,pool.getNumIdle(key));
-            assertEquals(0,pool.getNumActive(key));
-            final Object obj = pool.borrowObject(key);
-            assertEquals(getNthObject(key,0),obj);
-            assertEquals(0,pool.getNumIdle());
-            assertEquals(1,pool.getNumActive());
-            assertEquals(0,pool.getNumIdle(key));
-            assertEquals(1,pool.getNumActive(key));
-            pool.returnObject(key,obj);
-            assertEquals(1,pool.getNumIdle());
-            assertEquals(0,pool.getNumActive());
-            assertEquals(1,pool.getNumIdle(key));
-            assertEquals(0,pool.getNumActive(key));
-        } catch(final UnsupportedOperationException e) {
-            return; // skip this test if one of those calls is unsupported
-        } finally {
-            pool.close();
-        }
-    }
-
-    @Test
-    public void testBaseBorrow() {
-        try {
-            pool = makeEmptyPool(3);
-        } catch(final UnsupportedOperationException uoe) {
-            return; // skip this test if unsupported
-        }
-        final Object keya = makeKey(0);
-        final Object keyb = makeKey(1);
-        assertEquals(getNthObject(keya,0),pool.borrowObject(keya),"1");
-        assertEquals(getNthObject(keyb,0),pool.borrowObject(keyb),"2");
-        assertEquals(getNthObject(keyb,1),pool.borrowObject(keyb),"3");
-        assertEquals(getNthObject(keya,1),pool.borrowObject(keya),"4");
-        assertEquals(getNthObject(keyb,2),pool.borrowObject(keyb),"5");
-        assertEquals(getNthObject(keya,2),pool.borrowObject(keya),"6");
-        pool.close();
-    }
-
-    @Test
-    public void testBaseBorrowReturn() {
-        try {
-            pool = makeEmptyPool(3);
-        } catch(final UnsupportedOperationException uoe) {
-            return; // skip this test if unsupported
-        }
-        final Object keya = makeKey(0);
-        Object obj0 = pool.borrowObject(keya);
-        assertEquals(getNthObject(keya,0),obj0);
-        Object obj1 = pool.borrowObject(keya);
-        assertEquals(getNthObject(keya,1),obj1);
-        Object obj2 = pool.borrowObject(keya);
-        assertEquals(getNthObject(keya,2),obj2);
-        pool.returnObject(keya,obj2);
-        obj2 = pool.borrowObject(keya);
-        assertEquals(getNthObject(keya,2),obj2);
-        pool.returnObject(keya,obj1);
-        obj1 = pool.borrowObject(keya);
-        assertEquals(getNthObject(keya,1),obj1);
-        pool.returnObject(keya,obj0);
-        pool.returnObject(keya,obj2);
-        obj2 = pool.borrowObject(keya);
-        if (isLifo()) {
-            assertEquals(getNthObject(keya,2),obj2);
-        }
-        if (isFifo()) {
-            assertEquals(getNthObject(keya,0),obj2);
-        }
-        obj0 = pool.borrowObject(keya);
-        if (isLifo()) {
-            assertEquals(getNthObject(keya,0),obj0);
-        }
-        if (isFifo()) {
-            assertEquals(getNthObject(keya,2),obj0);
-        }
-        pool.close();
-    }
-
-    @Test
-    public void testBaseClear() {
-        try {
-            pool = makeEmptyPool(3);
-        } catch(final UnsupportedOperationException uoe) {
-            return; // skip this test if unsupported
-        }
-        final Object keya = makeKey(0);
-        assertEquals(0,pool.getNumActive(keya));
-        assertEquals(0,pool.getNumIdle(keya));
-        final Object obj0 = pool.borrowObject(keya);
-        final Object obj1 = pool.borrowObject(keya);
-        assertEquals(2,pool.getNumActive(keya));
-        assertEquals(0,pool.getNumIdle(keya));
-        pool.returnObject(keya,obj1);
-        pool.returnObject(keya,obj0);
-        assertEquals(0,pool.getNumActive(keya));
-        assertEquals(2,pool.getNumIdle(keya));
-        pool.clear(keya);
-        assertEquals(0,pool.getNumActive(keya));
-        assertEquals(0,pool.getNumIdle(keya));
-        final Object obj2 = pool.borrowObject(keya);
-        assertEquals(getNthObject(keya,2),obj2);
-        pool.close();
-    }
-
-    @Test
-    public void testBaseInvalidateObject() {
-        try {
-            pool = makeEmptyPool(3);
-        } catch(final UnsupportedOperationException uoe) {
-            return; // skip this test if unsupported
-        }
-        final Object keya = makeKey(0);
-        assertEquals(0,pool.getNumActive(keya));
-        assertEquals(0,pool.getNumIdle(keya));
-        final Object obj0 = pool.borrowObject(keya);
-        final Object obj1 = pool.borrowObject(keya);
-        assertEquals(2,pool.getNumActive(keya));
-        assertEquals(0,pool.getNumIdle(keya));
-        pool.invalidateObject(keya,obj0);
-        assertEquals(1,pool.getNumActive(keya));
-        assertEquals(0,pool.getNumIdle(keya));
-        pool.invalidateObject(keya,obj1);
-        assertEquals(0,pool.getNumActive(keya));
-        assertEquals(0,pool.getNumIdle(keya));
-        pool.close();
-    }
-
-    @Test
-    public void testBaseNumActiveNumIdle() {
-        try {
-            pool = makeEmptyPool(3);
-        } catch(final UnsupportedOperationException uoe) {
-            return; // skip this test if unsupported
-        }
-        final Object keya = makeKey(0);
-        assertEquals(0,pool.getNumActive(keya));
-        assertEquals(0,pool.getNumIdle(keya));
-        final Object obj0 = pool.borrowObject(keya);
-        assertEquals(1,pool.getNumActive(keya));
-        assertEquals(0,pool.getNumIdle(keya));
-        final Object obj1 = pool.borrowObject(keya);
-        assertEquals(2,pool.getNumActive(keya));
-        assertEquals(0,pool.getNumIdle(keya));
-        pool.returnObject(keya,obj1);
-        assertEquals(1,pool.getNumActive(keya));
-        assertEquals(1,pool.getNumIdle(keya));
-        pool.returnObject(keya,obj0);
-        assertEquals(0,pool.getNumActive(keya));
-        assertEquals(2,pool.getNumIdle(keya));
-
-        assertEquals(0,pool.getNumActive("xyzzy12345"));
-        assertEquals(0,pool.getNumIdle("xyzzy12345"));
-
-        pool.close();
-    }
-
-    @Test
-    public void testBaseNumActiveNumIdle2() {
-        try {
-            pool = makeEmptyPool(6);
-        } catch(final UnsupportedOperationException uoe) {
-            return; // skip this test if unsupported
-        }
-        final Object keya = makeKey(0);
-        final Object keyb = makeKey(1);
-        assertEquals(0,pool.getNumActive());
-        assertEquals(0,pool.getNumIdle());
-        assertEquals(0,pool.getNumActive(keya));
-        assertEquals(0,pool.getNumIdle(keya));
-        assertEquals(0,pool.getNumActive(keyb));
-        assertEquals(0,pool.getNumIdle(keyb));
-
-        final Object objA0 = pool.borrowObject(keya);
-        final Object objB0 = pool.borrowObject(keyb);
-
-        assertEquals(2,pool.getNumActive());
-        assertEquals(0,pool.getNumIdle());
-        assertEquals(1,pool.getNumActive(keya));
-        assertEquals(0,pool.getNumIdle(keya));
-        assertEquals(1,pool.getNumActive(keyb));
-        assertEquals(0,pool.getNumIdle(keyb));
-
-        final Object objA1 = pool.borrowObject(keya);
-        final Object objB1 = pool.borrowObject(keyb);
-
-        assertEquals(4,pool.getNumActive());
-        assertEquals(0,pool.getNumIdle());
-        assertEquals(2,pool.getNumActive(keya));
-        assertEquals(0,pool.getNumIdle(keya));
-        assertEquals(2,pool.getNumActive(keyb));
-        assertEquals(0,pool.getNumIdle(keyb));
-
-        pool.returnObject(keya,objA0);
-        pool.returnObject(keyb,objB0);
-
-        assertEquals(2,pool.getNumActive());
-        assertEquals(2,pool.getNumIdle());
-        assertEquals(1,pool.getNumActive(keya));
-        assertEquals(1,pool.getNumIdle(keya));
-        assertEquals(1,pool.getNumActive(keyb));
-        assertEquals(1,pool.getNumIdle(keyb));
-
-        pool.returnObject(keya,objA1);
-        pool.returnObject(keyb,objB1);
-
-        assertEquals(0,pool.getNumActive());
-        assertEquals(4,pool.getNumIdle());
-        assertEquals(0,pool.getNumActive(keya));
-        assertEquals(2,pool.getNumIdle(keya));
-        assertEquals(0,pool.getNumActive(keyb));
-        assertEquals(2,pool.getNumIdle(keyb));
-
-        pool.close();
-    }
-
-    @Test
-    public void testClosedPoolBehavior() {
-        final KeyedObjectPool<Object, Object, RuntimeException> pool;
-        try {
-            pool = makeEmptyPool(new TestFactory());
-        } catch(final UnsupportedOperationException uoe) {
-            return; // test not supported
-        }
-
-        final Object o1 = pool.borrowObject(KEY);
-        final Object o2 = pool.borrowObject(KEY);
-
-        pool.close();
-
-        assertThrows(IllegalStateException.class, () -> pool.addObject(KEY),
-                "A closed pool must throw an IllegalStateException when addObject is called.");
-
-        assertThrows(IllegalStateException.class, () -> pool.borrowObject(KEY),
-                "A closed pool must throw an IllegalStateException when borrowObject is called.");
-
-        // The following should not throw exceptions just because the pool is closed.
-        assertEquals( 0, pool.getNumIdle(KEY),"A closed pool shouldn't have any idle objects.");
-        assertEquals( 0, pool.getNumIdle(),"A closed pool shouldn't have any idle objects.");
-        pool.getNumActive();
-        pool.getNumActive(KEY);
-        pool.returnObject(KEY, o1);
-        assertEquals( 0, pool.getNumIdle(KEY),"returnObject should not add items back into the idle object pool for a closed pool.");
-        assertEquals( 0, pool.getNumIdle(),"returnObject should not add items back into the idle object pool for a closed pool.");
-        pool.invalidateObject(KEY, o2);
-        pool.clear(KEY);
-        pool.clear();
-        pool.close();
-    }
-
-    @Test
-    public void testKPOFAddObjectUsage() {
-        final FailingKeyedPooledObjectFactory factory = new FailingKeyedPooledObjectFactory();
-        final KeyedObjectPool<Object, Object, PrivateException> pool;
-        try {
-            pool = makeEmptyPool(factory);
-        } catch(final UnsupportedOperationException uoe) {
-            return; // test not supported
-        }
-        final List<MethodCall> expectedMethods = new ArrayList<>();
-
-        // addObject should make a new object, passivate it and put it in the pool
-        pool.addObject(KEY);
-        expectedMethods.add(new MethodCall("makeObject", KEY).returned(ZERO));
-        expectedMethods.add(new MethodCall("passivateObject", KEY, ZERO));
-        assertEquals(expectedMethods, factory.getMethodCalls());
-
-        // Test exception handling of addObject
-        reset(pool, factory, expectedMethods);
-
-        // makeObject Exceptions should be propagated to client code from addObject
-        factory.setMakeObjectFail(true);
-        assertThrows(PrivateException.class, () -> pool.addObject(KEY), "Expected addObject to propagate makeObject exception.");
-        expectedMethods.add(new MethodCall("makeObject", KEY));
-        assertEquals(expectedMethods, factory.getMethodCalls());
-
-        clear(factory, expectedMethods);
-
-        // passivateObject Exceptions should be propagated to client code from addObject
-        factory.setMakeObjectFail(false);
-        factory.setPassivateObjectFail(true);
-        assertThrows(PrivateException.class, () -> pool.addObject(KEY), "Expected addObject to propagate passivateObject exception.");
-        expectedMethods.add(new MethodCall("makeObject", KEY).returned(ONE));
-        expectedMethods.add(new MethodCall("passivateObject", KEY, ONE));
-        assertEquals(expectedMethods, factory.getMethodCalls());
-        pool.close();
-    }
-
-    @Test
-    public void testKPOFBorrowObjectUsages() {
-        final FailingKeyedPooledObjectFactory factory = new FailingKeyedPooledObjectFactory();
-        final KeyedObjectPool<Object, Object, PrivateException> pool;
-        try {
-            pool = makeEmptyPool(factory);
-        } catch(final UnsupportedOperationException uoe) {
-            return; // test not supported
-        }
-        final List<MethodCall> expectedMethods = new ArrayList<>();
-        Object obj;
-
-        if (pool instanceof GenericKeyedObjectPool) {
-            ((GenericKeyedObjectPool<Object, Object, PrivateException>) pool).setTestOnBorrow(true);
-        }
-
-        // Test correct behavior code paths
-
-        // existing idle object should be activated and validated
-        pool.addObject(KEY);
-        clear(factory, expectedMethods);
-        obj = pool.borrowObject(KEY);
-        expectedMethods.add(new MethodCall("activateObject", KEY, ZERO));
-        expectedMethods.add(new MethodCall("validateObject", KEY, ZERO).returned(Boolean.TRUE));
-        assertEquals(expectedMethods, factory.getMethodCalls());
-        pool.returnObject(KEY, obj);
-
-        // Test exception handling of borrowObject
-        reset(pool, factory, expectedMethods);
-
-        // makeObject Exceptions should be propagated to client code from borrowObject
-        factory.setMakeObjectFail(true);
-        try {
-            obj = pool.borrowObject(KEY);
-            fail("Expected borrowObject to propagate makeObject exception.");
-        } catch (final PrivateException pe) {
-            // expected
-        }
-        expectedMethods.add(new MethodCall("makeObject", KEY));
-        assertEquals(expectedMethods, factory.getMethodCalls());
-
-
-        // when activateObject fails in borrowObject, a new object should be borrowed/created
-        reset(pool, factory, expectedMethods);
-        pool.addObject(KEY);
-        clear(factory, expectedMethods);
-
-        factory.setActivateObjectFail(true);
-        expectedMethods.add(new MethodCall("activateObject", KEY, obj));
-        assertThrows(NoSuchElementException.class, () -> pool.borrowObject(KEY));
-        // After idle object fails validation, new on is created and activation
-        // fails again for the new one.
-        expectedMethods.add(new MethodCall("makeObject", KEY).returned(ONE));
-        expectedMethods.add(new MethodCall("activateObject", KEY, ONE));
-        TestObjectPool.removeDestroyObjectCall(factory.getMethodCalls()); // The exact timing of destroyObject is flexible here.
-        assertEquals(expectedMethods, factory.getMethodCalls());
-
-        // when validateObject fails in borrowObject, a new object should be borrowed/created
-        reset(pool, factory, expectedMethods);
-        pool.addObject(KEY);
-        clear(factory, expectedMethods);
-
-        factory.setValidateObjectFail(true);
-        // testOnBorrow is on, so this will throw when the newly created instance
-        // fails validation
-        assertThrows(NoSuchElementException.class, () -> pool.borrowObject(KEY));
-        // Activate, then validate for idle instance
-        expectedMethods.add(new MethodCall("activateObject", KEY, ZERO));
-        expectedMethods.add(new MethodCall("validateObject", KEY, ZERO));
-        // Make new instance, activate succeeds, validate fails
-        expectedMethods.add(new MethodCall("makeObject", KEY).returned(ONE));
-        expectedMethods.add(new MethodCall("activateObject", KEY, ONE));
-        expectedMethods.add(new MethodCall("validateObject", KEY, ONE));
-        TestObjectPool.removeDestroyObjectCall(factory.getMethodCalls());
-        assertEquals(expectedMethods, factory.getMethodCalls());
-        pool.close();
-    }
-
-    @Test
-    public void testKPOFClearUsages() {
-        final FailingKeyedPooledObjectFactory factory = new FailingKeyedPooledObjectFactory();
-        final KeyedObjectPool<Object, Object, PrivateException> pool;
-        try {
-            pool = makeEmptyPool(factory);
-        } catch(final UnsupportedOperationException uoe) {
-            return; // test not supported
-        }
-        final List<MethodCall> expectedMethods = new ArrayList<>();
-
-        // Test correct behavior code paths
-        pool.addObjects(KEY, 5);
-        pool.clear();
-
-        // Test exception handling clear should swallow destroy object failures
-        reset(pool, factory, expectedMethods);
-        factory.setDestroyObjectFail(true);
-        pool.addObjects(KEY, 5);
-        pool.clear();
-        pool.close();
-    }
-
-
-    @Test
-    public void testKPOFCloseUsages() {
-        final FailingKeyedPooledObjectFactory factory = new FailingKeyedPooledObjectFactory();
-        KeyedObjectPool<Object, Object, PrivateException> pool;
-        try {
-            pool = makeEmptyPool(factory);
-        } catch (final UnsupportedOperationException uoe) {
-            return; // test not supported
-        }
-        final List<MethodCall> expectedMethods = new ArrayList<>();
-
-        // Test correct behavior code paths
-        pool.addObjects(KEY, 5);
-        pool.close();
-
-        // Test exception handling close should swallow failures
-        try (final KeyedObjectPool<Object, Object, PrivateException> pool2 = makeEmptyPool(factory)) {
-            reset(pool2, factory, expectedMethods);
-            factory.setDestroyObjectFail(true);
-            pool2.addObjects(KEY, 5);
-        }
-    }
-
-    @Test
-    public void testKPOFInvalidateObjectUsages() throws InterruptedException {
-        final FailingKeyedPooledObjectFactory factory = new FailingKeyedPooledObjectFactory();
-        final KeyedObjectPool<Object, Object, PrivateException> pool;
-        try {
-            pool = makeEmptyPool(factory);
-        } catch(final UnsupportedOperationException uoe) {
-            return; // test not supported
-        }
-        final List<MethodCall> expectedMethods = new ArrayList<>();
-        Object obj;
-
-        // Test correct behavior code paths
-
-        obj = pool.borrowObject(KEY);
-        clear(factory, expectedMethods);
-
-        // invalidated object should be destroyed
-        pool.invalidateObject(KEY, obj);
-        expectedMethods.add(new MethodCall("destroyObject", KEY, obj));
-        assertEquals(expectedMethods, factory.getMethodCalls());
-
-        // Test exception handling of invalidateObject
-        reset(pool, factory, expectedMethods);
-        final Object obj2 = pool.borrowObject(KEY);
-        clear(factory, expectedMethods);
-        factory.setDestroyObjectFail(true);
-        assertThrows(PrivateException.class, () -> pool.invalidateObject(KEY, obj2), "Expecting destroy exception to propagate");
-        Thread.sleep(250); // could be defered
-        TestObjectPool.removeDestroyObjectCall(factory.getMethodCalls());
-        assertEquals(expectedMethods, factory.getMethodCalls());
-        pool.close();
-    }
-
-    @Test
-    public void testKPOFReturnObjectUsages() {
-        final FailingKeyedPooledObjectFactory factory = new FailingKeyedPooledObjectFactory();
-        final KeyedObjectPool<Object, Object, PrivateException> pool;
-        try {
-            pool = makeEmptyPool(factory);
-        } catch(final UnsupportedOperationException uoe) {
-            return; // test not supported
-        }
-        final List<MethodCall> expectedMethods = new ArrayList<>();
-        Object obj;
-
-        // Test correct behavior code paths
-        obj = pool.borrowObject(KEY);
-        clear(factory, expectedMethods);
-
-        // returned object should be passivated
-        pool.returnObject(KEY, obj);
-        expectedMethods.add(new MethodCall("passivateObject", KEY, obj));
-        assertEquals(expectedMethods, factory.getMethodCalls());
-
-        // Test exception handling of returnObject
-        reset(pool, factory, expectedMethods);
-
-        // passivateObject should swallow exceptions and not add the object to the pool
-        pool.addObject(KEY);
-        pool.addObject(KEY);
-        pool.addObject(KEY);
-        assertEquals(3, pool.getNumIdle(KEY));
-        obj = pool.borrowObject(KEY);
-        obj = pool.borrowObject(KEY);
-        assertEquals(1, pool.getNumIdle(KEY));
-        assertEquals(2, pool.getNumActive(KEY));
-        clear(factory, expectedMethods);
-        factory.setPassivateObjectFail(true);
-        pool.returnObject(KEY, obj);
-        expectedMethods.add(new MethodCall("passivateObject", KEY, obj));
-        TestObjectPool.removeDestroyObjectCall(factory.getMethodCalls()); // The exact timing of destroyObject is flexible here.
-        assertEquals(expectedMethods, factory.getMethodCalls());
-        assertEquals(1, pool.getNumIdle(KEY));   // Not added
-        assertEquals(1, pool.getNumActive(KEY)); // But not active
-
-        reset(pool, factory, expectedMethods);
-        obj = pool.borrowObject(KEY);
-        clear(factory, expectedMethods);
-        factory.setPassivateObjectFail(true);
-        factory.setDestroyObjectFail(true);
-        try {
-            pool.returnObject(KEY, obj);
-            if (!(pool instanceof GenericKeyedObjectPool)) { // ugh, 1.3-compat
-                fail("Expecting destroyObject exception to be propagated");
-            }
-        } catch (final PrivateException ex) {
-            // Expected
-        }
-        pool.close();
-    }
-
-    @Test
-    public void testToString() {
-        final FailingKeyedPooledObjectFactory factory = new FailingKeyedPooledObjectFactory();
-        try (final KeyedObjectPool<Object, Object, PrivateException> pool = makeEmptyPool(factory)) {
-            pool.toString();
-        } catch (final UnsupportedOperationException uoe) {
-            return; // test not supported
-        }
-    }
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.pool2;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.NoSuchElementException;
+
+import org.apache.commons.pool2.impl.DefaultPooledObject;
+import org.apache.commons.pool2.impl.GenericKeyedObjectPool;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Abstract test case for {@link ObjectPool} implementations.
+ */
+public abstract class TestKeyedObjectPool {
+
+    protected static class FailingKeyedPooledObjectFactory implements KeyedPooledObjectFactory<Object, Object, PrivateException> {
+        private final List<MethodCall> methodCalls = new ArrayList<>();
+        private int count;
+        private boolean makeObjectFail;
+        private boolean activateObjectFail;
+        private boolean validateObjectFail;
+        private boolean passivateObjectFail;
+        private boolean destroyObjectFail;
+
+        public FailingKeyedPooledObjectFactory() {
+        }
+
+        @Override
+        public void activateObject(final Object key, final PooledObject<Object> obj) {
+            methodCalls.add(new MethodCall("activateObject", key, obj.getObject()));
+            if (activateObjectFail) {
+                throw new PrivateException("activateObject");
+            }
+        }
+
+        @Override
+        public void destroyObject(final Object key, final PooledObject<Object> obj) {
+            methodCalls.add(new MethodCall("destroyObject", key, obj.getObject()));
+            if (destroyObjectFail) {
+                throw new PrivateException("destroyObject");
+            }
+        }
+
+        public int getCurrentCount() {
+            return count;
+        }
+
+        public List<MethodCall> getMethodCalls() {
+            return methodCalls;
+        }
+
+        public boolean isActivateObjectFail() {
+            return activateObjectFail;
+        }
+
+        public boolean isDestroyObjectFail() {
+            return destroyObjectFail;
+        }
+
+        public boolean isMakeObjectFail() {
+            return makeObjectFail;
+        }
+
+        public boolean isPassivateObjectFail() {
+            return passivateObjectFail;
+        }
+
+        public boolean isValidateObjectFail() {
+            return validateObjectFail;
+        }
+
+        @Override
+        public PooledObject<Object> makeObject(final Object key) {
+            final MethodCall call = new MethodCall("makeObject", key);
+            methodCalls.add(call);
+            final int originalCount = this.count++;
+            if (makeObjectFail) {
+                throw new PrivateException("makeObject");
+            }
+            // Deliberate choice to create new object in case future unit test
+            // checks for a specific object
+            final Integer obj = Integer.valueOf(originalCount);
+            call.setReturned(obj);
+            return new DefaultPooledObject<>(obj);
+        }
+
+        @Override
+        public void passivateObject(final Object key, final PooledObject<Object> obj) {
+            methodCalls.add(new MethodCall("passivateObject", key, obj.getObject()));
+            if (passivateObjectFail) {
+                throw new PrivateException("passivateObject");
+            }
+        }
+
+        public void reset() {
+            count = 0;
+            getMethodCalls().clear();
+            setMakeObjectFail(false);
+            setActivateObjectFail(false);
+            setValidateObjectFail(false);
+            setPassivateObjectFail(false);
+            setDestroyObjectFail(false);
+        }
+
+        public void setActivateObjectFail(final boolean activateObjectFail) {
+            this.activateObjectFail = activateObjectFail;
+        }
+
+        public void setCurrentCount(final int count) {
+            this.count = count;
+        }
+
+        public void setDestroyObjectFail(final boolean destroyObjectFail) {
+            this.destroyObjectFail = destroyObjectFail;
+        }
+
+        public void setMakeObjectFail(final boolean makeObjectFail) {
+            this.makeObjectFail = makeObjectFail;
+        }
+
+        public void setPassivateObjectFail(final boolean passivateObjectFail) {
+            this.passivateObjectFail = passivateObjectFail;
+        }
+
+        public void setValidateObjectFail(final boolean validateObjectFail) {
+            this.validateObjectFail = validateObjectFail;
+        }
+
+        @Override
+        public boolean validateObject(final Object key, final PooledObject<Object> obj) {
+            final MethodCall call = new MethodCall("validateObject", key, obj.getObject());
+            methodCalls.add(call);
+            if (validateObjectFail) {
+                throw new PrivateException("validateObject");
+            }
+            final boolean r = true;
+            call.returned(Boolean.valueOf(r));
+            return r;
+        }
+    }
+
+    private static class TestFactory extends BaseKeyedPooledObjectFactory<Object, Object, RuntimeException> {
+        @Override
+        public Object create(final Object key) {
+            return new Object();
+        }
+        @Override
+        public PooledObject<Object> wrap(final Object value) {
+            return new DefaultPooledObject<>(value);
+        }
+    }
+
+    protected static final String KEY = "key";
+
+    private KeyedObjectPool<Object, Object, RuntimeException> pool;
+
+    // Deliberate choice to create a new object in case future unit tests check
+    // for a specific object.
+    private final Integer ZERO = Integer.valueOf(0);
+
+    private final Integer ONE = Integer.valueOf(1);
+
+    private void clear(final FailingKeyedPooledObjectFactory factory, final List<MethodCall> expectedMethods) {
+        factory.getMethodCalls().clear();
+        expectedMethods.clear();
+    }
+
+    /**
+     * Return what we expect to be the n<sup>th</sup>
+     * object (zero indexed) created by the pool
+     * for the given key.
+     * @param key Key for the object to be obtained
+     * @param n   index of the object to be obtained
+     *
+     * @return the requested object
+     */
+    protected abstract Object getNthObject(Object key, int n);
+
+    protected abstract boolean isFifo();
+
+    protected abstract boolean isLifo();
+
+    /**
+     * Creates an {@link KeyedObjectPool} instance
+     * that can contain at least <i>minCapacity</i>
+     * idle and active objects, or
+     * throw {@link IllegalArgumentException}
+     * if such a pool cannot be created.
+     * @param minCapacity Minimum capacity of the pool to create
+     *
+     * @return the newly created keyed object pool
+     */
+    protected abstract <E extends Exception> KeyedObjectPool<Object, Object, E> makeEmptyPool(int minCapacity);
+
+    /**
+     * Creates an {@code KeyedObjectPool} with the specified factory.
+     * The pool should be in a default configuration and conform to the expected
+     * behaviors described in {@link KeyedObjectPool}.
+     * Generally speaking there should be no limits on the various object counts.
+     *
+     * @param <E> The type of exception thrown by the pool
+     * @param factory Factory to use to associate with the pool
+     * @return The newly created empty pool
+     */
+    protected abstract <E extends Exception> KeyedObjectPool<Object, Object, E> makeEmptyPool(KeyedPooledObjectFactory<Object, Object, E> factory);
+
+    protected abstract Object makeKey(int n);
+
+    private <E extends Exception> void reset(final KeyedObjectPool<Object, Object, E> pool, final FailingKeyedPooledObjectFactory factory,
+        final List<MethodCall> expectedMethods) throws E {
+        pool.clear();
+        clear(factory, expectedMethods);
+        factory.reset();
+    }
+
+    @AfterEach
+    public void tearDown() {
+        pool = null;
+    }
+
+    @Test
+    public void testBaseAddObject() {
+        try {
+            pool = makeEmptyPool(3);
+        } catch(final UnsupportedOperationException uoe) {
+            return; // skip this test if unsupported
+        }
+        final Object key = makeKey(0);
+        try {
+            assertEquals(0,pool.getNumIdle());
+            assertEquals(0,pool.getNumActive());
+            assertEquals(0,pool.getNumIdle(key));
+            assertEquals(0,pool.getNumActive(key));
+            pool.addObject(key);
+            assertEquals(1,pool.getNumIdle());
+            assertEquals(0,pool.getNumActive());
+            assertEquals(1,pool.getNumIdle(key));
+            assertEquals(0,pool.getNumActive(key));
+            final Object obj = pool.borrowObject(key);
+            assertEquals(getNthObject(key,0),obj);
+            assertEquals(0,pool.getNumIdle());
+            assertEquals(1,pool.getNumActive());
+            assertEquals(0,pool.getNumIdle(key));
+            assertEquals(1,pool.getNumActive(key));
+            pool.returnObject(key,obj);
+            assertEquals(1,pool.getNumIdle());
+            assertEquals(0,pool.getNumActive());
+            assertEquals(1,pool.getNumIdle(key));
+            assertEquals(0,pool.getNumActive(key));
+        } catch(final UnsupportedOperationException e) {
+            return; // skip this test if one of those calls is unsupported
+        } finally {
+            pool.close();
+        }
+    }
+
+    @Test
+    public void testBaseBorrow() {
+        try {
+            pool = makeEmptyPool(3);
+        } catch(final UnsupportedOperationException uoe) {
+            return; // skip this test if unsupported
+        }
+        final Object keya = makeKey(0);
+        final Object keyb = makeKey(1);
+        assertEquals(getNthObject(keya,0),pool.borrowObject(keya),"1");
+        assertEquals(getNthObject(keyb,0),pool.borrowObject(keyb),"2");
+        assertEquals(getNthObject(keyb,1),pool.borrowObject(keyb),"3");
+        assertEquals(getNthObject(keya,1),pool.borrowObject(keya),"4");
+        assertEquals(getNthObject(keyb,2),pool.borrowObject(keyb),"5");
+        assertEquals(getNthObject(keya,2),pool.borrowObject(keya),"6");
+        pool.close();
+    }
+
+    @Test
+    public void testBaseBorrowReturn() {
+        try {
+            pool = makeEmptyPool(3);
+        } catch(final UnsupportedOperationException uoe) {
+            return; // skip this test if unsupported
+        }
+        final Object keya = makeKey(0);
+        Object obj0 = pool.borrowObject(keya);
+        assertEquals(getNthObject(keya,0),obj0);
+        Object obj1 = pool.borrowObject(keya);
+        assertEquals(getNthObject(keya,1),obj1);
+        Object obj2 = pool.borrowObject(keya);
+        assertEquals(getNthObject(keya,2),obj2);
+        pool.returnObject(keya,obj2);
+        obj2 = pool.borrowObject(keya);
+        assertEquals(getNthObject(keya,2),obj2);
+        pool.returnObject(keya,obj1);
+        obj1 = pool.borrowObject(keya);
+        assertEquals(getNthObject(keya,1),obj1);
+        pool.returnObject(keya,obj0);
+        pool.returnObject(keya,obj2);
+        obj2 = pool.borrowObject(keya);
+        if (isLifo()) {
+            assertEquals(getNthObject(keya,2),obj2);
+        }
+        if (isFifo()) {
+            assertEquals(getNthObject(keya,0),obj2);
+        }
+        obj0 = pool.borrowObject(keya);
+        if (isLifo()) {
+            assertEquals(getNthObject(keya,0),obj0);
+        }
+        if (isFifo()) {
+            assertEquals(getNthObject(keya,2),obj0);
+        }
+        pool.close();
+    }
+
+    @Test
+    public void testBaseClear() {
+        try {
+            pool = makeEmptyPool(3);
+        } catch(final UnsupportedOperationException uoe) {
+            return; // skip this test if unsupported
+        }
+        final Object keya = makeKey(0);
+        assertEquals(0,pool.getNumActive(keya));
+        assertEquals(0,pool.getNumIdle(keya));
+        final Object obj0 = pool.borrowObject(keya);
+        final Object obj1 = pool.borrowObject(keya);
+        assertEquals(2,pool.getNumActive(keya));
+        assertEquals(0,pool.getNumIdle(keya));
+        pool.returnObject(keya,obj1);
+        pool.returnObject(keya,obj0);
+        assertEquals(0,pool.getNumActive(keya));
+        assertEquals(2,pool.getNumIdle(keya));
+        pool.clear(keya);
+        assertEquals(0,pool.getNumActive(keya));
+        assertEquals(0,pool.getNumIdle(keya));
+        final Object obj2 = pool.borrowObject(keya);
+        assertEquals(getNthObject(keya,2),obj2);
+        pool.close();
+    }
+
+    @Test
+    public void testBaseInvalidateObject() {
+        try {
+            pool = makeEmptyPool(3);
+        } catch(final UnsupportedOperationException uoe) {
+            return; // skip this test if unsupported
+        }
+        final Object keya = makeKey(0);
+        assertEquals(0,pool.getNumActive(keya));
+        assertEquals(0,pool.getNumIdle(keya));
+        final Object obj0 = pool.borrowObject(keya);
+        final Object obj1 = pool.borrowObject(keya);
+        assertEquals(2,pool.getNumActive(keya));
+        assertEquals(0,pool.getNumIdle(keya));
+        pool.invalidateObject(keya,obj0);
+        assertEquals(1,pool.getNumActive(keya));
+        assertEquals(0,pool.getNumIdle(keya));
+        pool.invalidateObject(keya,obj1);
+        assertEquals(0,pool.getNumActive(keya));
+        assertEquals(0,pool.getNumIdle(keya));
+        pool.close();
+    }
+
+    @Test
+    public void testBaseNumActiveNumIdle() {
+        try {
+            pool = makeEmptyPool(3);
+        } catch(final UnsupportedOperationException uoe) {
+            return; // skip this test if unsupported
+        }
+        final Object keya = makeKey(0);
+        assertEquals(0,pool.getNumActive(keya));
+        assertEquals(0,pool.getNumIdle(keya));
+        final Object obj0 = pool.borrowObject(keya);
+        assertEquals(1,pool.getNumActive(keya));
+        assertEquals(0,pool.getNumIdle(keya));
+        final Object obj1 = pool.borrowObject(keya);
+        assertEquals(2,pool.getNumActive(keya));
+        assertEquals(0,pool.getNumIdle(keya));
+        pool.returnObject(keya,obj1);
+        assertEquals(1,pool.getNumActive(keya));
+        assertEquals(1,pool.getNumIdle(keya));
+        pool.returnObject(keya,obj0);
+        assertEquals(0,pool.getNumActive(keya));
+        assertEquals(2,pool.getNumIdle(keya));
+
+        assertEquals(0,pool.getNumActive("xyzzy12345"));
+        assertEquals(0,pool.getNumIdle("xyzzy12345"));
+
+        pool.close();
+    }
+
+    @Test
+    public void testBaseNumActiveNumIdle2() {
+        try {
+            pool = makeEmptyPool(6);
+        } catch(final UnsupportedOperationException uoe) {
+            return; // skip this test if unsupported
+        }
+        final Object keya = makeKey(0);
+        final Object keyb = makeKey(1);
+        assertEquals(0,pool.getNumActive());
+        assertEquals(0,pool.getNumIdle());
+        assertEquals(0,pool.getNumActive(keya));
+        assertEquals(0,pool.getNumIdle(keya));
+        assertEquals(0,pool.getNumActive(keyb));
+        assertEquals(0,pool.getNumIdle(keyb));
+
+        final Object objA0 = pool.borrowObject(keya);
+        final Object objB0 = pool.borrowObject(keyb);
+
+        assertEquals(2,pool.getNumActive());
+        assertEquals(0,pool.getNumIdle());
+        assertEquals(1,pool.getNumActive(keya));
+        assertEquals(0,pool.getNumIdle(keya));
+        assertEquals(1,pool.getNumActive(keyb));
+        assertEquals(0,pool.getNumIdle(keyb));
+
+        final Object objA1 = pool.borrowObject(keya);
+        final Object objB1 = pool.borrowObject(keyb);
+
+        assertEquals(4,pool.getNumActive());
+        assertEquals(0,pool.getNumIdle());
+        assertEquals(2,pool.getNumActive(keya));
+        assertEquals(0,pool.getNumIdle(keya));
+        assertEquals(2,pool.getNumActive(keyb));
+        assertEquals(0,pool.getNumIdle(keyb));
+
+        pool.returnObject(keya,objA0);
+        pool.returnObject(keyb,objB0);
+
+        assertEquals(2,pool.getNumActive());
+        assertEquals(2,pool.getNumIdle());
+        assertEquals(1,pool.getNumActive(keya));
+        assertEquals(1,pool.getNumIdle(keya));
+        assertEquals(1,pool.getNumActive(keyb));
+        assertEquals(1,pool.getNumIdle(keyb));
+
+        pool.returnObject(keya,objA1);
+        pool.returnObject(keyb,objB1);
+
+        assertEquals(0,pool.getNumActive());
+        assertEquals(4,pool.getNumIdle());
+        assertEquals(0,pool.getNumActive(keya));
+        assertEquals(2,pool.getNumIdle(keya));
+        assertEquals(0,pool.getNumActive(keyb));
+        assertEquals(2,pool.getNumIdle(keyb));
+
+        pool.close();
+    }
+
+    @Test
+    public void testClosedPoolBehavior() {
+        final KeyedObjectPool<Object, Object, RuntimeException> pool;
+        try {
+            pool = makeEmptyPool(new TestFactory());
+        } catch(final UnsupportedOperationException uoe) {
+            return; // test not supported
+        }
+
+        final Object o1 = pool.borrowObject(KEY);
+        final Object o2 = pool.borrowObject(KEY);
+
+        pool.close();
+
+        assertThrows(IllegalStateException.class, () -> pool.addObject(KEY),
+                "A closed pool must throw an IllegalStateException when addObject is called.");
+
+        assertThrows(IllegalStateException.class, () -> pool.borrowObject(KEY),
+                "A closed pool must throw an IllegalStateException when borrowObject is called.");
+
+        // The following should not throw exceptions just because the pool is closed.
+        assertEquals( 0, pool.getNumIdle(KEY),"A closed pool shouldn't have any idle objects.");
+        assertEquals( 0, pool.getNumIdle(),"A closed pool shouldn't have any idle objects.");
+        pool.getNumActive();
+        pool.getNumActive(KEY);
+        pool.returnObject(KEY, o1);
+        assertEquals( 0, pool.getNumIdle(KEY),"returnObject should not add items back into the idle object pool for a closed pool.");
+        assertEquals( 0, pool.getNumIdle(),"returnObject should not add items back into the idle object pool for a closed pool.");
+        pool.invalidateObject(KEY, o2);
+        pool.clear(KEY);
+        pool.clear();
+        pool.close();
+    }
+
+    @Test
+    public void testKPOFAddObjectUsage() {
+        final FailingKeyedPooledObjectFactory factory = new FailingKeyedPooledObjectFactory();
+        final KeyedObjectPool<Object, Object, PrivateException> pool;
+        try {
+            pool = makeEmptyPool(factory);
+        } catch(final UnsupportedOperationException uoe) {
+            return; // test not supported
+        }
+        final List<MethodCall> expectedMethods = new ArrayList<>();
+
+        // addObject should make a new object, passivate it and put it in the pool
+        pool.addObject(KEY);
+        expectedMethods.add(new MethodCall("makeObject", KEY).returned(ZERO));
+        expectedMethods.add(new MethodCall("passivateObject", KEY, ZERO));
+        assertEquals(expectedMethods, factory.getMethodCalls());
+
+        // Test exception handling of addObject
+        reset(pool, factory, expectedMethods);
+
+        // makeObject Exceptions should be propagated to client code from addObject
+        factory.setMakeObjectFail(true);
+        assertThrows(PrivateException.class, () -> pool.addObject(KEY), "Expected addObject to propagate makeObject exception.");
+        expectedMethods.add(new MethodCall("makeObject", KEY));
+        assertEquals(expectedMethods, factory.getMethodCalls());
+
+        clear(factory, expectedMethods);
+
+        // passivateObject Exceptions should be propagated to client code from addObject
+        factory.setMakeObjectFail(false);
+        factory.setPassivateObjectFail(true);
+        assertThrows(PrivateException.class, () -> pool.addObject(KEY), "Expected addObject to propagate passivateObject exception.");
+        expectedMethods.add(new MethodCall("makeObject", KEY).returned(ONE));
+        expectedMethods.add(new MethodCall("passivateObject", KEY, ONE));
+        assertEquals(expectedMethods, factory.getMethodCalls());
+        pool.close();
+    }
+
+    @Test
+    public void testKPOFBorrowObjectUsages() {
+        final FailingKeyedPooledObjectFactory factory = new FailingKeyedPooledObjectFactory();
+        final KeyedObjectPool<Object, Object, PrivateException> pool;
+        try {
+            pool = makeEmptyPool(factory);
+        } catch(final UnsupportedOperationException uoe) {
+            return; // test not supported
+        }
+        final List<MethodCall> expectedMethods = new ArrayList<>();
+        Object obj;
+
+        if (pool instanceof GenericKeyedObjectPool) {
+            ((GenericKeyedObjectPool<Object, Object, PrivateException>) pool).setTestOnBorrow(true);
+        }
+
+        // Test correct behavior code paths
+
+        // existing idle object should be activated and validated
+        pool.addObject(KEY);
+        clear(factory, expectedMethods);
+        obj = pool.borrowObject(KEY);
+        expectedMethods.add(new MethodCall("activateObject", KEY, ZERO));
+        expectedMethods.add(new MethodCall("validateObject", KEY, ZERO).returned(Boolean.TRUE));
+        assertEquals(expectedMethods, factory.getMethodCalls());
+        pool.returnObject(KEY, obj);
+
+        // Test exception handling of borrowObject
+        reset(pool, factory, expectedMethods);
+
+        // makeObject Exceptions should be propagated to client code from borrowObject
+        factory.setMakeObjectFail(true);
+        try {
+            obj = pool.borrowObject(KEY);
+            fail("Expected borrowObject to propagate makeObject exception.");
+        } catch (final PrivateException pe) {
+            // expected
+        }
+        expectedMethods.add(new MethodCall("makeObject", KEY));
+        assertEquals(expectedMethods, factory.getMethodCalls());
+
+
+        // when activateObject fails in borrowObject, a new object should be borrowed/created
+        reset(pool, factory, expectedMethods);
+        pool.addObject(KEY);
+        clear(factory, expectedMethods);
+
+        factory.setActivateObjectFail(true);
+        expectedMethods.add(new MethodCall("activateObject", KEY, obj));
+        assertThrows(NoSuchElementException.class, () -> pool.borrowObject(KEY));
+        // After idle object fails validation, new on is created and activation
+        // fails again for the new one.
+        expectedMethods.add(new MethodCall("makeObject", KEY).returned(ONE));
+        expectedMethods.add(new MethodCall("activateObject", KEY, ONE));
+        TestObjectPool.removeDestroyObjectCall(factory.getMethodCalls()); // The exact timing of destroyObject is flexible here.
+        assertEquals(expectedMethods, factory.getMethodCalls());
+
+        // when validateObject fails in borrowObject, a new object should be borrowed/created
+        reset(pool, factory, expectedMethods);
+        pool.addObject(KEY);
+        clear(factory, expectedMethods);
+
+        factory.setValidateObjectFail(true);
+        // testOnBorrow is on, so this will throw when the newly created instance
+        // fails validation
+        assertThrows(NoSuchElementException.class, () -> pool.borrowObject(KEY));
+        // Activate, then validate for idle instance
+        expectedMethods.add(new MethodCall("activateObject", KEY, ZERO));
+        expectedMethods.add(new MethodCall("validateObject", KEY, ZERO));
+        // Make new instance, activate succeeds, validate fails
+        expectedMethods.add(new MethodCall("makeObject", KEY).returned(ONE));
+        expectedMethods.add(new MethodCall("activateObject", KEY, ONE));
+        expectedMethods.add(new MethodCall("validateObject", KEY, ONE));
+        TestObjectPool.removeDestroyObjectCall(factory.getMethodCalls());
+        assertEquals(expectedMethods, factory.getMethodCalls());
+        pool.close();
+    }
+
+    @Test
+    public void testKPOFClearUsages() {
+        final FailingKeyedPooledObjectFactory factory = new FailingKeyedPooledObjectFactory();
+        final KeyedObjectPool<Object, Object, PrivateException> pool;
+        try {
+            pool = makeEmptyPool(factory);
+        } catch(final UnsupportedOperationException uoe) {
+            return; // test not supported
+        }
+        final List<MethodCall> expectedMethods = new ArrayList<>();
+
+        // Test correct behavior code paths
+        pool.addObjects(KEY, 5);
+        pool.clear();
+
+        // Test exception handling clear should swallow destroy object failures
+        reset(pool, factory, expectedMethods);
+        factory.setDestroyObjectFail(true);
+        pool.addObjects(KEY, 5);
+        pool.clear();
+        pool.close();
+    }
+
+
+    @Test
+    public void testKPOFCloseUsages() {
+        final FailingKeyedPooledObjectFactory factory = new FailingKeyedPooledObjectFactory();
+        KeyedObjectPool<Object, Object, PrivateException> pool;
+        try {
+            pool = makeEmptyPool(factory);
+        } catch (final UnsupportedOperationException uoe) {
+            return; // test not supported
+        }
+        final List<MethodCall> expectedMethods = new ArrayList<>();
+
+        // Test correct behavior code paths
+        pool.addObjects(KEY, 5);
+        pool.close();
+
+        // Test exception handling close should swallow failures
+        try (final KeyedObjectPool<Object, Object, PrivateException> pool2 = makeEmptyPool(factory)) {
+            reset(pool2, factory, expectedMethods);
+            factory.setDestroyObjectFail(true);
+            pool2.addObjects(KEY, 5);
+        }
+    }
+
+    @Test
+    public void testKPOFInvalidateObjectUsages() throws InterruptedException {
+        final FailingKeyedPooledObjectFactory factory = new FailingKeyedPooledObjectFactory();
+        final KeyedObjectPool<Object, Object, PrivateException> pool;
+        try {
+            pool = makeEmptyPool(factory);
+        } catch(final UnsupportedOperationException uoe) {
+            return; // test not supported
+        }
+        final List<MethodCall> expectedMethods = new ArrayList<>();
+        Object obj;
+
+        // Test correct behavior code paths
+
+        obj = pool.borrowObject(KEY);
+        clear(factory, expectedMethods);
+
+        // invalidated object should be destroyed
+        pool.invalidateObject(KEY, obj);
+        expectedMethods.add(new MethodCall("destroyObject", KEY, obj));
+        assertEquals(expectedMethods, factory.getMethodCalls());
+
+        // Test exception handling of invalidateObject
+        reset(pool, factory, expectedMethods);
+        final Object obj2 = pool.borrowObject(KEY);
+        clear(factory, expectedMethods);
+        factory.setDestroyObjectFail(true);
+        assertThrows(PrivateException.class, () -> pool.invalidateObject(KEY, obj2), "Expecting destroy exception to propagate");
+        Thread.sleep(250); // could be defered
+        TestObjectPool.removeDestroyObjectCall(factory.getMethodCalls());
+        assertEquals(expectedMethods, factory.getMethodCalls());
+        pool.close();
+    }
+
+    @Test
+    public void testKPOFReturnObjectUsages() {
+        final FailingKeyedPooledObjectFactory factory = new FailingKeyedPooledObjectFactory();
+        final KeyedObjectPool<Object, Object, PrivateException> pool;
+        try {
+            pool = makeEmptyPool(factory);
+        } catch(final UnsupportedOperationException uoe) {
+            return; // test not supported
+        }
+        final List<MethodCall> expectedMethods = new ArrayList<>();
+        Object obj;
+
+        // Test correct behavior code paths
+        obj = pool.borrowObject(KEY);
+        clear(factory, expectedMethods);
+
+        // returned object should be passivated
+        pool.returnObject(KEY, obj);
+        expectedMethods.add(new MethodCall("passivateObject", KEY, obj));
+        assertEquals(expectedMethods, factory.getMethodCalls());
+
+        // Test exception handling of returnObject
+        reset(pool, factory, expectedMethods);
+
+        // passivateObject should swallow exceptions and not add the object to the pool
+        pool.addObject(KEY);
+        pool.addObject(KEY);
+        pool.addObject(KEY);
+        assertEquals(3, pool.getNumIdle(KEY));
+        obj = pool.borrowObject(KEY);
+        obj = pool.borrowObject(KEY);
+        assertEquals(1, pool.getNumIdle(KEY));
+        assertEquals(2, pool.getNumActive(KEY));
+        clear(factory, expectedMethods);
+        factory.setPassivateObjectFail(true);
+        pool.returnObject(KEY, obj);
+        expectedMethods.add(new MethodCall("passivateObject", KEY, obj));
+        TestObjectPool.removeDestroyObjectCall(factory.getMethodCalls()); // The exact timing of destroyObject is flexible here.
+        assertEquals(expectedMethods, factory.getMethodCalls());
+        assertEquals(1, pool.getNumIdle(KEY));   // Not added
+        assertEquals(1, pool.getNumActive(KEY)); // But not active
+
+        reset(pool, factory, expectedMethods);
+        obj = pool.borrowObject(KEY);
+        clear(factory, expectedMethods);
+        factory.setPassivateObjectFail(true);
+        factory.setDestroyObjectFail(true);
+        try {
+            pool.returnObject(KEY, obj);
+            if (!(pool instanceof GenericKeyedObjectPool)) { // ugh, 1.3-compat
+                fail("Expecting destroyObject exception to be propagated");
+            }
+        } catch (final PrivateException ex) {
+            // Expected
+        }
+        pool.close();
+    }
+
+    @Test
+    public void testToString() {
+        final FailingKeyedPooledObjectFactory factory = new FailingKeyedPooledObjectFactory();
+        try (final KeyedObjectPool<Object, Object, PrivateException> pool = makeEmptyPool(factory)) {
+            pool.toString();
+        } catch (final UnsupportedOperationException uoe) {
+            return; // test not supported
+        }
+    }
+}
diff --git a/src/test/java/org/apache/commons/pool2/WaiterFactory.java b/src/test/java/org/apache/commons/pool2/WaiterFactory.java
index 3a190391..014bf375 100644
--- a/src/test/java/org/apache/commons/pool2/WaiterFactory.java
+++ b/src/test/java/org/apache/commons/pool2/WaiterFactory.java
@@ -1,217 +1,217 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.commons.pool2;
-
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map;
-
-import org.apache.commons.pool2.impl.DefaultPooledObject;
-
-/**
- * Object factory with configurable latencies for object lifecycle methods.
- * This factory will also track and enforce maxActive, maxActivePerKey contracts.
- * If the factory's maxActive / maxActivePerKey are set to match those of the
- * pool, makeObject will throw IllegalStateException if the number of makes - destroys
- * (per key) exceeds the configured max.
- * 
- * @param <K> The type of keys managed by this factory.
- */
-public class WaiterFactory<K> implements PooledObjectFactory<Waiter, IllegalStateException>, KeyedPooledObjectFactory<K, Waiter, RuntimeException> {
-
-    /** Latency of activateObject */
-    private final long activateLatency;
-
-    /** Latency of destroyObject */
-    private final long destroyLatency;
-
-    /** Latency of makeObject */
-    private final long makeLatency;
-
-    /** Latency of passivateObject */
-    private final long passivateLatency;
-
-    /** Latency of validateObject */
-    private final long validateLatency;
-
-    /** Latency of doWait for Waiter instances created by this factory */
-    private final long waiterLatency;
-
-    /** Probability that passivation will invalidate Waiter instances */
-    private final double passivateInvalidationProbability;
-
-    /** Count of (makes - destroys) since last reset */
-    private long activeCount;
-
-    /** Count of (makes - destroys) per key since last reset */
-    private final Map<K,Integer> activeCounts = new HashMap<>();
-
-    /** Maximum of (makes - destroys) - if exceeded IllegalStateException */
-    private final long maxActive;  // GKOP 1.x calls this maxTotal
-
-    /** Maximum of (makes - destroys) per key */
-    private final long maxActivePerKey;  // GKOP 1.x calls this maxActive
-
-    public WaiterFactory(final long activateLatency, final long destroyLatency,
-            final long makeLatency, final long passivateLatency, final long validateLatency,
-            final long waiterLatency) {
-        this(activateLatency, destroyLatency, makeLatency, passivateLatency,
-                validateLatency, waiterLatency, Long.MAX_VALUE, Long.MAX_VALUE, 0);
-    }
-
-    public WaiterFactory(final long activateLatency, final long destroyLatency,
-            final long makeLatency, final long passivateLatency, final long validateLatency,
-            final long waiterLatency,final long maxActive) {
-        this(activateLatency, destroyLatency, makeLatency, passivateLatency,
-                validateLatency, waiterLatency, maxActive, Long.MAX_VALUE, 0);
-    }
-
-    public WaiterFactory(final long activateLatency, final long destroyLatency,
-            final long makeLatency, final long passivateLatency, final long validateLatency,
-            final long waiterLatency,final long maxActive, final long maxActivePerKey,
-            final double passivateInvalidationProbability) {
-        this.activateLatency = activateLatency;
-        this.destroyLatency = destroyLatency;
-        this.makeLatency = makeLatency;
-        this.passivateLatency = passivateLatency;
-        this.validateLatency = validateLatency;
-        this.waiterLatency = waiterLatency;
-        this.maxActive = maxActive;
-        this.maxActivePerKey = maxActivePerKey;
-        this.passivateInvalidationProbability = passivateInvalidationProbability;
-    }
-
-    @Override
-    public void activateObject(final K key, final PooledObject<Waiter> obj) {
-        activateObject(obj);
-    }
-
-    @Override
-    public void activateObject(final PooledObject<Waiter> obj) {
-        doWait(activateLatency);
-        obj.getObject().setActive(true);
-    }
-
-    @Override
-    public void destroyObject(final K key,final PooledObject<Waiter> obj) {
-        destroyObject(obj);
-        synchronized (this) {
-            final Integer count = activeCounts.get(key);
-            activeCounts.put(key, Integer.valueOf(count.intValue() - 1));
-        }
-    }
-
-    @Override
-    public void destroyObject(final PooledObject<Waiter> obj) {
-        doWait(destroyLatency);
-        obj.getObject().setValid(false);
-        obj.getObject().setActive(false);
-        // Decrement *after* destroy
-        synchronized (this) {
-            activeCount--;
-        }
-    }
-
-    protected void doWait(final long latency) {
-        if (latency == 0) {
-            return;
-        }
-        Waiter.sleepQuietly(latency);
-    }
-
-    /**
-     * @return the maxActive
-     */
-    public synchronized long getMaxActive() {
-        return maxActive;
-    }
-
-    @Override
-    public PooledObject<Waiter> makeObject() {
-        // Increment and test *before* make
-        synchronized (this) {
-            if (activeCount >= maxActive) {
-                throw new IllegalStateException("Too many active instances: " +
-                activeCount + " in circulation with maxActive = " + maxActive);
-            }
-            activeCount++;
-        }
-        doWait(makeLatency);
-        return new DefaultPooledObject<>(new Waiter(false, true, waiterLatency));
-    }
-
-    @Override
-    public PooledObject<Waiter> makeObject(final K key) {
-        synchronized (this) {
-            Integer count = activeCounts.get(key);
-            if (count == null) {
-                count = Integer.valueOf(1);
-                activeCounts.put(key, count);
-            } else {
-                if (count.intValue() >= maxActivePerKey) {
-                    throw new IllegalStateException("Too many active " +
-                    "instances for key = " + key + ": " + count.intValue() +
-                    " in circulation " + "with maxActivePerKey = " +
-                    maxActivePerKey);
-                }
-                activeCounts.put(key, Integer.valueOf(count.intValue() + 1));
-            }
-        }
-        return makeObject();
-    }
-
-    // KeyedPoolableObjectFactory methods
-
-    @Override
-    public void passivateObject(final K key, final PooledObject<Waiter> obj) {
-        passivateObject(obj);
-    }
-
-    @Override
-    public void passivateObject(final PooledObject<Waiter> obj) {
-        obj.getObject().setActive(false);
-        doWait(passivateLatency);
-        if (Math.random() < passivateInvalidationProbability) {
-            obj.getObject().setValid(false);
-        }
-    }
-
-    public synchronized void reset() {
-        activeCount = 0;
-        if (activeCounts.isEmpty()) {
-            return;
-        }
-        final Iterator<K> it = activeCounts.keySet().iterator();
-        while (it.hasNext()) {
-            final K key = it.next();
-            activeCounts.put(key, Integer.valueOf(0));
-        }
-    }
-
-    @Override
-    public boolean validateObject(final K key, final PooledObject<Waiter> obj) {
-        return validateObject(obj);
-    }
-
-    @Override
-    public boolean validateObject(final PooledObject<Waiter> obj) {
-        doWait(validateLatency);
-        return obj.getObject().isValid();
-    }
-
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.pool2;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.apache.commons.pool2.impl.DefaultPooledObject;
+
+/**
+ * Object factory with configurable latencies for object lifecycle methods.
+ * This factory will also track and enforce maxActive, maxActivePerKey contracts.
+ * If the factory's maxActive / maxActivePerKey are set to match those of the
+ * pool, makeObject will throw IllegalStateException if the number of makes - destroys
+ * (per key) exceeds the configured max.
+ *
+ * @param <K> The type of keys managed by this factory.
+ */
+public class WaiterFactory<K> implements PooledObjectFactory<Waiter, IllegalStateException>, KeyedPooledObjectFactory<K, Waiter, RuntimeException> {
+
+    /** Latency of activateObject */
+    private final long activateLatency;
+
+    /** Latency of destroyObject */
+    private final long destroyLatency;
+
+    /** Latency of makeObject */
+    private final long makeLatency;
+
+    /** Latency of passivateObject */
+    private final long passivateLatency;
+
+    /** Latency of validateObject */
+    private final long validateLatency;
+
+    /** Latency of doWait for Waiter instances created by this factory */
+    private final long waiterLatency;
+
+    /** Probability that passivation will invalidate Waiter instances */
+    private final double passivateInvalidationProbability;
+
+    /** Count of (makes - destroys) since last reset */
+    private long activeCount;
+
+    /** Count of (makes - destroys) per key since last reset */
+    private final Map<K,Integer> activeCounts = new HashMap<>();
+
+    /** Maximum of (makes - destroys) - if exceeded IllegalStateException */
+    private final long maxActive;  // GKOP 1.x calls this maxTotal
+
+    /** Maximum of (makes - destroys) per key */
+    private final long maxActivePerKey;  // GKOP 1.x calls this maxActive
+
+    public WaiterFactory(final long activateLatency, final long destroyLatency,
+            final long makeLatency, final long passivateLatency, final long validateLatency,
+            final long waiterLatency) {
+        this(activateLatency, destroyLatency, makeLatency, passivateLatency,
+                validateLatency, waiterLatency, Long.MAX_VALUE, Long.MAX_VALUE, 0);
+    }
+
+    public WaiterFactory(final long activateLatency, final long destroyLatency,
+            final long makeLatency, final long passivateLatency, final long validateLatency,
+            final long waiterLatency,final long maxActive) {
+        this(activateLatency, destroyLatency, makeLatency, passivateLatency,
+                validateLatency, waiterLatency, maxActive, Long.MAX_VALUE, 0);
+    }
+
+    public WaiterFactory(final long activateLatency, final long destroyLatency,
+            final long makeLatency, final long passivateLatency, final long validateLatency,
+            final long waiterLatency,final long maxActive, final long maxActivePerKey,
+            final double passivateInvalidationProbability) {
+        this.activateLatency = activateLatency;
+        this.destroyLatency = destroyLatency;
+        this.makeLatency = makeLatency;
+        this.passivateLatency = passivateLatency;
+        this.validateLatency = validateLatency;
+        this.waiterLatency = waiterLatency;
+        this.maxActive = maxActive;
+        this.maxActivePerKey = maxActivePerKey;
+        this.passivateInvalidationProbability = passivateInvalidationProbability;
+    }
+
+    @Override
+    public void activateObject(final K key, final PooledObject<Waiter> obj) {
+        activateObject(obj);
+    }
+
+    @Override
+    public void activateObject(final PooledObject<Waiter> obj) {
+        doWait(activateLatency);
+        obj.getObject().setActive(true);
+    }
+
+    @Override
+    public void destroyObject(final K key,final PooledObject<Waiter> obj) {
+        destroyObject(obj);
+        synchronized (this) {
+            final Integer count = activeCounts.get(key);
+            activeCounts.put(key, Integer.valueOf(count.intValue() - 1));
+        }
+    }
+
+    @Override
+    public void destroyObject(final PooledObject<Waiter> obj) {
+        doWait(destroyLatency);
+        obj.getObject().setValid(false);
+        obj.getObject().setActive(false);
+        // Decrement *after* destroy
+        synchronized (this) {
+            activeCount--;
+        }
+    }
+
+    protected void doWait(final long latency) {
+        if (latency == 0) {
+            return;
+        }
+        Waiter.sleepQuietly(latency);
+    }
+
+    /**
+     * @return the maxActive
+     */
+    public synchronized long getMaxActive() {
+        return maxActive;
+    }
+
+    @Override
+    public PooledObject<Waiter> makeObject() {
+        // Increment and test *before* make
+        synchronized (this) {
+            if (activeCount >= maxActive) {
+                throw new IllegalStateException("Too many active instances: " +
+                activeCount + " in circulation with maxActive = " + maxActive);
+            }
+            activeCount++;
+        }
+        doWait(makeLatency);
+        return new DefaultPooledObject<>(new Waiter(false, true, waiterLatency));
+    }
+
+    @Override
+    public PooledObject<Waiter> makeObject(final K key) {
+        synchronized (this) {
+            Integer count = activeCounts.get(key);
+            if (count == null) {
+                count = Integer.valueOf(1);
+                activeCounts.put(key, count);
+            } else {
+                if (count.intValue() >= maxActivePerKey) {
+                    throw new IllegalStateException("Too many active " +
+                    "instances for key = " + key + ": " + count.intValue() +
+                    " in circulation " + "with maxActivePerKey = " +
+                    maxActivePerKey);
+                }
+                activeCounts.put(key, Integer.valueOf(count.intValue() + 1));
+            }
+        }
+        return makeObject();
+    }
+
+    // KeyedPoolableObjectFactory methods
+
+    @Override
+    public void passivateObject(final K key, final PooledObject<Waiter> obj) {
+        passivateObject(obj);
+    }
+
+    @Override
+    public void passivateObject(final PooledObject<Waiter> obj) {
+        obj.getObject().setActive(false);
+        doWait(passivateLatency);
+        if (Math.random() < passivateInvalidationProbability) {
+            obj.getObject().setValid(false);
+        }
+    }
+
+    public synchronized void reset() {
+        activeCount = 0;
+        if (activeCounts.isEmpty()) {
+            return;
+        }
+        final Iterator<K> it = activeCounts.keySet().iterator();
+        while (it.hasNext()) {
+            final K key = it.next();
+            activeCounts.put(key, Integer.valueOf(0));
+        }
+    }
+
+    @Override
+    public boolean validateObject(final K key, final PooledObject<Waiter> obj) {
+        return validateObject(obj);
+    }
+
+    @Override
+    public boolean validateObject(final PooledObject<Waiter> obj) {
+        doWait(validateLatency);
+        return obj.getObject().isValid();
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/pool2/impl/TestGenericKeyedObjectPool.java b/src/test/java/org/apache/commons/pool2/impl/TestGenericKeyedObjectPool.java
index 071cabb1..548626b5 100644
--- a/src/test/java/org/apache/commons/pool2/impl/TestGenericKeyedObjectPool.java
+++ b/src/test/java/org/apache/commons/pool2/impl/TestGenericKeyedObjectPool.java
@@ -126,11 +126,11 @@ public class TestGenericKeyedObjectPool extends TestKeyedObjectPool {
             this.pool = pool;
             this.key = key;
         }
-        
+
         public boolean complete() {
             return done;
         }
-        
+
         @Override
         public void run() {
             try {
@@ -226,55 +226,55 @@ public class TestGenericKeyedObjectPool extends TestKeyedObjectPool {
             }
             return new DefaultPooledObject<>(out);
         }
-        
+
         @Override
         public void passivateObject(final K key, final PooledObject<String> obj) throws TestException {
             if (exceptionOnPassivate) {
                 throw new TestException();
             }
         }
-        
+
         public void setDestroyLatency(final long destroyLatency) {
             this.destroyLatency = destroyLatency;
         }
-        
+
         void setEvenValid(final boolean valid) {
             evenValid = valid;
         }
-        
+
         public void setMakeLatency(final long makeLatency) {
             this.makeLatency = makeLatency;
         }
-        
+
         public void setMaxTotalPerKey(final int maxTotalPerKey) {
             this.maxTotalPerKey = maxTotalPerKey;
         }
-        
+
         public void setThrowExceptionOnActivate(final boolean b) {
             exceptionOnActivate = b;
         }
-        
+
         public void setThrowExceptionOnDestroy(final boolean b) {
             exceptionOnDestroy = b;
         }
-        
+
         public void setThrowExceptionOnPassivate(final boolean b) {
             exceptionOnPassivate = b;
         }
-        
+
         public void setThrowExceptionOnValidate(final boolean b) {
             exceptionOnValidate = b;
         }
-        
+
         void setValid(final boolean valid) {
             evenValid = valid;
             oddValid = valid;
         }
-        
+
         public void setValidateLatency(final long validateLatency) {
             this.validateLatency = validateLatency;
         }
-        
+
         public void setValidationEnabled(final boolean b) {
             enableValidation = b;
         }
@@ -291,7 +291,7 @@ public class TestGenericKeyedObjectPool extends TestKeyedObjectPool {
             return valid;
         }
     }
-    
+
     private static class SimplePerKeyFactory extends BaseKeyedPooledObjectFactory<Object, Object, RuntimeException> {
         final ConcurrentHashMap<Object, AtomicInteger> map = new ConcurrentHashMap<>();
 
@@ -496,7 +496,7 @@ public class TestGenericKeyedObjectPool extends TestKeyedObjectPool {
     private static final Integer KEY_TWO = Integer.valueOf(2);
 
     private static final boolean DISPLAY_THREAD_DETAILS=
-        Boolean.parseBoolean(System.getProperty("TestGenericKeyedObjectPool.display.thread.details", "false"));
+    Boolean.getBoolean("TestGenericKeyedObjectPool.display.thread.details");
     // To pass this to a Maven test, use:
     // mvn test -DargLine="-DTestGenericKeyedObjectPool.display.thread.details=true"
     // @see https://issues.apache.org/jira/browse/SUREFIRE-121
@@ -684,7 +684,7 @@ public class TestGenericKeyedObjectPool extends TestKeyedObjectPool {
             // FIFO - 24, 25, 26
             for (int i = 0; i < 8; i++) {
                 final VisitTracker<Integer> tracker = intPool.borrowObject(KEY_ONE);
-                if ((lifo && tracker.getId() > 1) || (!lifo && tracker.getId() > 2)) {
+                if (lifo && tracker.getId() > 1 || !lifo && tracker.getId() > 2) {
                     assertEquals( 1,
                             tracker.getValidateCount(),"Instance " + tracker.getId() + " visited wrong number of times.");
                 } else {
@@ -738,7 +738,7 @@ public class TestGenericKeyedObjectPool extends TestKeyedObjectPool {
                     final int totalInstances = zeroLength + oneLength + twoLength;
 
                     // Number of times evictor should have cycled through pools
-                    final int cycleCount = (runs * intPool.getNumTestsPerEvictionRun()) / totalInstances;
+                    final int cycleCount = runs * intPool.getNumTestsPerEvictionRun() / totalInstances;
 
                     // Look at elements and make sure they are visited cycleCount
                     // or cycleCount + 1 times
@@ -861,7 +861,7 @@ public class TestGenericKeyedObjectPool extends TestKeyedObjectPool {
             t.start();
         }
         for (final TestThread<T, E> testThread : threads) {
-            while (!(testThread.complete())) {
+            while (!testThread.complete()) {
                 Waiter.sleepQuietly(500L);
             }
             if (testThread.failed()) {
@@ -909,12 +909,12 @@ public class TestGenericKeyedObjectPool extends TestKeyedObjectPool {
     @Test
     public void testAppendStats() {
         assertFalse(gkoPool.getMessageStatistics());
-        assertEquals("foo", (gkoPool.appendStats("foo")));
+        assertEquals("foo", gkoPool.appendStats("foo"));
         try (final GenericKeyedObjectPool<?, ?, TestException> pool = new GenericKeyedObjectPool<>(new SimpleFactory<>())) {
             pool.setMessagesStatistics(true);
-            assertNotEquals("foo", (pool.appendStats("foo")));
+            assertNotEquals("foo", pool.appendStats("foo"));
             pool.setMessagesStatistics(false);
-            assertEquals("foo", (pool.appendStats("foo")));
+            assertEquals("foo", pool.appendStats("foo"));
         }
     }
 
@@ -929,7 +929,7 @@ public class TestGenericKeyedObjectPool extends TestKeyedObjectPool {
         final long startMillis = System.currentTimeMillis();
         // Needs to be in a separate thread as this will block
         final Runnable simple = new SimpleTestThread<>(gkoPool, "one");
-        (new Thread(simple)).start();
+        new Thread(simple).start();
         // This should be almost instant. If it isn't it means this thread got
         // stuck behind the thread created above which is bad.
         // Give other thread a chance to start
@@ -940,7 +940,7 @@ public class TestGenericKeyedObjectPool extends TestKeyedObjectPool {
         // If it passes it should be almost instant
         // Use 3000ms as the threshold - should avoid timing issues on most
         // (all? platforms)
-        assertTrue((endMillis - startMillis) < 4000,
+        assertTrue(endMillis - startMillis < 4000,
                 "Elapsed time: " + (endMillis - startMillis) + " should be less than 4000");
 
     }
@@ -995,7 +995,7 @@ public class TestGenericKeyedObjectPool extends TestKeyedObjectPool {
 
         // Wait for threads to finish
         for (int i = 0; i < numThreads; i++) {
-            while (!(threads[i]).complete()) {
+            while (!threads[i].complete()) {
                 Waiter.sleepQuietly(500L);
             }
             if (threads[i].failed()) {
@@ -1099,7 +1099,7 @@ public class TestGenericKeyedObjectPool extends TestKeyedObjectPool {
           final String six2 = gkoPool.borrowObject("six");
           Thread.sleep(100);
           // Launch the waiters - all will be blocked waiting
-          for (Thread t : testThreads) {
+          for (final Thread t : testThreads) {
               t.start();
           }
           Thread.sleep(100);
@@ -1120,7 +1120,7 @@ public class TestGenericKeyedObjectPool extends TestKeyedObjectPool {
           gkoPool.returnObject("six", six2);
           gkoPool.clear("six");
           Thread.sleep(20);
-          for (Thread t : testThreads) {
+          for (final Thread t : testThreads) {
               assertFalse(t.isAlive());
           }
       }
@@ -1139,52 +1139,52 @@ public class TestGenericKeyedObjectPool extends TestKeyedObjectPool {
         config.setMaxIdlePerKey(-1);
         config.setMaxTotal(-1);
         config.setMaxWait(Duration.ofMillis(5));
-        GenericKeyedObjectPool<Integer, Integer, InterruptedException> testPool = new GenericKeyedObjectPool<>(
+        final GenericKeyedObjectPool<Integer, Integer, InterruptedException> testPool = new GenericKeyedObjectPool<>(
                 new KeyedPooledObjectFactory<Integer, Integer, InterruptedException>() {
                     @Override
-                    public void activateObject(Integer key, PooledObject<Integer> p) {
+                    public void activateObject(final Integer key, final PooledObject<Integer> p) {
                         // do nothing
                     }
 
                     @Override
-                    public void destroyObject(Integer key, PooledObject<Integer> p) throws InterruptedException {
+                    public void destroyObject(final Integer key, final PooledObject<Integer> p) throws InterruptedException {
                         Thread.sleep(500);
                     }
 
                     @Override
-                    public PooledObject<Integer> makeObject(Integer key) {
+                    public PooledObject<Integer> makeObject(final Integer key) {
                         return new DefaultPooledObject<>(10);
                     }
 
                     @Override
-                    public void passivateObject(Integer key, PooledObject<Integer> p) {
+                    public void passivateObject(final Integer key, final PooledObject<Integer> p) {
                         // do nothing
                     }
 
                     @Override
-                    public boolean validateObject(Integer key, PooledObject<Integer> p) {
+                    public boolean validateObject(final Integer key, final PooledObject<Integer> p) {
                         return true;
                     }
                 }, config);
         final int borrowKey = 10;
-        Thread t = new Thread(() -> {
+        final Thread t = new Thread(() -> {
             try {
                 while (true) {
-                    Integer integer = testPool.borrowObject(borrowKey);
+                    final Integer integer = testPool.borrowObject(borrowKey);
                     testPool.returnObject(borrowKey, integer);
                     Thread.sleep(10);
                 }
-            } catch (Exception e) {
+            } catch (final Exception e) {
                 fail();
             }
         });
-        Thread t2 = new Thread(() -> {
+        final Thread t2 = new Thread(() -> {
             try {
                 while (true) {
                     testPool.clear(borrowKey);
                     Thread.sleep(10);
                 }
-            } catch (Exception e) {
+            } catch (final Exception e) {
                 fail();
             }
         });
@@ -1683,7 +1683,7 @@ public class TestGenericKeyedObjectPool extends TestKeyedObjectPool {
 
     @Test
     public void testGetStatsString() {
-        assertNotNull((gkoPool.getStatsString()));
+        assertNotNull(gkoPool.getStatsString());
     }
 
     /**
@@ -1722,8 +1722,8 @@ public class TestGenericKeyedObjectPool extends TestKeyedObjectPool {
     public void testInvalidateFreesCapacityForOtherKeys() throws Exception {
         gkoPool.setMaxTotal(1);
         gkoPool.setMaxWait(Duration.ofMillis(500));
-        Thread borrower = new Thread(new SimpleTestThread<>(gkoPool, "two"));
-        String obj = gkoPool.borrowObject("one");
+        final Thread borrower = new Thread(new SimpleTestThread<>(gkoPool, "two"));
+        final String obj = gkoPool.borrowObject("one");
         borrower.start();  // Will block
         Thread.sleep(100);  // Make sure borrower has started
         gkoPool.invalidateObject("one", obj);  // Should free capacity to serve the other key
@@ -1915,7 +1915,7 @@ public class TestGenericKeyedObjectPool extends TestKeyedObjectPool {
         for(int i=0;i<100;i++) {
             gkoPool.returnObject("",active[i]);
             assertEquals(99 - i,gkoPool.getNumActive(""));
-            assertEquals((i < 8 ? i+1 : 8),gkoPool.getNumIdle(""));
+            assertEquals(i < 8 ? i+1 : 8,gkoPool.getNumIdle(""));
         }
 
         for(int i=0;i<100;i++) {
@@ -1926,7 +1926,7 @@ public class TestGenericKeyedObjectPool extends TestKeyedObjectPool {
         for(int i=0;i<100;i++) {
             gkoPool.returnObject("a",active[i]);
             assertEquals(99 - i,gkoPool.getNumActive("a"));
-            assertEquals((i < 8 ? i+1 : 8),gkoPool.getNumIdle("a"));
+            assertEquals(i < 8 ? i+1 : 8,gkoPool.getNumIdle("a"));
         }
 
         // total number of idle instances is twice maxIdle
@@ -2169,7 +2169,7 @@ public class TestGenericKeyedObjectPool extends TestKeyedObjectPool {
                         " BorrowTime: " + (wt.postBorrowMillis != 0 ? wt.postBorrowMillis - wt.preBorrowMillis : -1) +
                         " PostReturn: " + (wt.postReturnMillis != 0 ? wt.postReturnMillis - originMillis : -1) +
                         " Ended: " + (wt.endedMillis - originMillis) +
-                        " Key: " + (wt.key) +
+                        " Key: " + wt.key +
                         " ObjId: " + wt.objectId
                         );
             }
@@ -2418,7 +2418,7 @@ public class TestGenericKeyedObjectPool extends TestKeyedObjectPool {
         gkoPool.setMaxTotal(1);
 
         // Test return object with no take waiters
-        String obj = gkoPool.borrowObject("0");
+        final String obj = gkoPool.borrowObject("0");
         gkoPool.returnObject("0", obj);
 
         // Test return object with a take waiter
@@ -2437,7 +2437,7 @@ public class TestGenericKeyedObjectPool extends TestKeyedObjectPool {
         gkoPool.setBlockWhenExhausted(false);
 
         // Test return object with no take waiters
-        String obj = gkoPool.borrowObject("0");
+        final String obj = gkoPool.borrowObject("0");
         gkoPool.returnObject("0", obj);
     }
 
diff --git a/src/test/java/org/apache/commons/pool2/impl/TestGenericObjectPool.java b/src/test/java/org/apache/commons/pool2/impl/TestGenericObjectPool.java
index f7cae2bf..7ae0a386 100644
--- a/src/test/java/org/apache/commons/pool2/impl/TestGenericObjectPool.java
+++ b/src/test/java/org/apache/commons/pool2/impl/TestGenericObjectPool.java
@@ -1,3011 +1,3011 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.commons.pool2.impl;
-
-
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.lessThanOrEqualTo;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertNotEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertNull;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-import static org.junit.jupiter.api.Assertions.fail;
-
-import java.lang.management.ManagementFactory;
-import java.lang.ref.WeakReference;
-import java.nio.charset.UnsupportedCharsetException;
-import java.time.Duration;
-import java.time.Instant;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.NoSuchElementException;
-import java.util.Objects;
-import java.util.Random;
-import java.util.Set;
-import java.util.Timer;
-import java.util.TimerTask;
-import java.util.concurrent.Semaphore;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import javax.management.MBeanServer;
-import javax.management.ObjectName;
-
-import org.apache.commons.pool2.BasePooledObjectFactory;
-import org.apache.commons.pool2.ObjectPool;
-import org.apache.commons.pool2.PoolUtils;
-import org.apache.commons.pool2.PooledObject;
-import org.apache.commons.pool2.PooledObjectFactory;
-import org.apache.commons.pool2.SwallowedExceptionListener;
-import org.apache.commons.pool2.TestBaseObjectPool;
-import org.apache.commons.pool2.TestException;
-import org.apache.commons.pool2.VisitTracker;
-import org.apache.commons.pool2.VisitTrackerFactory;
-import org.apache.commons.pool2.Waiter;
-import org.apache.commons.pool2.WaiterFactory;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.Timeout;
-
-/**
- */
-public class TestGenericObjectPool extends TestBaseObjectPool {
-
-    private class ConcurrentBorrowAndEvictThread extends Thread {
-        private final boolean borrow;
-        public String obj;
-
-        public ConcurrentBorrowAndEvictThread(final boolean borrow) {
-            this.borrow = borrow;
-        }
-
-        @Override
-        public void run() {
-            try {
-                if (borrow) {
-                    obj = genericObjectPool.borrowObject();
-                } else {
-                    genericObjectPool.evict();
-                }
-            } catch (final Exception e) {
-                // Ignore.
-            }
-        }
-    }
-
-    private static class CreateErrorFactory extends BasePooledObjectFactory<String, InterruptedException> {
-
-        private final Semaphore semaphore = new Semaphore(0);
-
-        @Override
-        public String create() throws InterruptedException {
-            semaphore.acquire();
-            throw new UnknownError("wiggle");
-        }
-
-        public boolean hasQueuedThreads() {
-            return semaphore.hasQueuedThreads();
-        }
-
-        public void release() {
-            semaphore.release();
-        }
-
-        @Override
-        public PooledObject<String> wrap(final String obj) {
-            return new DefaultPooledObject<>(obj);
-        }
-    }
-
-    private static class CreateFailFactory extends BasePooledObjectFactory<String, InterruptedException> {
-
-        private final Semaphore semaphore = new Semaphore(0);
-
-        @Override
-        public String create() throws InterruptedException {
-            semaphore.acquire();
-            throw new UnsupportedCharsetException("wibble");
-        }
-
-        public boolean hasQueuedThreads() {
-            return semaphore.hasQueuedThreads();
-        }
-
-        public void release() {
-            semaphore.release();
-        }
-
-        @Override
-        public PooledObject<String> wrap(final String obj) {
-            return new DefaultPooledObject<>(obj);
-        }
-    }
-
-    private static final class DummyFactory
-            extends BasePooledObjectFactory<Object, RuntimeException> {
-        @Override
-        public Object create() {
-            return null;
-        }
-        @Override
-        public PooledObject<Object> wrap(final Object value) {
-            return new DefaultPooledObject<>(value);
-        }
-    }
-
-    private static class EvictionThread<T, E extends Exception> extends Thread {
-
-        private final GenericObjectPool<T, E> pool;
-
-        public EvictionThread(final GenericObjectPool<T, E> pool) {
-            this.pool = pool;
-        }
-
-        @Override
-        public void run() {
-            try {
-                pool.evict();
-            } catch (final Exception e) {
-                // Ignore
-            }
-        }
-    }
-
-    /**
-     * Factory that creates HashSets.  Note that this means
-     *  0) All instances are initially equal (not discernible by equals)
-     *  1) Instances are mutable and mutation can cause change in identity / hashcode.
-     */
-    private static final class HashSetFactory
-            extends BasePooledObjectFactory<HashSet<String>, RuntimeException> {
-        @Override
-        public HashSet<String> create() {
-            return new HashSet<>();
-        }
-        @Override
-        public PooledObject<HashSet<String>> wrap(final HashSet<String> value) {
-            return new DefaultPooledObject<>(value);
-        }
-    }
-
-    /**
-     * Attempts to invalidate an object, swallowing IllegalStateException.
-     */
-    static class InvalidateThread implements Runnable {
-        private final String obj;
-        private final ObjectPool<String, ? extends Exception> pool;
-        private boolean done;
-        public InvalidateThread(final ObjectPool<String, ? extends Exception> pool, final String obj) {
-            this.obj = obj;
-            this.pool = pool;
-        }
-        public boolean complete() {
-            return done;
-        }
-        @Override
-        public void run() {
-            try {
-                pool.invalidateObject(obj);
-            } catch (final IllegalStateException ex) {
-                // Ignore
-            } catch (final Exception ex) {
-                fail("Unexpected exception " + ex.toString());
-            } finally {
-                done = true;
-            }
-        }
-    }
-
-    private static class InvalidFactory
-            extends BasePooledObjectFactory<Object, RuntimeException> {
-
-        @Override
-        public Object create() {
-            return new Object();
-        }
-        @Override
-        public boolean validateObject(final PooledObject<Object> obj) {
-            Waiter.sleepQuietly(1000);
-            return false;
-        }
-
-        @Override
-        public PooledObject<Object> wrap(final Object value) {
-            return new DefaultPooledObject<>(value);
-        }
-    }
-
-    public static class SimpleFactory implements PooledObjectFactory<String, TestException> {
-        int makeCounter;
-
-        int activationCounter;
-
-        int validateCounter;
-
-        int activeCount;
-
-        boolean evenValid = true;
-
-        boolean oddValid = true;
-
-        boolean exceptionOnPassivate;
-
-        boolean exceptionOnActivate;
-
-        boolean exceptionOnDestroy;
-
-        boolean exceptionOnValidate;
-
-        boolean enableValidation = true;
-
-        long destroyLatency;
-
-        long makeLatency;
-
-        long validateLatency;
-
-        int maxTotal = Integer.MAX_VALUE;
-
-        public SimpleFactory() {
-            this(true);
-        }
-
-        public SimpleFactory(final boolean valid) {
-            this(valid,valid);
-        }
-        
-        public SimpleFactory(final boolean evalid, final boolean ovalid) {
-            evenValid = evalid;
-            oddValid = ovalid;
-        }
-        
-        @Override
-        public void activateObject(final PooledObject<String> obj) throws TestException {
-            final boolean hurl;
-            final boolean evenTest;
-            final boolean oddTest;
-            final int counter;
-            synchronized(this) {
-                hurl = exceptionOnActivate;
-                evenTest = evenValid;
-                oddTest = oddValid;
-                counter = activationCounter++;
-            }
-            if (hurl && !(counter%2 == 0 ? evenTest : oddTest)) {
-                throw new TestException();
-            }
-        }
-        
-        @Override
-        public void destroyObject(final PooledObject<String> obj) throws TestException {
-            final long waitLatency;
-            final boolean hurl;
-            synchronized(this) {
-                waitLatency = destroyLatency;
-                hurl = exceptionOnDestroy;
-            }
-            if (waitLatency > 0) {
-                doWait(waitLatency);
-            }
-            synchronized(this) {
-                activeCount--;
-            }
-            if (hurl) {
-                throw new TestException();
-            }
-        }
-        
-        private void doWait(final long latency) {
-            Waiter.sleepQuietly(latency);
-        }
-        
-        public synchronized int getMakeCounter() {
-            return makeCounter;
-        }
-        
-        public synchronized boolean isThrowExceptionOnActivate() {
-            return exceptionOnActivate;
-        }
-        
-        public synchronized boolean isValidationEnabled() {
-            return enableValidation;
-        }
-        
-        @Override
-        public PooledObject<String> makeObject() {
-            final long waitLatency;
-            synchronized(this) {
-                activeCount++;
-                if (activeCount > maxTotal) {
-                    throw new IllegalStateException(
-                        "Too many active instances: " + activeCount);
-                }
-                waitLatency = makeLatency;
-            }
-            if (waitLatency > 0) {
-                doWait(waitLatency);
-            }
-            final int counter;
-            synchronized(this) {
-                counter = makeCounter++;
-            }
-            return new DefaultPooledObject<>(String.valueOf(counter));
-        }
-        
-        @Override
-        public void passivateObject(final PooledObject<String> obj) throws TestException {
-            final boolean hurl;
-            synchronized(this) {
-                hurl = exceptionOnPassivate;
-            }
-            if (hurl) {
-                throw new TestException();
-            }
-        }
-        
-        public synchronized void setDestroyLatency(final long destroyLatency) {
-            this.destroyLatency = destroyLatency;
-        }
-        
-        public synchronized void setEvenValid(final boolean valid) {
-            evenValid = valid;
-        }
-        
-        public synchronized void setMakeLatency(final long makeLatency) {
-            this.makeLatency = makeLatency;
-        }
-        
-        public synchronized void setMaxTotal(final int maxTotal) {
-            this.maxTotal = maxTotal;
-        }
-        
-        public synchronized void setOddValid(final boolean valid) {
-            oddValid = valid;
-        }
-
-        public synchronized void setThrowExceptionOnActivate(final boolean b) {
-            exceptionOnActivate = b;
-        }
-
-        public synchronized void setThrowExceptionOnDestroy(final boolean b) {
-            exceptionOnDestroy = b;
-        }
-
-        public synchronized void setThrowExceptionOnPassivate(final boolean bool) {
-            exceptionOnPassivate = bool;
-        }
-
-        public synchronized void setThrowExceptionOnValidate(final boolean bool) {
-            exceptionOnValidate = bool;
-        }
-
-        public synchronized void setValid(final boolean valid) {
-            setEvenValid(valid);
-            setOddValid(valid);
-        }
-
-        public synchronized void setValidateLatency(final long validateLatency) {
-            this.validateLatency = validateLatency;
-        }
-
-        public synchronized void setValidationEnabled(final boolean b) {
-            enableValidation = b;
-        }
-
-        @Override
-        public boolean validateObject(final PooledObject<String> obj) {
-            final boolean validate;
-            final boolean throwException;
-            final boolean evenTest;
-            final boolean oddTest;
-            final long waitLatency;
-            final int counter;
-            synchronized(this) {
-                validate = enableValidation;
-                throwException = exceptionOnValidate;
-                evenTest = evenValid;
-                oddTest = oddValid;
-                counter = validateCounter++;
-                waitLatency = validateLatency;
-            }
-            if (waitLatency > 0) {
-                doWait(waitLatency);
-            }
-            if (throwException) {
-                throw new RuntimeException("validation failed");
-            }
-            if (validate) {
-                return counter%2 == 0 ? evenTest : oddTest;
-            }
-            return true;
-        }
-    }
-
-    public static class TestEvictionPolicy<T> implements EvictionPolicy<T> {
-
-        private final AtomicInteger callCount = new AtomicInteger(0);
-
-        @Override
-        public boolean evict(final EvictionConfig config, final PooledObject<T> underTest,
-                final int idleCount) {
-            return callCount.incrementAndGet() > 1500;
-        }
-    }
-
-    static class TestThread<T, E extends Exception> implements Runnable {
-
-        /** source of random delay times */
-        private final java.util.Random random;
-
-        /** pool to borrow from */
-        private final ObjectPool<T, E> pool;
-
-        /** number of borrow attempts */
-        private final int iter;
-
-        /** delay before each borrow attempt */
-        private final int startDelay;
-
-        /** time to hold each borrowed object before returning it */
-        private final int holdTime;
-
-        /** whether or not start and hold time are randomly generated */
-        private final boolean randomDelay;
-
-        /** object expected to be borrowed (fail otherwise) */
-        private final Object expectedObject;
-
-        private volatile boolean complete;
-        private volatile boolean failed;
-        private volatile Throwable error;
-
-        public TestThread(final ObjectPool<T, E> pool) {
-            this(pool, 100, 50, true, null);
-        }
-
-        public TestThread(final ObjectPool<T, E> pool, final int iter) {
-            this(pool, iter, 50, true, null);
-        }
-
-        public TestThread(final ObjectPool<T, E> pool, final int iter, final int delay) {
-            this(pool, iter, delay, true, null);
-        }
-
-        public TestThread(final ObjectPool<T, E> pool, final int iter, final int delay,
-                final boolean randomDelay) {
-            this(pool, iter, delay, randomDelay, null);
-        }
-
-        public TestThread(final ObjectPool<T, E> pool, final int iter, final int delay,
-                final boolean randomDelay, final Object obj) {
-            this(pool, iter, delay, delay, randomDelay, obj);
-        }
-
-        public TestThread(final ObjectPool<T, E> pool, final int iter, final int startDelay,
-            final int holdTime, final boolean randomDelay, final Object obj) {
-       this.pool = pool;
-       this.iter = iter;
-       this.startDelay = startDelay;
-       this.holdTime = holdTime;
-       this.randomDelay = randomDelay;
-       this.random = this.randomDelay ? new Random() : null;
-       this.expectedObject = obj;
-    }
-
-        public boolean complete() {
-            return complete;
-        }
-
-        public boolean failed() {
-            return failed;
-        }
-
-        @Override
-        public void run() {
-            for (int i = 0; i < iter; i++) {
-                final long actualStartDelay = randomDelay ? (long) random.nextInt(startDelay) : startDelay;
-                final long actualHoldTime = randomDelay ? (long) random.nextInt(holdTime) : holdTime;
-                Waiter.sleepQuietly(actualStartDelay);
-                T obj = null;
-                try {
-                    obj = pool.borrowObject();
-                } catch (final Exception e) {
-                    error = e;
-                    failed = true;
-                    complete = true;
-                    break;
-                }
-
-                if (expectedObject != null && !expectedObject.equals(obj)) {
-                    error = new Throwable("Expected: " + expectedObject + " found: " + obj);
-                    failed = true;
-                    complete = true;
-                    break;
-                }
-
-                Waiter.sleepQuietly(actualHoldTime);
-                try {
-                    pool.returnObject(obj);
-                } catch (final Exception e) {
-                    error = e;
-                    failed = true;
-                    complete = true;
-                    break;
-                }
-            }
-            complete = true;
-        }
-    }
-
-    /*
-     * Very simple test thread that just tries to borrow an object from
-     * the provided pool returns it after a wait
-     */
-    static class WaitingTestThread<E extends Exception> extends Thread {
-        private final GenericObjectPool<String, E> pool;
-        private final long pause;
-        private Throwable thrown;
-
-        private long preBorrowMillis; // just before borrow
-        private long postBorrowMillis; //  borrow returned
-        private long postReturnMillis; // after object was returned
-        private long endedMillis;
-        private String objectId;
-
-        public WaitingTestThread(final GenericObjectPool<String, E> pool, final long pause) {
-            this.pool = pool;
-            this.pause = pause;
-            this.thrown = null;
-        }
-
-        @Override
-        public void run() {
-            try {
-                preBorrowMillis = System.currentTimeMillis();
-                final String obj = pool.borrowObject();
-                objectId = obj;
-                postBorrowMillis = System.currentTimeMillis();
-                Thread.sleep(pause);
-                pool.returnObject(obj);
-                postReturnMillis = System.currentTimeMillis();
-            } catch (final Throwable e) {
-                thrown = e;
-            } finally{
-                endedMillis = System.currentTimeMillis();
-            }
-        }
-    }
-
-    private static final boolean DISPLAY_THREAD_DETAILS=
-        Boolean.parseBoolean(System.getProperty("TestGenericObjectPool.display.thread.details", "false"));
-    // To pass this to a Maven test, use:
-    // mvn test -DargLine="-DTestGenericObjectPool.display.thread.details=true"
-    // @see https://issues.apache.org/jira/browse/SUREFIRE-121
-
-    protected GenericObjectPool<String, TestException> genericObjectPool;
-
-    private SimpleFactory simpleFactory;
-
-    @SuppressWarnings("deprecation")
-    private void assertConfiguration(final GenericObjectPoolConfig<?> expected, final GenericObjectPool<?, ?> actual) {
-        assertEquals(Boolean.valueOf(expected.getTestOnCreate()), Boolean.valueOf(actual.getTestOnCreate()),
-                "testOnCreate");
-        assertEquals(Boolean.valueOf(expected.getTestOnBorrow()), Boolean.valueOf(actual.getTestOnBorrow()),
-                "testOnBorrow");
-        assertEquals(Boolean.valueOf(expected.getTestOnReturn()), Boolean.valueOf(actual.getTestOnReturn()),
-                "testOnReturn");
-        assertEquals(Boolean.valueOf(expected.getTestWhileIdle()), Boolean.valueOf(actual.getTestWhileIdle()),
-                "testWhileIdle");
-        assertEquals(Boolean.valueOf(expected.getBlockWhenExhausted()), Boolean.valueOf(actual.getBlockWhenExhausted()),
-                "whenExhaustedAction");
-        assertEquals(expected.getMaxTotal(), actual.getMaxTotal(), "maxTotal");
-        assertEquals(expected.getMaxIdle(), actual.getMaxIdle(), "maxIdle");
-        assertEquals(expected.getMaxWaitMillis(), actual.getMaxWaitMillis(), "maxWaitDuration");
-        assertEquals(expected.getMaxWaitDuration(), actual.getMaxWaitDuration(), "maxWaitDuration");
-        assertEquals(expected.getMinEvictableIdleTimeMillis(), actual.getMinEvictableIdleTimeMillis(),
-                "minEvictableIdleTimeMillis");
-        assertEquals(expected.getMinEvictableIdleTime(), actual.getMinEvictableIdleTime(),
-                "minEvictableIdleTime");
-        assertEquals(expected.getMinEvictableIdleDuration(), actual.getMinEvictableIdleDuration(),
-                "minEvictableIdleDuration");
-        assertEquals(expected.getNumTestsPerEvictionRun(), actual.getNumTestsPerEvictionRun(),
-                "numTestsPerEvictionRun");
-        assertEquals(expected.getEvictorShutdownTimeoutDuration(), actual.getEvictorShutdownTimeoutDuration(),
-                "evictorShutdownTimeoutDuration");
-        assertEquals(expected.getEvictorShutdownTimeoutMillis(), actual.getEvictorShutdownTimeoutMillis(),
-                "evictorShutdownTimeoutMillis");
-        assertEquals(expected.getEvictorShutdownTimeout(), actual.getEvictorShutdownTimeout(),
-                "evictorShutdownTimeout");
-        assertEquals(expected.getTimeBetweenEvictionRunsMillis(), actual.getTimeBetweenEvictionRunsMillis(),
-                "timeBetweenEvictionRunsMillis");
-        assertEquals(expected.getDurationBetweenEvictionRuns(), actual.getTimeBetweenEvictionRuns(),
-                "timeBetweenEvictionRuns");
-        assertEquals(expected.getTimeBetweenEvictionRuns(), actual.getTimeBetweenEvictionRuns(),
-                "timeBetweenEvictionRuns");
-    }
-
-    private void checkEvict(final boolean lifo) throws Exception {
-        // yea this is hairy but it tests all the code paths in GOP.evict()
-        genericObjectPool.setSoftMinEvictableIdle(Duration.ofMillis(10));
-        genericObjectPool.setSoftMinEvictableIdleTime(Duration.ofMillis(10));
-        genericObjectPool.setMinIdle(2);
-        genericObjectPool.setTestWhileIdle(true);
-        genericObjectPool.setLifo(lifo);
-        genericObjectPool.addObjects(5);
-        genericObjectPool.evict();
-        simpleFactory.setEvenValid(false);
-        simpleFactory.setOddValid(false);
-        simpleFactory.setThrowExceptionOnActivate(true);
-        genericObjectPool.evict();
-        genericObjectPool.addObjects(5);
-        simpleFactory.setThrowExceptionOnActivate(false);
-        simpleFactory.setThrowExceptionOnPassivate(true);
-        genericObjectPool.evict();
-        simpleFactory.setThrowExceptionOnPassivate(false);
-        simpleFactory.setEvenValid(true);
-        simpleFactory.setOddValid(true);
-        Thread.sleep(125);
-        genericObjectPool.evict();
-        assertEquals(2, genericObjectPool.getNumIdle());
-    }
-
-    private void checkEvictionOrder(final boolean lifo) throws Exception {
-        checkEvictionOrderPart1(lifo);
-        tearDown();
-        setUp();
-        checkEvictionOrderPart2(lifo);
-    }
-
-    private void checkEvictionOrderPart1(final boolean lifo) throws Exception {
-        genericObjectPool.setNumTestsPerEvictionRun(2);
-        genericObjectPool.setMinEvictableIdleTime(Duration.ofMillis(100));
-        genericObjectPool.setLifo(lifo);
-        for (int i = 0; i < 5; i++) {
-            genericObjectPool.addObject();
-            Thread.sleep(100);
-        }
-        // Order, oldest to youngest, is "0", "1", ...,"4"
-        genericObjectPool.evict(); // Should evict "0" and "1"
-        final Object obj = genericObjectPool.borrowObject();
-        assertNotEquals("0", obj, "oldest not evicted");
-        assertNotEquals("1", obj, "second oldest not evicted");
-        // 2 should be next out for FIFO, 4 for LIFO
-        assertEquals(lifo ? "4" : "2" , obj,"Wrong instance returned");
-    }
-
-    private void checkEvictionOrderPart2(final boolean lifo) throws Exception {
-        // Two eviction runs in sequence
-        genericObjectPool.setNumTestsPerEvictionRun(2);
-        genericObjectPool.setMinEvictableIdleTime(Duration.ofMillis(100));
-        genericObjectPool.setLifo(lifo);
-        for (int i = 0; i < 5; i++) {
-            genericObjectPool.addObject();
-            Thread.sleep(100);
-        }
-        genericObjectPool.evict(); // Should evict "0" and "1"
-        genericObjectPool.evict(); // Should evict "2" and "3"
-        final Object obj = genericObjectPool.borrowObject();
-        assertEquals("4", obj,"Wrong instance remaining in pool");
-    }
-
-    private void checkEvictorVisiting(final boolean lifo) throws Exception {
-        VisitTracker<Object> obj;
-        VisitTrackerFactory<Object> trackerFactory = new VisitTrackerFactory<>();
-        try (GenericObjectPool<VisitTracker<Object>,RuntimeException> trackerPool = new GenericObjectPool<>(trackerFactory)) {
-            trackerPool.setNumTestsPerEvictionRun(2);
-            trackerPool.setMinEvictableIdleTime(Duration.ofMillis(-1));
-            trackerPool.setTestWhileIdle(true);
-            trackerPool.setLifo(lifo);
-            trackerPool.setTestOnReturn(false);
-            trackerPool.setTestOnBorrow(false);
-            for (int i = 0; i < 8; i++) {
-                trackerPool.addObject();
-            }
-            trackerPool.evict(); // Visit oldest 2 - 0 and 1
-            obj = trackerPool.borrowObject();
-            trackerPool.returnObject(obj);
-            obj = trackerPool.borrowObject();
-            trackerPool.returnObject(obj);
-            // borrow, return, borrow, return
-            // FIFO will move 0 and 1 to end
-            // LIFO, 7 out, then in, then out, then in
-            trackerPool.evict(); // Should visit 2 and 3 in either case
-            for (int i = 0; i < 8; i++) {
-                final VisitTracker<Object> tracker = trackerPool.borrowObject();
-                if (tracker.getId() >= 4) {
-                    assertEquals( 0, tracker.getValidateCount(),"Unexpected instance visited " + tracker.getId());
-                } else {
-                    assertEquals( 1, tracker.getValidateCount(),
-                            "Instance " + tracker.getId() + " visited wrong number of times.");
-                }
-            }
-        }
-
-        trackerFactory = new VisitTrackerFactory<>();
-        try (GenericObjectPool<VisitTracker<Object>, RuntimeException> trackerPool = new GenericObjectPool<>(trackerFactory)) {
-            trackerPool.setNumTestsPerEvictionRun(3);
-            trackerPool.setMinEvictableIdleTime(Duration.ofMillis(-1));
-            trackerPool.setTestWhileIdle(true);
-            trackerPool.setLifo(lifo);
-            trackerPool.setTestOnReturn(false);
-            trackerPool.setTestOnBorrow(false);
-            for (int i = 0; i < 8; i++) {
-                trackerPool.addObject();
-            }
-            trackerPool.evict(); // 0, 1, 2
-            trackerPool.evict(); // 3, 4, 5
-            obj = trackerPool.borrowObject();
-            trackerPool.returnObject(obj);
-            obj = trackerPool.borrowObject();
-            trackerPool.returnObject(obj);
-            obj = trackerPool.borrowObject();
-            trackerPool.returnObject(obj);
-            // borrow, return, borrow, return
-            // FIFO 3,4,5,6,7,0,1,2
-            // LIFO 7,6,5,4,3,2,1,0
-            // In either case, pointer should be at 6
-            trackerPool.evict();
-            // Should hit 6,7,0 - 0 for second time
-            for (int i = 0; i < 8; i++) {
-                final VisitTracker<Object> tracker = trackerPool.borrowObject();
-                if (tracker.getId() != 0) {
-                    assertEquals( 1, tracker.getValidateCount(),
-                            "Instance " + tracker.getId() + " visited wrong number of times.");
-                } else {
-                    assertEquals( 2, tracker.getValidateCount(),
-                            "Instance " + tracker.getId() + " visited wrong number of times.");
-                }
-            }
-        }
-
-        // Randomly generate a pools with random numTests
-        // and make sure evictor cycles through elements appropriately
-        final int[] smallPrimes = { 2, 3, 5, 7 };
-        final Random random = new Random();
-        random.setSeed(System.currentTimeMillis());
-        for (int i = 0; i < 4; i++) {
-            for (int j = 0; j < 5; j++) {
-                try (GenericObjectPool<VisitTracker<Object>, RuntimeException> trackerPool = new GenericObjectPool<>(trackerFactory)) {
-                    trackerPool.setNumTestsPerEvictionRun(smallPrimes[i]);
-                    trackerPool.setMinEvictableIdleTime(Duration.ofMillis(-1));
-                    trackerPool.setTestWhileIdle(true);
-                    trackerPool.setLifo(lifo);
-                    trackerPool.setTestOnReturn(false);
-                    trackerPool.setTestOnBorrow(false);
-                    trackerPool.setMaxIdle(-1);
-                    final int instanceCount = 10 + random.nextInt(20);
-                    trackerPool.setMaxTotal(instanceCount);
-                    for (int k = 0; k < instanceCount; k++) {
-                        trackerPool.addObject();
-                    }
-
-                    // Execute a random number of evictor runs
-                    final int runs = 10 + random.nextInt(50);
-                    for (int k = 0; k < runs; k++) {
-                        trackerPool.evict();
-                    }
-
-                    // Number of times evictor should have cycled through the pool
-                    final int cycleCount = (runs * trackerPool.getNumTestsPerEvictionRun()) / instanceCount;
-
-                    // Look at elements and make sure they are visited cycleCount
-                    // or cycleCount + 1 times
-                    VisitTracker<Object> tracker = null;
-                    int visitCount = 0;
-                    for (int k = 0; k < instanceCount; k++) {
-                        tracker = trackerPool.borrowObject();
-                        assertTrue(trackerPool.getNumActive() <= trackerPool.getMaxTotal());
-                        visitCount = tracker.getValidateCount();
-                        assertTrue(visitCount >= cycleCount && visitCount <= cycleCount + 1);
-                    }
-                }
-            }
-        }
-    }
-
-    private BasePooledObjectFactory<String, RuntimeException> createDefaultPooledObjectFactory() {
-        return new BasePooledObjectFactory<String, RuntimeException>() {
-            @Override
-            public String create() {
-                // fake
-                return null;
-            }
-
-            @Override
-            public PooledObject<String> wrap(final String obj) {
-                // fake
-                return new DefaultPooledObject<>(obj);
-            }
-        };
-    }
-
-    private BasePooledObjectFactory<String, RuntimeException> createNullPooledObjectFactory() {
-        return new BasePooledObjectFactory<String, RuntimeException>() {
-            @Override
-            public String create() {
-                // fake
-                return null;
-            }
-
-            @Override
-            public PooledObject<String> wrap(final String obj) {
-                // fake
-                return null;
-            }
-        };
-    }
-
-    private BasePooledObjectFactory<String, InterruptedException> createSlowObjectFactory(final long elapsedTimeMillis) {
-        return new BasePooledObjectFactory<String, InterruptedException>() {
-            @Override
-            public String create() throws InterruptedException {
-                Thread.sleep(elapsedTimeMillis);
-                return "created";
-            }
-
-            @Override
-            public PooledObject<String> wrap(final String obj) {
-                // fake
-                return new DefaultPooledObject<>(obj);
-            }
-        };
-    }
-
-    @Override
-    protected Object getNthObject(final int n) {
-        return String.valueOf(n);
-    }
-
-    @Override
-    protected boolean isFifo() {
-        return false;
-    }
-
-    @Override
-    protected boolean isLifo() {
-        return true;
-    }
-
-    @Override
-    protected ObjectPool<String, TestException> makeEmptyPool(final int minCap) {
-        final GenericObjectPool<String, TestException> mtPool = new GenericObjectPool<>(new SimpleFactory());
-        mtPool.setMaxTotal(minCap);
-       mtPool.setMaxIdle(minCap);
-       return mtPool;
-    }
-
-    @Override
-    protected <E extends Exception> ObjectPool<Object, E> makeEmptyPool(final PooledObjectFactory<Object, E> fac) {
-        return new GenericObjectPool<>(fac);
-    }
-
-    /**
-     * Kicks off <numThreads> test threads, each of which will go through
-     * <iterations> borrow-return cycles with random delay times <= delay
-     * in between.
-     */
-    private <T, E extends Exception> void runTestThreads(final int numThreads, final int iterations, final int delay, final GenericObjectPool<T, E> testPool) {
-        final TestThread<T, E>[] threads = new TestThread[numThreads];
-        for (int i = 0; i < numThreads; i++) {
-            threads[i] = new TestThread<>(testPool, iterations, delay);
-            final Thread t = new Thread(threads[i]);
-            t.start();
-        }
-        for (int i = 0; i < numThreads; i++) {
-            while (!(threads[i]).complete()) {
-                Waiter.sleepQuietly(500L);
-            }
-            if (threads[i].failed()) {
-                fail("Thread " + i + " failed: " + threads[i].error.toString());
-            }
-        }
-    }
-
-    @BeforeEach
-    public void setUp() {
-        simpleFactory = new SimpleFactory();
-        genericObjectPool = new GenericObjectPool<>(simpleFactory);
-    }
-
-    @AfterEach
-    public void tearDown() throws Exception {
-        final ObjectName jmxName = genericObjectPool.getJmxName();
-        final String poolName = Objects.toString(jmxName, null);
-
-        genericObjectPool.clear();
-        genericObjectPool.close();
-        genericObjectPool = null;
-        simpleFactory = null;
-
-        final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
-        final Set<ObjectName> result = mbs.queryNames(new ObjectName("org.apache.commoms.pool2:type=GenericObjectPool,*"), null);
-        // There should be no registered pools at this point
-        final int registeredPoolCount = result.size();
-        final StringBuilder msg = new StringBuilder("Current pool is: ");
-        msg.append(poolName);
-        msg.append("  Still open pools are: ");
-        for (final ObjectName name : result) {
-            // Clean these up ready for the next test
-            msg.append(name.toString());
-            msg.append(" created via\n");
-            msg.append(mbs.getAttribute(name, "CreationStackTrace"));
-            msg.append('\n');
-            mbs.unregisterMBean(name);
-        }
-        assertEquals(0, registeredPoolCount, msg.toString());
-
-        // Make sure that EvictionTimer executor is shut down.
-        Thread.yield();
-        if (EvictionTimer.getExecutor() != null) {
-            Thread.sleep(1000);
-        }
-        assertNull(EvictionTimer.getExecutor(), "EvictionTimer.getExecutor()");
-    }
-
-    /**
-     * Check that a pool that starts an evictor, but is never closed does not leave EvictionTimer executor running. Confirmation check is in
-     * {@link #tearDown()}.
-     * 
-     * @throws TestException Custom exception
-     * @throws InterruptedException if any thread has interrupted the current thread. The <em>interrupted status</em> of the current thread is cleared when this
-     *         exception is thrown.
-     */
-    @SuppressWarnings("deprecation")
-    @Test
-    public void testAbandonedPool() throws TestException, InterruptedException {
-        final GenericObjectPoolConfig<String> config = new GenericObjectPoolConfig<>();
-        config.setJmxEnabled(false);
-        GenericObjectPool<String, TestException> abandoned = new GenericObjectPool<>(simpleFactory, config);
-        abandoned.setTimeBetweenEvictionRuns(Duration.ofMillis(100)); // Starts evictor
-        assertEquals(abandoned.getRemoveAbandonedTimeout(), abandoned.getRemoveAbandonedTimeoutDuration().getSeconds());
-
-        // This is ugly, but forces GC to hit the pool
-        final WeakReference<GenericObjectPool<String, TestException>> ref = new WeakReference<>(abandoned);
-        abandoned = null;
-        while (ref.get() != null) {
-            System.gc();
-            Thread.sleep(100);
-        }
-    }
-
-
-    @Test
-    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
-    public void testAddObject() throws Exception {
-        assertEquals( 0, genericObjectPool.getNumIdle(),"should be zero idle");
-        genericObjectPool.addObject();
-        assertEquals( 1, genericObjectPool.getNumIdle(),"should be one idle");
-        assertEquals( 0, genericObjectPool.getNumActive(),"should be zero active");
-        final String obj = genericObjectPool.borrowObject();
-        assertEquals( 0, genericObjectPool.getNumIdle(),"should be zero idle");
-        assertEquals( 1, genericObjectPool.getNumActive(),"should be one active");
-        genericObjectPool.returnObject(obj);
-        assertEquals( 1, genericObjectPool.getNumIdle(),"should be one idle");
-        assertEquals( 0, genericObjectPool.getNumActive(),"should be zero active");
-    }
-
-    @Test
-    public void testAppendStats() {
-        assertFalse(genericObjectPool.getMessageStatistics());
-        assertEquals("foo", (genericObjectPool.appendStats("foo")));
-        try (final GenericObjectPool<?, TestException> pool = new GenericObjectPool<>(new SimpleFactory())) {
-            pool.setMessagesStatistics(true);
-            assertNotEquals("foo", (pool.appendStats("foo")));
-            pool.setMessagesStatistics(false);
-            assertEquals("foo", (pool.appendStats("foo")));
-        }
-    }
-
-    /*
-     * Note: This test relies on timing for correct execution. There *should* be
-     * enough margin for this to work correctly on most (all?) systems but be
-     * aware of this if you see a failure of this test.
-     */
-    @SuppressWarnings({
-        "rawtypes", "unchecked"
-    })
-    @Test
-    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
-    public void testBorrowObjectFairness() throws Exception {
-
-        final int numThreads = 40;
-        final int maxTotal = 40;
-
-        final GenericObjectPoolConfig config = new GenericObjectPoolConfig();
-        config.setMaxTotal(maxTotal);
-        config.setMaxIdle(maxTotal);
-        config.setFairness(true);
-        config.setLifo(false);
-
-        genericObjectPool = new GenericObjectPool(simpleFactory, config);
-
-        // Exhaust the pool
-        final String[] objects = new String[maxTotal];
-        for (int i = 0; i < maxTotal; i++) {
-            objects[i] = genericObjectPool.borrowObject();
-        }
-
-        // Start and park threads waiting to borrow objects
-        final TestThread[] threads = new TestThread[numThreads];
-        for(int i=0;i<numThreads;i++) {
-            threads[i] = new TestThread(genericObjectPool, 1, 0, 2000, false, String.valueOf(i % maxTotal));
-            final Thread t = new Thread(threads[i]);
-            t.start();
-            // Short delay to ensure threads start in correct order
-            try {
-                Thread.sleep(10);
-            } catch (final InterruptedException e) {
-                fail(e.toString());
-            }
-        }
-
-        // Return objects, other threads should get served in order
-        for (int i = 0; i < maxTotal; i++) {
-            genericObjectPool.returnObject(objects[i]);
-        }
-
-        // Wait for threads to finish
-        for (int i = 0; i < numThreads; i++) {
-            while (!(threads[i]).complete()) {
-                Waiter.sleepQuietly(500L);
-            }
-            if (threads[i].failed()) {
-                fail("Thread " + i + " failed: " + threads[i].error.toString());
-            }
-        }
-    }
-
-    @Test
-    public void testBorrowTimings() throws Exception {
-        // Borrow
-        final String object = genericObjectPool.borrowObject();
-        final PooledObject<String> po = genericObjectPool.getPooledObject(object);
-        // In the initial state, all instants are the creation instant: last borrow, last use, last return.
-        // In the initial state, the active duration is the time between "now" and the creation time.
-        // In the initial state, the idle duration is the time between "now" and the last return, which is the creation time.
-        // But... this PO might have already been used in other tests in this class.
-
-        final Instant lastBorrowInstant1 = po.getLastBorrowInstant();
-        final Instant lastReturnInstant1 = po.getLastReturnInstant();
-        final Instant lastUsedInstant1 = po.getLastUsedInstant();
-
-        assertThat(po.getCreateInstant(), lessThanOrEqualTo(lastBorrowInstant1));
-        assertThat(po.getCreateInstant(), lessThanOrEqualTo(lastReturnInstant1));
-        assertThat(po.getCreateInstant(), lessThanOrEqualTo(lastUsedInstant1));
-        assertThat(po.getCreateTime(), lessThanOrEqualTo(lastBorrowInstant1.toEpochMilli()));
-        assertThat(po.getCreateTime(), lessThanOrEqualTo(lastReturnInstant1.toEpochMilli()));
-        assertThat(po.getCreateTime(), lessThanOrEqualTo(lastUsedInstant1.toEpochMilli()));
-
-        // Sleep MUST be "long enough" to detect that more than 0 milliseconds have elapsed.
-        // Need an API in Java 8 to get the clock granularity.
-        Thread.sleep(200);
-
-        assertFalse(po.getActiveDuration().isNegative());
-        assertFalse(po.getActiveDuration().isZero());
-        // We use greaterThanOrEqualTo instead of equal because "now" many be different when each argument is evaluated.
-        assertThat(1L, lessThanOrEqualTo(2L)); // sanity check
-        assertThat(Duration.ZERO, lessThanOrEqualTo(Duration.ZERO.plusNanos(1))); // sanity check
-        assertThat(po.getActiveDuration(), lessThanOrEqualTo(po.getIdleDuration()));
-        // Deprecated
-        assertThat(po.getActiveDuration().toMillis(), lessThanOrEqualTo(po.getActiveTimeMillis()));
-        assertThat(po.getActiveDuration(), lessThanOrEqualTo(po.getActiveTime()));
-        //
-        // TODO How to compare ID with AD since other tests may have touched the PO?
-        assertThat(po.getActiveDuration(), lessThanOrEqualTo(po.getIdleTime()));
-        assertThat(po.getActiveDuration().toMillis(), lessThanOrEqualTo(po.getIdleTimeMillis()));
-        //
-        assertThat(po.getCreateInstant(), lessThanOrEqualTo(po.getLastBorrowInstant()));
-        assertThat(po.getCreateInstant(), lessThanOrEqualTo(po.getLastReturnInstant()));
-        assertThat(po.getCreateInstant(), lessThanOrEqualTo(po.getLastUsedInstant()));
-
-        assertThat(lastBorrowInstant1, lessThanOrEqualTo(po.getLastBorrowInstant()));
-        assertThat(lastReturnInstant1, lessThanOrEqualTo(po.getLastReturnInstant()));
-        assertThat(lastUsedInstant1, lessThanOrEqualTo(po.getLastUsedInstant()));
-
-        genericObjectPool.returnObject(object);
-
-        assertFalse(po.getActiveDuration().isNegative());
-        assertFalse(po.getActiveDuration().isZero());
-        assertThat(po.getActiveDuration().toMillis(), lessThanOrEqualTo(po.getActiveTimeMillis()));
-        assertThat(po.getActiveDuration(), lessThanOrEqualTo(po.getActiveTime()));
-
-        assertThat(lastBorrowInstant1, lessThanOrEqualTo(po.getLastBorrowInstant()));
-        assertThat(lastReturnInstant1, lessThanOrEqualTo(po.getLastReturnInstant()));
-        assertThat(lastUsedInstant1, lessThanOrEqualTo(po.getLastUsedInstant()));
-    }
-
-    /**
-     * On first borrow, first object fails validation, second object is OK.
-     * Subsequent borrows are OK. This was POOL-152.
-     * @throws Exception
-     */
-    @Test
-    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
-    public void testBrokenFactoryShouldNotBlockPool() throws Exception {
-        final int maxTotal = 1;
-
-        simpleFactory.setMaxTotal(maxTotal);
-        genericObjectPool.setMaxTotal(maxTotal);
-        genericObjectPool.setBlockWhenExhausted(true);
-        genericObjectPool.setTestOnBorrow(true);
-
-        // First borrow object will need to create a new object which will fail
-        // validation.
-        String obj = null;
-        Exception ex = null;
-        simpleFactory.setValid(false);
-        try {
-            obj = genericObjectPool.borrowObject();
-        } catch (final Exception e) {
-            ex = e;
-        }
-        // Failure expected
-        assertNotNull(ex);
-        assertTrue(ex instanceof NoSuchElementException);
-        assertNull(obj);
-
-        // Configure factory to create valid objects so subsequent borrows work
-        simpleFactory.setValid(true);
-
-        // Subsequent borrows should be OK
-        obj = genericObjectPool.borrowObject();
-        assertNotNull(obj);
-        genericObjectPool.returnObject(obj);
-    }
-
-    // POOL-259
-    @Test
-    public void testClientWaitStats() throws TestException {
-        final SimpleFactory factory = new SimpleFactory();
-        // Give makeObject a little latency
-        factory.setMakeLatency(200);
-        try (final GenericObjectPool<String, TestException> pool = new GenericObjectPool<>(factory, new GenericObjectPoolConfig<>())) {
-            final String s = pool.borrowObject();
-            // First borrow waits on create, so wait time should be at least 200 ms
-            // Allow 100ms error in clock times
-            assertTrue(pool.getMaxBorrowWaitTimeMillis() >= 100);
-            assertTrue(pool.getMeanBorrowWaitTimeMillis() >= 100);
-            pool.returnObject(s);
-            pool.borrowObject();
-            // Second borrow does not have to wait on create, average should be about 100
-            assertTrue(pool.getMaxBorrowWaitTimeMillis() > 100);
-            assertTrue(pool.getMeanBorrowWaitTimeMillis() < 200);
-            assertTrue(pool.getMeanBorrowWaitTimeMillis() > 20);
-        }
-    }
-
-    @Test
-    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
-    public void testCloseMultiplePools1() {
-        try (final GenericObjectPool<String, TestException> genericObjectPool2 = new GenericObjectPool<>(simpleFactory)) {
-            genericObjectPool.setTimeBetweenEvictionRuns(TestConstants.ONE_MILLISECOND_DURATION);
-            genericObjectPool2.setTimeBetweenEvictionRuns(TestConstants.ONE_MILLISECOND_DURATION);
-        }
-        genericObjectPool.close();
-    }
-
-    @Test
-    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
-    public void testCloseMultiplePools2() throws Exception {
-        try (final GenericObjectPool<String, TestException> genericObjectPool2 = new GenericObjectPool<>(simpleFactory)) {
-            // Ensure eviction takes a long time, during which time EvictionTimer.executor's queue is empty
-            simpleFactory.setDestroyLatency(1000L);
-            // Ensure there is an object to evict, so that above latency takes effect
-            genericObjectPool.setTimeBetweenEvictionRuns(TestConstants.ONE_MILLISECOND_DURATION);
-            genericObjectPool2.setTimeBetweenEvictionRuns(TestConstants.ONE_MILLISECOND_DURATION);
-            genericObjectPool.setMinEvictableIdleTime(TestConstants.ONE_MILLISECOND_DURATION);
-            genericObjectPool2.setMinEvictableIdleTime(TestConstants.ONE_MILLISECOND_DURATION);
-            genericObjectPool.addObject();
-            genericObjectPool2.addObject();
-            // Close both pools
-        }
-        genericObjectPool.close();
-    }
-
-
-    @Test
-    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
-    public void testConcurrentBorrowAndEvict() throws Exception {
-
-        genericObjectPool.setMaxTotal(1);
-        genericObjectPool.addObject();
-
-        for (int i = 0; i < 5000; i++) {
-            final ConcurrentBorrowAndEvictThread one =
-                    new ConcurrentBorrowAndEvictThread(true);
-            final ConcurrentBorrowAndEvictThread two =
-                    new ConcurrentBorrowAndEvictThread(false);
-
-            one.start();
-            two.start();
-            one.join();
-            two.join();
-
-            genericObjectPool.returnObject(one.obj);
-
-            /* Uncomment this for a progress indication
-            if (i % 10 == 0) {
-                System.out.println(i/10);
-            }
-            */
-        }
-    }
-
-    /**
-     * POOL-231 - verify that concurrent invalidates of the same object do not
-     * corrupt pool destroyCount.
-     *
-     * @throws Exception May occur in some failure modes
-     */
-    @Test
-    public void testConcurrentInvalidate() throws Exception {
-        // Get allObjects and idleObjects loaded with some instances
-        final int nObjects = 1000;
-        genericObjectPool.setMaxTotal(nObjects);
-        genericObjectPool.setMaxIdle(nObjects);
-        final String[] obj = new String[nObjects];
-        for (int i = 0; i < nObjects; i++) {
-            obj[i] = genericObjectPool.borrowObject();
-        }
-        for (int i = 0; i < nObjects; i++) {
-            if (i % 2 == 0) {
-                genericObjectPool.returnObject(obj[i]);
-            }
-        }
-        final int nThreads = 20;
-        final int nIterations = 60;
-        final InvalidateThread[] threads = new InvalidateThread[nThreads];
-        // Randomly generated list of distinct invalidation targets
-        final ArrayList<Integer> targets = new ArrayList<>();
-        final Random random = new Random();
-        for (int j = 0; j < nIterations; j++) {
-            // Get a random invalidation target
-            Integer targ = Integer.valueOf(random.nextInt(nObjects));
-            while (targets.contains(targ)) {
-                targ = Integer.valueOf(random.nextInt(nObjects));
-            }
-            targets.add(targ);
-            // Launch nThreads threads all trying to invalidate the target
-            for (int i = 0; i < nThreads; i++) {
-                threads[i] = new InvalidateThread(genericObjectPool, obj[targ.intValue()]);
-            }
-            for (int i = 0; i < nThreads; i++) {
-                new Thread(threads[i]).start();
-            }
-            boolean done = false;
-            while (!done) {
-                done = true;
-                for (int i = 0; i < nThreads; i++) {
-                    done = done && threads[i].complete();
-                }
-                Thread.sleep(100);
-            }
-        }
-        assertEquals(nIterations, genericObjectPool.getDestroyedCount());
-    }
-
-    @Test
-    public void testConstructorNullFactory() {
-        // add dummy assert (won't be invoked because of IAE) to avoid "unused" warning
-        assertThrows(IllegalArgumentException.class,
-                () -> new GenericObjectPool<>(null));
-    }
-
-    @Test
-    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
-    public void testConstructors() {
-
-        // Make constructor arguments all different from defaults
-        final int minIdle = 2;
-        final Duration maxWaitDuration = Duration.ofMillis(3);
-        final long maxWaitMillis = maxWaitDuration.toMillis();
-        final int maxIdle = 4;
-        final int maxTotal = 5;
-        final Duration minEvictableIdleDuration = Duration.ofMillis(6);
-        final long minEvictableIdleMillis = minEvictableIdleDuration.toMillis();
-        final int numTestsPerEvictionRun = 7;
-        final boolean testOnBorrow = true;
-        final boolean testOnReturn = true;
-        final boolean testWhileIdle = true;
-        final long timeBetweenEvictionRunsMillis = 8;
-        final boolean blockWhenExhausted = false;
-        final boolean lifo = false;
-        final PooledObjectFactory<Object, RuntimeException> dummyFactory = new DummyFactory();
-        try (GenericObjectPool<Object, RuntimeException> dummyPool = new GenericObjectPool<>(dummyFactory)) {
-            assertEquals(GenericObjectPoolConfig.DEFAULT_MAX_IDLE, dummyPool.getMaxIdle());
-            assertEquals(BaseObjectPoolConfig.DEFAULT_MAX_WAIT_MILLIS, dummyPool.getMaxWaitMillis());
-            assertEquals(GenericObjectPoolConfig.DEFAULT_MIN_IDLE, dummyPool.getMinIdle());
-            assertEquals(GenericObjectPoolConfig.DEFAULT_MAX_TOTAL, dummyPool.getMaxTotal());
-            assertEquals(BaseObjectPoolConfig.DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS,
-                    dummyPool.getMinEvictableIdleTimeMillis());
-            assertEquals(BaseObjectPoolConfig.DEFAULT_MIN_EVICTABLE_IDLE_TIME,
-                    dummyPool.getMinEvictableIdleTime());
-            assertEquals(BaseObjectPoolConfig.DEFAULT_MIN_EVICTABLE_IDLE_TIME,
-                    dummyPool.getMinEvictableIdleDuration());
-            assertEquals(BaseObjectPoolConfig.DEFAULT_NUM_TESTS_PER_EVICTION_RUN,
-                    dummyPool.getNumTestsPerEvictionRun());
-            assertEquals(Boolean.valueOf(BaseObjectPoolConfig.DEFAULT_TEST_ON_BORROW),
-                    Boolean.valueOf(dummyPool.getTestOnBorrow()));
-            assertEquals(Boolean.valueOf(BaseObjectPoolConfig.DEFAULT_TEST_ON_RETURN),
-                    Boolean.valueOf(dummyPool.getTestOnReturn()));
-            assertEquals(Boolean.valueOf(BaseObjectPoolConfig.DEFAULT_TEST_WHILE_IDLE),
-                    Boolean.valueOf(dummyPool.getTestWhileIdle()));
-            assertEquals(BaseObjectPoolConfig.DEFAULT_TIME_BETWEEN_EVICTION_RUNS,
-                    dummyPool.getDurationBetweenEvictionRuns());
-            assertEquals(BaseObjectPoolConfig.DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS,
-                    dummyPool.getTimeBetweenEvictionRunsMillis());
-            assertEquals(BaseObjectPoolConfig.DEFAULT_TIME_BETWEEN_EVICTION_RUNS,
-                    dummyPool.getTimeBetweenEvictionRuns());
-            assertEquals(Boolean.valueOf(BaseObjectPoolConfig.DEFAULT_BLOCK_WHEN_EXHAUSTED),
-                    Boolean.valueOf(dummyPool.getBlockWhenExhausted()));
-            assertEquals(Boolean.valueOf(BaseObjectPoolConfig.DEFAULT_LIFO), Boolean.valueOf(dummyPool.getLifo()));
-        }
-
-        final GenericObjectPoolConfig<Object> config = new GenericObjectPoolConfig<>();
-        config.setLifo(lifo);
-        config.setMaxIdle(maxIdle);
-        config.setMinIdle(minIdle);
-        config.setMaxTotal(maxTotal);
-        config.setMaxWait(maxWaitDuration);
-        config.setMinEvictableIdleTimeMillis(minEvictableIdleMillis);
-        assertEquals(minEvictableIdleMillis, config.getMinEvictableIdleTime().toMillis());
-        config.setNumTestsPerEvictionRun(numTestsPerEvictionRun);
-        config.setTestOnBorrow(testOnBorrow);
-        config.setTestOnReturn(testOnReturn);
-        config.setTestWhileIdle(testWhileIdle);
-        config.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
-        assertEquals(timeBetweenEvictionRunsMillis, config.getTimeBetweenEvictionRuns().toMillis());
-        config.setBlockWhenExhausted(blockWhenExhausted);
-        try (GenericObjectPool<Object, RuntimeException> dummyPool = new GenericObjectPool<>(dummyFactory, config)) {
-            assertEquals(maxIdle, dummyPool.getMaxIdle());
-            assertEquals(maxWaitDuration, dummyPool.getMaxWaitDuration());
-            assertEquals(maxWaitMillis, dummyPool.getMaxWaitMillis());
-            assertEquals(minIdle, dummyPool.getMinIdle());
-            assertEquals(maxTotal, dummyPool.getMaxTotal());
-            assertEquals(minEvictableIdleMillis, dummyPool.getMinEvictableIdleTimeMillis());
-            assertEquals(numTestsPerEvictionRun, dummyPool.getNumTestsPerEvictionRun());
-            assertEquals(Boolean.valueOf(testOnBorrow), Boolean.valueOf(dummyPool.getTestOnBorrow()));
-            assertEquals(Boolean.valueOf(testOnReturn), Boolean.valueOf(dummyPool.getTestOnReturn()));
-            assertEquals(Boolean.valueOf(testWhileIdle), Boolean.valueOf(dummyPool.getTestWhileIdle()));
-            assertEquals(timeBetweenEvictionRunsMillis, dummyPool.getTimeBetweenEvictionRunsMillis());
-            assertEquals(Boolean.valueOf(blockWhenExhausted), Boolean.valueOf(dummyPool.getBlockWhenExhausted()));
-            assertEquals(Boolean.valueOf(lifo), Boolean.valueOf(dummyPool.getLifo()));
-        }
-    }
-
-    @Test
-    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
-    public void testDefaultConfiguration() {
-        assertConfiguration(new GenericObjectPoolConfig<>(),genericObjectPool);
-    }
-
-    /**
-     * Verifies that when a factory's makeObject produces instances that are not
-     * discernible by equals, the pool can handle them.
-     *
-     * JIRA: POOL-283
-     */
-    @Test
-    public void testEqualsIndiscernible() throws Exception {
-        final HashSetFactory factory = new HashSetFactory();
-        try (final GenericObjectPool<HashSet<String>, RuntimeException> pool = new GenericObjectPool<>(factory,
-                new GenericObjectPoolConfig<>())) {
-            final HashSet<String> s1 = pool.borrowObject();
-            final HashSet<String> s2 = pool.borrowObject();
-            pool.returnObject(s1);
-            pool.returnObject(s2);
-        }
-    }
-
-    @Test
-    public void testErrorFactoryDoesNotBlockThreads() throws Exception {
-
-        final CreateErrorFactory factory = new CreateErrorFactory();
-        try (final GenericObjectPool<String, InterruptedException> createFailFactoryPool = new GenericObjectPool<>(factory)) {
-
-            createFailFactoryPool.setMaxTotal(1);
-
-            // Try and borrow the first object from the pool
-            final WaitingTestThread<InterruptedException> thread1 = new WaitingTestThread<>(createFailFactoryPool, 0);
-            thread1.start();
-
-            // Wait for thread to reach semaphore
-            while (!factory.hasQueuedThreads()) {
-                Thread.sleep(200);
-            }
-
-            // Try and borrow the second object from the pool
-            final WaitingTestThread<InterruptedException> thread2 = new WaitingTestThread<>(createFailFactoryPool, 0);
-            thread2.start();
-            // Pool will not call factory since maximum number of object creations
-            // are already queued.
-
-            // Thread 2 will wait on an object being returned to the pool
-            // Give thread 2 a chance to reach this state
-            Thread.sleep(1000);
-
-            // Release thread1
-            factory.release();
-            // Pre-release thread2
-            factory.release();
-
-            // Both threads should now complete.
-            boolean threadRunning = true;
-            int count = 0;
-            while (threadRunning && count < 15) {
-                threadRunning = thread1.isAlive();
-                threadRunning = thread2.isAlive();
-                Thread.sleep(200);
-                count++;
-            }
-            assertFalse(thread1.isAlive());
-            assertFalse(thread2.isAlive());
-
-            assertTrue(thread1.thrown instanceof UnknownError);
-            assertTrue(thread2.thrown instanceof UnknownError);
-        }
-    }
-
-    /**
-     * Tests addObject contention between ensureMinIdle triggered by
-     * the Evictor with minIdle &gt; 0 and borrowObject.
-     *
-     * @throws Exception May occur in some failure modes
-     */
-    @Test
-    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
-    public void testEvictAddObjects() throws Exception {
-        simpleFactory.setMakeLatency(300);
-        simpleFactory.setMaxTotal(2);
-        genericObjectPool.setMaxTotal(2);
-        genericObjectPool.setMinIdle(1);
-        genericObjectPool.borrowObject(); // numActive = 1, numIdle = 0
-        // Create a test thread that will run once and try a borrow after
-        // 150ms fixed delay
-        final TestThread<String, TestException> borrower = new TestThread<>(genericObjectPool, 1, 150, false);
-        final Thread borrowerThread = new Thread(borrower);
-        // Set evictor to run in 100 ms - will create idle instance
-        genericObjectPool.setTimeBetweenEvictionRuns(Duration.ofMillis(100));
-        borrowerThread.start();  // Off to the races
-        borrowerThread.join();
-        assertFalse(borrower.failed());
-    }
-
-    @Test
-    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
-    public void testEvictFIFO() throws Exception {
-        checkEvict(false);
-    }
-
-    @Test
-    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
-    public void testEviction() throws Exception {
-        genericObjectPool.setMaxIdle(500);
-        genericObjectPool.setMaxTotal(500);
-        genericObjectPool.setNumTestsPerEvictionRun(100);
-        genericObjectPool.setMinEvictableIdleTime(Duration.ofMillis(250));
-        genericObjectPool.setTimeBetweenEvictionRuns(Duration.ofMillis(500));
-        genericObjectPool.setTestWhileIdle(true);
-
-        final String[] active = new String[500];
-        for (int i = 0; i < 500; i++) {
-            active[i] = genericObjectPool.borrowObject();
-        }
-        for (int i = 0; i < 500; i++) {
-            genericObjectPool.returnObject(active[i]);
-        }
-
-        Waiter.sleepQuietly(1000L);
-        assertTrue(genericObjectPool.getNumIdle() < 500,"Should be less than 500 idle, found " + genericObjectPool.getNumIdle());
-        Waiter.sleepQuietly(600L);
-        assertTrue(genericObjectPool.getNumIdle() < 400,"Should be less than 400 idle, found " + genericObjectPool.getNumIdle());
-        Waiter.sleepQuietly(600L);
-        assertTrue(genericObjectPool.getNumIdle() < 300,"Should be less than 300 idle, found " + genericObjectPool.getNumIdle());
-        Waiter.sleepQuietly(600L);
-        assertTrue(genericObjectPool.getNumIdle() < 200,"Should be less than 200 idle, found " + genericObjectPool.getNumIdle());
-        Waiter.sleepQuietly(600L);
-        assertTrue(genericObjectPool.getNumIdle() < 100,"Should be less than 100 idle, found " + genericObjectPool.getNumIdle());
-        Waiter.sleepQuietly(600L);
-        assertEquals(0,genericObjectPool.getNumIdle(),"Should be zero idle, found " + genericObjectPool.getNumIdle());
-
-        for (int i = 0; i < 500; i++) {
-            active[i] = genericObjectPool.borrowObject();
-        }
-        for (int i = 0; i < 500; i++) {
-            genericObjectPool.returnObject(active[i]);
-        }
-
-        Waiter.sleepQuietly(1000L);
-        assertTrue(genericObjectPool.getNumIdle() < 500,"Should be less than 500 idle, found " + genericObjectPool.getNumIdle());
-        Waiter.sleepQuietly(600L);
-        assertTrue(genericObjectPool.getNumIdle() < 400,"Should be less than 400 idle, found " + genericObjectPool.getNumIdle());
-        Waiter.sleepQuietly(600L);
-        assertTrue(genericObjectPool.getNumIdle() < 300,"Should be less than 300 idle, found " + genericObjectPool.getNumIdle());
-        Waiter.sleepQuietly(600L);
-        assertTrue(genericObjectPool.getNumIdle() < 200,"Should be less than 200 idle, found " + genericObjectPool.getNumIdle());
-        Waiter.sleepQuietly(600L);
-        assertTrue(genericObjectPool.getNumIdle() < 100,"Should be less than 100 idle, found " + genericObjectPool.getNumIdle());
-        Waiter.sleepQuietly(600L);
-        assertEquals(0,genericObjectPool.getNumIdle(),"Should be zero idle, found " + genericObjectPool.getNumIdle());
-    }
-
-    @Test
-    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
-    public void testEvictionInvalid() throws Exception {
-
-        try (final GenericObjectPool<Object, RuntimeException> invalidFactoryPool = new GenericObjectPool<>(new InvalidFactory())) {
-
-            invalidFactoryPool.setMaxIdle(1);
-            invalidFactoryPool.setMaxTotal(1);
-            invalidFactoryPool.setTestOnBorrow(false);
-            invalidFactoryPool.setTestOnReturn(false);
-            invalidFactoryPool.setTestWhileIdle(true);
-            invalidFactoryPool.setMinEvictableIdleTime(Duration.ofSeconds(100));
-            invalidFactoryPool.setNumTestsPerEvictionRun(1);
-
-            final Object p = invalidFactoryPool.borrowObject();
-            invalidFactoryPool.returnObject(p);
-
-            // Run eviction in a separate thread
-            final Thread t = new EvictionThread<>(invalidFactoryPool);
-            t.start();
-
-            // Sleep to make sure evictor has started
-            Thread.sleep(300);
-
-            try {
-                invalidFactoryPool.borrowObject(1);
-            } catch (final NoSuchElementException nsee) {
-                // Ignore
-            }
-
-            // Make sure evictor has finished
-            Thread.sleep(1000);
-
-            // Should have an empty pool
-            assertEquals( 0, invalidFactoryPool.getNumIdle(),"Idle count different than expected.");
-            assertEquals( 0, invalidFactoryPool.getNumActive(),"Total count different than expected.");
-        }
-    }
-
-    /**
-     * Test to make sure evictor visits least recently used objects first,
-     * regardless of FIFO/LIFO.
-     *
-     * JIRA: POOL-86
-     *
-     * @throws Exception May occur in some failure modes
-     */
-    @Test
-    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
-    public void testEvictionOrder() throws Exception {
-        checkEvictionOrder(false);
-        tearDown();
-        setUp();
-        checkEvictionOrder(true);
-    }
-
-    @Test
-    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
-    public void testEvictionPolicy() throws Exception {
-        genericObjectPool.setMaxIdle(500);
-        genericObjectPool.setMaxTotal(500);
-        genericObjectPool.setNumTestsPerEvictionRun(500);
-        genericObjectPool.setMinEvictableIdleTime(Duration.ofMillis(250));
-        genericObjectPool.setTimeBetweenEvictionRuns(Duration.ofMillis(500));
-        genericObjectPool.setTestWhileIdle(true);
-
-        // ClassNotFoundException
-        assertThrows(IllegalArgumentException.class, () -> genericObjectPool.setEvictionPolicyClassName(Long.toString(System.currentTimeMillis())),
-                "setEvictionPolicyClassName must throw an error if the class name is invalid.");
-
-        // InstantiationException
-        assertThrows(IllegalArgumentException.class, () -> genericObjectPool.setEvictionPolicyClassName(java.io.Serializable.class.getName()),
-                "setEvictionPolicyClassName must throw an error if the class name is invalid.");
-
-        // IllegalAccessException
-        assertThrows(IllegalArgumentException.class, () -> genericObjectPool.setEvictionPolicyClassName(java.util.Collections.class.getName()),
-                "setEvictionPolicyClassName must throw an error if the class name is invalid.");
-
-        assertThrows(IllegalArgumentException.class, () -> genericObjectPool.setEvictionPolicyClassName(java.lang.String.class.getName()),
-                () -> "setEvictionPolicyClassName must throw an error if a class that does not implement EvictionPolicy is specified.");
-
-        genericObjectPool.setEvictionPolicy(new TestEvictionPolicy<>());
-        assertEquals(TestEvictionPolicy.class.getName(), genericObjectPool.getEvictionPolicyClassName());
-
-        genericObjectPool.setEvictionPolicyClassName(TestEvictionPolicy.class.getName());
-        assertEquals(TestEvictionPolicy.class.getName(), genericObjectPool.getEvictionPolicyClassName());
-
-        final String[] active = new String[500];
-        for (int i = 0; i < 500; i++) {
-            active[i] = genericObjectPool.borrowObject();
-        }
-        for (int i = 0; i < 500; i++) {
-            genericObjectPool.returnObject(active[i]);
-        }
-
-        // Eviction policy ignores first 1500 attempts to evict and then always
-        // evicts. After 1s, there should have been two runs of 500 tests so no
-        // evictions
-        Waiter.sleepQuietly(1000L);
-        assertEquals(500, genericObjectPool.getNumIdle(), "Should be 500 idle");
-        // A further 1s wasn't enough so allow 2s for the evictor to clear out
-        // all of the idle objects.
-        Waiter.sleepQuietly(2000L);
-        assertEquals(0, genericObjectPool.getNumIdle(), "Should be 0 idle");
-    }
-
-    @Test
-    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
-    public void testEvictionSoftMinIdle() throws Exception {
-        class TimeTest extends BasePooledObjectFactory<TimeTest, RuntimeException> {
-            private final long createTimeMillis;
-
-            public TimeTest() {
-                createTimeMillis = System.currentTimeMillis();
-            }
-
-            @Override
-            public TimeTest create() {
-                return new TimeTest();
-            }
-
-            public long getCreateTimeMillis() {
-                return createTimeMillis;
-            }
-
-            @Override
-            public PooledObject<TimeTest> wrap(final TimeTest value) {
-                return new DefaultPooledObject<>(value);
-            }
-        }
-
-        try (final GenericObjectPool<TimeTest, RuntimeException> timePool = new GenericObjectPool<>(new TimeTest())) {
-
-            timePool.setMaxIdle(5);
-            timePool.setMaxTotal(5);
-            timePool.setNumTestsPerEvictionRun(5);
-            timePool.setMinEvictableIdle(Duration.ofSeconds(3));
-            timePool.setMinEvictableIdleTime(Duration.ofSeconds(3));
-            timePool.setSoftMinEvictableIdleTime(TestConstants.ONE_SECOND_DURATION);
-            timePool.setMinIdle(2);
-
-            final TimeTest[] active = new TimeTest[5];
-            final Long[] creationTime = new Long[5];
-            for (int i = 0; i < 5; i++) {
-                active[i] = timePool.borrowObject();
-                creationTime[i] = Long.valueOf((active[i]).getCreateTimeMillis());
-            }
-
-            for (int i = 0; i < 5; i++) {
-                timePool.returnObject(active[i]);
-            }
-
-            // Soft evict all but minIdle(2)
-            Thread.sleep(1500L);
-            timePool.evict();
-            assertEquals( 2, timePool.getNumIdle(),"Idle count different than expected.");
-
-            // Hard evict the rest.
-            Thread.sleep(2000L);
-            timePool.evict();
-            assertEquals( 0, timePool.getNumIdle(),"Idle count different than expected.");
-        }
-    }
-
-    @Test
-    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
-    public void testEvictionWithNegativeNumTests() throws Exception {
-        // when numTestsPerEvictionRun is negative, it represents a fraction of the idle objects to test
-        genericObjectPool.setMaxIdle(6);
-        genericObjectPool.setMaxTotal(6);
-        genericObjectPool.setNumTestsPerEvictionRun(-2);
-        genericObjectPool.setMinEvictableIdleTime(Duration.ofMillis(50));
-        genericObjectPool.setTimeBetweenEvictionRuns(Duration.ofMillis(100));
-
-        final String[] active = new String[6];
-        for (int i = 0; i < 6; i++) {
-            active[i] = genericObjectPool.borrowObject();
-        }
-        for (int i = 0; i < 6; i++) {
-            genericObjectPool.returnObject(active[i]);
-        }
-
-        Waiter.sleepQuietly(100L);
-        assertTrue(genericObjectPool.getNumIdle() <= 6,"Should at most 6 idle, found " + genericObjectPool.getNumIdle());
-        Waiter.sleepQuietly(100L);
-        assertTrue(genericObjectPool.getNumIdle() <= 3,"Should at most 3 idle, found " + genericObjectPool.getNumIdle());
-        Waiter.sleepQuietly(100L);
-        assertTrue(genericObjectPool.getNumIdle() <= 2,"Should be at most 2 idle, found " + genericObjectPool.getNumIdle());
-        Waiter.sleepQuietly(100L);
-        assertEquals(0,genericObjectPool.getNumIdle(),"Should be zero idle, found " + genericObjectPool.getNumIdle());
-    }
-
-    @Test
-    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
-    public void testEvictLIFO() throws Exception {
-        checkEvict(true);
-    }
-
-    /**
-     * Verifies that the evictor visits objects in expected order
-     * and frequency.
-     *
-     * @throws Exception May occur in some failure modes
-     */
-    @Test
-    public void testEvictorVisiting() throws Exception {
-        checkEvictorVisiting(true);
-        checkEvictorVisiting(false);
-    }
-
-    @Test
-    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
-    public void testEvictWhileEmpty() throws Exception {
-        genericObjectPool.evict();
-        genericObjectPool.evict();
-        genericObjectPool.close();
-    }
-
-    @Test
-    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
-    public void testExceptionInValidationDuringEviction() throws Exception {
-        genericObjectPool.setMaxIdle(1);
-        genericObjectPool.setMinEvictableIdleTime(Duration.ZERO);
-        genericObjectPool.setTestWhileIdle(true);
-
-        final String active = genericObjectPool.borrowObject();
-        genericObjectPool.returnObject(active);
-
-        simpleFactory.setThrowExceptionOnValidate(true);
-
-        assertThrows(RuntimeException.class, () -> genericObjectPool.evict());
-        assertEquals(0, genericObjectPool.getNumActive());
-        assertEquals(0, genericObjectPool.getNumIdle());
-    }
-
-    @Test
-    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
-    public void testExceptionOnActivateDuringBorrow() throws Exception {
-        final String obj1 = genericObjectPool.borrowObject();
-        final String obj2 = genericObjectPool.borrowObject();
-        genericObjectPool.returnObject(obj1);
-        genericObjectPool.returnObject(obj2);
-        simpleFactory.setThrowExceptionOnActivate(true);
-        simpleFactory.setEvenValid(false);
-        // Activation will now throw every other time
-        // First attempt throws, but loop continues and second succeeds
-        final String obj = genericObjectPool.borrowObject();
-        assertEquals(1, genericObjectPool.getNumActive());
-        assertEquals(0, genericObjectPool.getNumIdle());
-
-        genericObjectPool.returnObject(obj);
-        simpleFactory.setValid(false);
-        // Validation will now fail on activation when borrowObject returns
-        // an idle instance, and then when attempting to create a new instance
-        assertThrows(NoSuchElementException.class, () -> genericObjectPool.borrowObject());
-        assertEquals(0, genericObjectPool.getNumActive());
-        assertEquals(0, genericObjectPool.getNumIdle());
-    }
-
-    @Test
-    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
-    public void testExceptionOnDestroyDuringBorrow() throws Exception {
-        simpleFactory.setThrowExceptionOnDestroy(true);
-        genericObjectPool.setTestOnBorrow(true);
-        genericObjectPool.borrowObject();
-        simpleFactory.setValid(false); // Make validation fail on next borrow attempt
-        assertThrows(NoSuchElementException.class, () -> genericObjectPool.borrowObject());
-        assertEquals(1, genericObjectPool.getNumActive());
-        assertEquals(0, genericObjectPool.getNumIdle());
-    }
-
-    @Test
-    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
-    public void testExceptionOnDestroyDuringReturn() throws Exception {
-        simpleFactory.setThrowExceptionOnDestroy(true);
-        genericObjectPool.setTestOnReturn(true);
-        final String obj1 = genericObjectPool.borrowObject();
-        genericObjectPool.borrowObject();
-        simpleFactory.setValid(false); // Make validation fail
-        genericObjectPool.returnObject(obj1);
-        assertEquals(1, genericObjectPool.getNumActive());
-        assertEquals(0, genericObjectPool.getNumIdle());
-    }
-
-    @Test
-    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
-    public void testExceptionOnPassivateDuringReturn() throws Exception {
-        final String obj = genericObjectPool.borrowObject();
-        simpleFactory.setThrowExceptionOnPassivate(true);
-        genericObjectPool.returnObject(obj);
-        assertEquals(0,genericObjectPool.getNumIdle());
-    }
-
-    @Test
-    public void testFailingFactoryDoesNotBlockThreads() throws Exception {
-
-        final CreateFailFactory factory = new CreateFailFactory();
-        try (final GenericObjectPool<String, InterruptedException> createFailFactoryPool = new GenericObjectPool<>(factory)) {
-
-            createFailFactoryPool.setMaxTotal(1);
-
-            // Try and borrow the first object from the pool
-            final WaitingTestThread<InterruptedException> thread1 = new WaitingTestThread<>(createFailFactoryPool, 0);
-            thread1.start();
-
-            // Wait for thread to reach semaphore
-            while (!factory.hasQueuedThreads()) {
-                Thread.sleep(200);
-            }
-
-            // Try and borrow the second object from the pool
-            final WaitingTestThread<InterruptedException> thread2 = new WaitingTestThread<>(createFailFactoryPool, 0);
-            thread2.start();
-            // Pool will not call factory since maximum number of object creations
-            // are already queued.
-
-            // Thread 2 will wait on an object being returned to the pool
-            // Give thread 2 a chance to reach this state
-            Thread.sleep(1000);
-
-            // Release thread1
-            factory.release();
-            // Pre-release thread2
-            factory.release();
-
-            // Both threads should now complete.
-            boolean threadRunning = true;
-            int count = 0;
-            while (threadRunning && count < 15) {
-                threadRunning = thread1.isAlive();
-                threadRunning = thread2.isAlive();
-                Thread.sleep(200);
-                count++;
-            }
-            assertFalse(thread1.isAlive());
-            assertFalse(thread2.isAlive());
-
-            assertTrue(thread1.thrown instanceof UnsupportedCharsetException);
-            assertTrue(thread2.thrown instanceof UnsupportedCharsetException);
-        }
-    }
-
-    @Test
-    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
-    public void testFIFO() throws Exception {
-        genericObjectPool.setLifo(false);
-        genericObjectPool.addObject(); // "0"
-        genericObjectPool.addObject(); // "1"
-        genericObjectPool.addObject(); // "2"
-        assertEquals( "0", genericObjectPool.borrowObject(),"Oldest");
-        assertEquals( "1", genericObjectPool.borrowObject(),"Middle");
-        assertEquals( "2", genericObjectPool.borrowObject(),"Youngest");
-        final String o = genericObjectPool.borrowObject();
-        assertEquals( "3", o,"new-3");
-        genericObjectPool.returnObject(o);
-        assertEquals( o, genericObjectPool.borrowObject(),"returned-3");
-        assertEquals( "4", genericObjectPool.borrowObject(),"new-4");
-    }
-
-    @Test
-    public void testGetFactoryType_DefaultPooledObjectFactory() {
-        try (final GenericObjectPool<String, RuntimeException> pool = new GenericObjectPool<>(createDefaultPooledObjectFactory())) {
-            assertNotNull((pool.getFactoryType()));
-        }
-    }
-
-    @Test
-    public void testGetFactoryType_NullPooledObjectFactory() {
-        try (final GenericObjectPool<String, RuntimeException> pool = new GenericObjectPool<>(createNullPooledObjectFactory())) {
-            assertNotNull((pool.getFactoryType()));
-        }
-    }
-
-    @Test
-    public void testGetFactoryType_PoolUtilsSynchronizedDefaultPooledFactory() {
-        try (final GenericObjectPool<String, RuntimeException> pool = new GenericObjectPool<>(
-                PoolUtils.synchronizedPooledFactory(createDefaultPooledObjectFactory()))) {
-            assertNotNull((pool.getFactoryType()));
-        }
-    }
-
-    @Test
-    public void testGetFactoryType_PoolUtilsSynchronizedNullPooledFactory() {
-        try (final GenericObjectPool<String, RuntimeException> pool = new GenericObjectPool<>(
-                PoolUtils.synchronizedPooledFactory(createNullPooledObjectFactory()))) {
-            assertNotNull((pool.getFactoryType()));
-        }
-    }
-
-    @Test
-    public void testGetFactoryType_SynchronizedDefaultPooledObjectFactory() {
-        try (final GenericObjectPool<String, RuntimeException> pool = new GenericObjectPool<>(
-                new TestSynchronizedPooledObjectFactory<>(createDefaultPooledObjectFactory()))) {
-            assertNotNull((pool.getFactoryType()));
-        }
-    }
-
-    @Test
-    public void testGetFactoryType_SynchronizedNullPooledObjectFactory() {
-        try (final GenericObjectPool<String, RuntimeException> pool = new GenericObjectPool<>(
-                new TestSynchronizedPooledObjectFactory<>(createNullPooledObjectFactory()))) {
-            assertNotNull((pool.getFactoryType()));
-        }
-    }
-
-    @Test
-    public void testGetStatsString() {
-        try (final GenericObjectPool<String, RuntimeException> pool = new GenericObjectPool<>(
-                new TestSynchronizedPooledObjectFactory<>(createNullPooledObjectFactory()))) {
-            assertNotNull(pool.getStatsString());
-        }
-    }
-
-    /**
-     * Verify that threads waiting on a depleted pool get served when a checked out object is
-     * invalidated.
-     *
-     * JIRA: POOL-240
-     *
-     * @throws Exception May occur in some failure modes
-     */
-    @Test
-    public void testInvalidateFreesCapacity() throws Exception {
-        final SimpleFactory factory = new SimpleFactory();
-        try (final GenericObjectPool<String, TestException> pool = new GenericObjectPool<>(factory)) {
-            pool.setMaxTotal(2);
-            pool.setMaxWaitMillis(500);
-            // Borrow an instance and hold if for 5 seconds
-            final WaitingTestThread<TestException> thread1 = new WaitingTestThread<>(pool, 5000);
-            thread1.start();
-            // Borrow another instance
-            final String obj = pool.borrowObject();
-            // Launch another thread - will block, but fail in 500 ms
-            final WaitingTestThread<TestException> thread2 = new WaitingTestThread<>(pool, 100);
-            thread2.start();
-            // Invalidate the object borrowed by this thread - should allow thread2 to create
-            Thread.sleep(20);
-            pool.invalidateObject(obj);
-            Thread.sleep(600); // Wait for thread2 to timeout
-            if (thread2.thrown != null) {
-                fail(thread2.thrown.toString());
-            }
-        }
-    }
-
-    /**
-     * Ensure the pool is registered.
-     */
-    @Test
-    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
-    public void testJmxRegistration() {
-        final ObjectName oname = genericObjectPool.getJmxName();
-        final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
-        final Set<ObjectName> result = mbs.queryNames(oname, null);
-        assertEquals(1, result.size());
-        genericObjectPool.jmxUnregister();
-
-        final GenericObjectPoolConfig<String> config = new GenericObjectPoolConfig<>();
-        config.setJmxEnabled(false);
-        try (final GenericObjectPool<String, TestException> poolWithoutJmx = new GenericObjectPool<>(simpleFactory, config)) {
-            assertNull(poolWithoutJmx.getJmxName());
-            config.setJmxEnabled(true);
-            poolWithoutJmx.jmxUnregister();
-        }
-
-        config.setJmxNameBase(null);
-        try (final GenericObjectPool<String, TestException> poolWithDefaultJmxNameBase = new GenericObjectPool<>(simpleFactory, config)) {
-            assertNotNull(poolWithDefaultJmxNameBase.getJmxName());
-        }
-    }
-
-    @Test
-    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
-    public void testLIFO() throws Exception {
-        final String o;
-        genericObjectPool.setLifo(true);
-        genericObjectPool.addObject(); // "0"
-        genericObjectPool.addObject(); // "1"
-        genericObjectPool.addObject(); // "2"
-        assertEquals( "2", genericObjectPool.borrowObject(),"Youngest");
-        assertEquals( "1", genericObjectPool.borrowObject(),"Middle");
-        assertEquals( "0", genericObjectPool.borrowObject(),"Oldest");
-        o = genericObjectPool.borrowObject();
-        assertEquals( "3", o,"new-3");
-        genericObjectPool.returnObject(o);
-        assertEquals( o, genericObjectPool.borrowObject(),"returned-3");
-        assertEquals( "4", genericObjectPool.borrowObject(),"new-4");
-    }
-
-    /**
-     * Test the following scenario:
-     *   Thread 1 borrows an instance
-     *   Thread 2 starts to borrow another instance before thread 1 returns its instance
-     *   Thread 1 returns its instance while thread 2 is validating its newly created instance
-     * The test verifies that the instance created by Thread 2 is not leaked.
-     *
-     * @throws Exception May occur in some failure modes
-     */
-    @Test
-    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
-    public void testMakeConcurrentWithReturn() throws Exception {
-        genericObjectPool.setTestOnBorrow(true);
-        simpleFactory.setValid(true);
-        // Borrow and return an instance, with a short wait
-        final WaitingTestThread<TestException> thread1 = new WaitingTestThread<>(genericObjectPool, 200);
-        thread1.start();
-        Thread.sleep(50); // wait for validation to succeed
-        // Slow down validation and borrow an instance
-        simpleFactory.setValidateLatency(400);
-        final String instance = genericObjectPool.borrowObject();
-        // Now make sure that we have not leaked an instance
-        assertEquals(simpleFactory.getMakeCounter(), genericObjectPool.getNumIdle() + 1);
-        genericObjectPool.returnObject(instance);
-        assertEquals(simpleFactory.getMakeCounter(), genericObjectPool.getNumIdle());
-    }
-
-    @Test
-    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
-    public void testMaxIdle() throws Exception {
-        genericObjectPool.setMaxTotal(100);
-        genericObjectPool.setMaxIdle(8);
-        final String[] active = new String[100];
-        for(int i=0;i<100;i++) {
-            active[i] = genericObjectPool.borrowObject();
-        }
-        assertEquals(100,genericObjectPool.getNumActive());
-        assertEquals(0,genericObjectPool.getNumIdle());
-        for(int i=0;i<100;i++) {
-            genericObjectPool.returnObject(active[i]);
-            assertEquals(99 - i,genericObjectPool.getNumActive());
-            assertEquals((i < 8 ? i+1 : 8),genericObjectPool.getNumIdle());
-        }
-    }
-
-    @Test
-    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
-    public void testMaxIdleZero() throws Exception {
-        genericObjectPool.setMaxTotal(100);
-        genericObjectPool.setMaxIdle(0);
-        final String[] active = new String[100];
-        for(int i=0;i<100;i++) {
-            active[i] = genericObjectPool.borrowObject();
-        }
-        assertEquals(100,genericObjectPool.getNumActive());
-        assertEquals(0,genericObjectPool.getNumIdle());
-        for(int i=0;i<100;i++) {
-            genericObjectPool.returnObject(active[i]);
-            assertEquals(99 - i,genericObjectPool.getNumActive());
-            assertEquals(0, genericObjectPool.getNumIdle());
-        }
-    }
-
-    /**
-     * Showcasing a possible deadlock situation as reported in POOL-356
-     */
-    @Test
-    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
-    @SuppressWarnings("rawtypes")
-    public void testMaxIdleZeroUnderLoad() {
-        // Config
-        final int numThreads = 199; // And main thread makes a round 200.
-        final int numIter = 20;
-        final int delay = 25;
-        final int maxTotal = 10;
-
-        simpleFactory.setMaxTotal(maxTotal);
-        genericObjectPool.setMaxTotal(maxTotal);
-        genericObjectPool.setBlockWhenExhausted(true);
-        genericObjectPool.setTimeBetweenEvictionRuns(Duration.ofMillis(-1));
-
-        // this is important to trigger POOL-356
-        genericObjectPool.setMaxIdle(0);
-
-        // Start threads to borrow objects
-        final TestThread[] threads = new TestThread[numThreads];
-        for(int i=0;i<numThreads;i++) {
-            // Factor of 2 on iterations so main thread does work whilst other
-            // threads are running. Factor of 2 on delay so average delay for
-            // other threads == actual delay for main thread
-            threads[i] = new TestThread<>(genericObjectPool, numIter * 2, delay * 2);
-            final Thread t = new Thread(threads[i]);
-            t.start();
-        }
-        // Give the threads a chance to start doing some work
-        Waiter.sleepQuietly(100L);
-
-        for (int i = 0; i < numIter; i++) {
-            String obj = null;
-            try {
-                Waiter.sleepQuietly(delay);
-                obj = genericObjectPool.borrowObject();
-                // Under load, observed numActive > maxTotal
-                if (genericObjectPool.getNumActive() > genericObjectPool.getMaxTotal()) {
-                    throw new IllegalStateException("Too many active objects");
-                }
-                Waiter.sleepQuietly(delay);
-            } catch (final Exception e) {
-                // Shouldn't happen
-                e.printStackTrace();
-                fail("Exception on borrow");
-            } finally {
-                if (obj != null) {
-                    try {
-                        genericObjectPool.returnObject(obj);
-                    } catch (final Exception e) {
-                        // Ignore
-                    }
-                }
-            }
-        }
-
-        for (int i = 0; i < numThreads; i++) {
-            while (!(threads[i]).complete()) {
-                Waiter.sleepQuietly(500L);
-            }
-            if (threads[i].failed()) {
-                threads[i].error.printStackTrace();
-                fail("Thread " + i + " failed: " + threads[i].error.toString());
-            }
-        }
-    }
-
-    @Test
-    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
-    public void testMaxTotal() throws Exception {
-        genericObjectPool.setMaxTotal(3);
-        genericObjectPool.setBlockWhenExhausted(false);
-
-        genericObjectPool.borrowObject();
-        genericObjectPool.borrowObject();
-        genericObjectPool.borrowObject();
-        assertThrows(NoSuchElementException.class, () -> genericObjectPool.borrowObject());
-    }
-
-    /**
-     * Verifies that maxTotal is not exceeded when factory destroyObject
-     * has high latency, testOnReturn is set and there is high incidence of
-     * validation failures.
-     */
-    @Test
-    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
-    public void testMaxTotalInvariant() {
-        final int maxTotal = 15;
-        simpleFactory.setEvenValid(false);     // Every other validation fails
-        simpleFactory.setDestroyLatency(100);  // Destroy takes 100 ms
-        simpleFactory.setMaxTotal(maxTotal); // (makes - destroys) bound
-        simpleFactory.setValidationEnabled(true);
-        genericObjectPool.setMaxTotal(maxTotal);
-        genericObjectPool.setMaxIdle(-1);
-        genericObjectPool.setTestOnReturn(true);
-        genericObjectPool.setMaxWaitMillis(1000L);
-        runTestThreads(5, 10, 50, genericObjectPool);
-    }
-
-    @Test
-    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
-    @SuppressWarnings("rawtypes")
-    public void testMaxTotalUnderLoad() {
-        // Config
-        final int numThreads = 199; // And main thread makes a round 200.
-        final int numIter = 20;
-        final int delay = 25;
-        final int maxTotal = 10;
-
-        simpleFactory.setMaxTotal(maxTotal);
-        genericObjectPool.setMaxTotal(maxTotal);
-        genericObjectPool.setBlockWhenExhausted(true);
-        genericObjectPool.setTimeBetweenEvictionRuns(Duration.ofMillis(-1));
-
-        // Start threads to borrow objects
-        final TestThread[] threads = new TestThread[numThreads];
-        for(int i=0;i<numThreads;i++) {
-            // Factor of 2 on iterations so main thread does work whilst other
-            // threads are running. Factor of 2 on delay so average delay for
-            // other threads == actual delay for main thread
-            threads[i] = new TestThread<>(genericObjectPool, numIter * 2, delay * 2);
-            final Thread t = new Thread(threads[i]);
-            t.start();
-        }
-        // Give the threads a chance to start doing some work
-        Waiter.sleepQuietly(5000);
-
-        for (int i = 0; i < numIter; i++) {
-            String obj = null;
-            try {
-                Waiter.sleepQuietly(delay);
-                obj = genericObjectPool.borrowObject();
-                // Under load, observed numActive > maxTotal
-                if (genericObjectPool.getNumActive() > genericObjectPool.getMaxTotal()) {
-                    throw new IllegalStateException("Too many active objects");
-                }
-                Waiter.sleepQuietly(delay);
-            } catch (final Exception e) {
-                // Shouldn't happen
-                e.printStackTrace();
-                fail("Exception on borrow");
-            } finally {
-                if (obj != null) {
-                    try {
-                        genericObjectPool.returnObject(obj);
-                    } catch (final Exception e) {
-                        // Ignore
-                    }
-                }
-            }
-        }
-
-        for (int i = 0; i < numThreads; i++) {
-            while(!(threads[i]).complete()) {
-                Waiter.sleepQuietly(500L);
-            }
-            if(threads[i].failed()) {
-                fail("Thread " + i + " failed: " + threads[i].error.toString());
-            }
-        }
-    }
-
-    @Test
-    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
-    public void testMaxTotalZero() throws Exception {
-        genericObjectPool.setMaxTotal(0);
-        genericObjectPool.setBlockWhenExhausted(false);
-        assertThrows(NoSuchElementException.class, () -> genericObjectPool.borrowObject());
-    }
-
-    /*
-     * Test multi-threaded pool access.
-     * Multiple threads, but maxTotal only allows half the threads to succeed.
-     *
-     * This test was prompted by Continuum build failures in the Commons DBCP test case:
-     * TestPerUserPoolDataSource.testMultipleThreads2()
-     * Let's see if the this fails on Continuum too!
-     */
-    @Test
-    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
-    public void testMaxWaitMultiThreaded() throws Exception {
-        final long maxWait = 500; // wait for connection
-        final long holdTime = 2 * maxWait; // how long to hold connection
-        final int threads = 10; // number of threads to grab the object initially
-        genericObjectPool.setBlockWhenExhausted(true);
-        genericObjectPool.setMaxWaitMillis(maxWait);
-        genericObjectPool.setMaxTotal(threads);
-        // Create enough threads so half the threads will have to wait
-        final WaitingTestThread<TestException>[] wtt = new WaitingTestThread[threads * 2];
-        for (int i = 0; i < wtt.length; i++) {
-            wtt[i] = new WaitingTestThread<>(genericObjectPool, holdTime);
-        }
-        final long originMillis = System.currentTimeMillis() - 1000;
-        for (final WaitingTestThread<TestException> element : wtt) {
-            element.start();
-        }
-        int failed = 0;
-        for (final WaitingTestThread<TestException> element : wtt) {
-            element.join();
-            if (element.thrown != null){
-                failed++;
-            }
-        }
-        if (DISPLAY_THREAD_DETAILS || wtt.length/2 != failed){
-            System.out.println(
-                    "MaxWait: " + maxWait +
-                    " HoldTime: " + holdTime +
-                     " MaxTotal: " + threads +
-                    " Threads: " + wtt.length +
-                    " Failed: " + failed
-                    );
-            for (final WaitingTestThread<TestException> wt : wtt) {
-                System.out.println(
-                        "PreBorrow: " + (wt.preBorrowMillis - originMillis) +
-                        " PostBorrow: " + (wt.postBorrowMillis != 0 ? wt.postBorrowMillis - originMillis : -1) +
-                        " BorrowTime: " + (wt.postBorrowMillis != 0 ? wt.postBorrowMillis - wt.preBorrowMillis : -1) +
-                        " PostReturn: " + (wt.postReturnMillis != 0 ? wt.postReturnMillis - originMillis : -1) +
-                        " Ended: " + (wt.endedMillis - originMillis) +
-                        " ObjId: " + wt.objectId
-                        );
-            }
-        }
-        assertEquals(wtt.length / 2, failed,"Expected half the threads to fail");
-    }
-
-    @Test
-    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
-    public void testMinIdle() throws Exception {
-        genericObjectPool.setMaxIdle(500);
-        genericObjectPool.setMinIdle(5);
-        genericObjectPool.setMaxTotal(10);
-        genericObjectPool.setNumTestsPerEvictionRun(0);
-        genericObjectPool.setMinEvictableIdleTime(Duration.ofMillis(50));
-        genericObjectPool.setTimeBetweenEvictionRuns(Duration.ofMillis(100));
-        genericObjectPool.setTestWhileIdle(true);
-
-        Waiter.sleepQuietly(150L);
-        assertEquals(5, genericObjectPool.getNumIdle(), "Should be 5 idle, found " + genericObjectPool.getNumIdle());
-
-        final String[] active = new String[5];
-        active[0] = genericObjectPool.borrowObject();
-
-        Waiter.sleepQuietly(150L);
-        assertEquals(5, genericObjectPool.getNumIdle(), "Should be 5 idle, found " + genericObjectPool.getNumIdle());
-
-        for (int i = 1; i < 5; i++) {
-            active[i] = genericObjectPool.borrowObject();
-        }
-
-        Waiter.sleepQuietly(150L);
-        assertEquals(5, genericObjectPool.getNumIdle(), "Should be 5 idle, found " + genericObjectPool.getNumIdle());
-
-        for (int i = 0; i < 5; i++) {
-            genericObjectPool.returnObject(active[i]);
-        }
-
-        Waiter.sleepQuietly(150L);
-        assertEquals(10, genericObjectPool.getNumIdle(), "Should be 10 idle, found " + genericObjectPool.getNumIdle());
-    }
-
-    @Test
-    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
-    public void testMinIdleMaxTotal() throws Exception {
-        genericObjectPool.setMaxIdle(500);
-        genericObjectPool.setMinIdle(5);
-        genericObjectPool.setMaxTotal(10);
-        genericObjectPool.setNumTestsPerEvictionRun(0);
-        genericObjectPool.setMinEvictableIdleTime(Duration.ofMillis(50));
-        genericObjectPool.setTimeBetweenEvictionRuns(Duration.ofMillis(100));
-        genericObjectPool.setTestWhileIdle(true);
-
-        Waiter.sleepQuietly(150L);
-        assertEquals(5, genericObjectPool.getNumIdle(), "Should be 5 idle, found " + genericObjectPool.getNumIdle());
-
-        final String[] active = new String[10];
-
-        Waiter.sleepQuietly(150L);
-        assertEquals(5, genericObjectPool.getNumIdle(), "Should be 5 idle, found " + genericObjectPool.getNumIdle());
-
-        for (int i = 0; i < 5; i++) {
-            active[i] = genericObjectPool.borrowObject();
-        }
-
-        Waiter.sleepQuietly(150L);
-        assertEquals(5, genericObjectPool.getNumIdle(), "Should be 5 idle, found " + genericObjectPool.getNumIdle());
-
-        for(int i = 0 ; i < 5 ; i++) {
-            genericObjectPool.returnObject(active[i]);
-        }
-
-        Waiter.sleepQuietly(150L);
-        assertEquals(10, genericObjectPool.getNumIdle(), "Should be 10 idle, found " + genericObjectPool.getNumIdle());
-
-        for (int i = 0; i < 10; i++) {
-            active[i] = genericObjectPool.borrowObject();
-        }
-
-        Waiter.sleepQuietly(150L);
-        assertEquals(0, genericObjectPool.getNumIdle(), "Should be 0 idle, found " + genericObjectPool.getNumIdle());
-
-        for (int i = 0; i < 10; i++) {
-            genericObjectPool.returnObject(active[i]);
-        }
-
-        Waiter.sleepQuietly(150L);
-        assertEquals(10, genericObjectPool.getNumIdle(), "Should be 10 idle, found " + genericObjectPool.getNumIdle());
-    }
-
-    /**
-     * Verifies that returning an object twice (without borrow in between) causes ISE
-     * but does not re-validate or re-passivate the instance.
-     *
-     * JIRA: POOL-285
-     */
-    @Test
-    public void testMultipleReturn() throws Exception {
-        final WaiterFactory<String> factory = new WaiterFactory<>(0, 0, 0, 0, 0, 0);
-        try (final GenericObjectPool<Waiter, IllegalStateException> pool = new GenericObjectPool<>(factory)) {
-            pool.setTestOnReturn(true);
-            final Waiter waiter = pool.borrowObject();
-            pool.returnObject(waiter);
-            assertEquals(1, waiter.getValidationCount());
-            assertEquals(1, waiter.getPassivationCount());
-            try {
-                pool.returnObject(waiter);
-                fail("Expecting IllegalStateException from multiple return");
-            } catch (final IllegalStateException ex) {
-                // Exception is expected, now check no repeat validation/passivation
-                assertEquals(1, waiter.getValidationCount());
-                assertEquals(1, waiter.getPassivationCount());
-            }
-        }
-    }
-
-    // POOL-248
-    @Test
-    public void testMultipleReturnOfSameObject() throws Exception {
-        try (final GenericObjectPool<String, TestException> pool = new GenericObjectPool<>(simpleFactory, new GenericObjectPoolConfig<>())) {
-
-            assertEquals(0, pool.getNumActive());
-            assertEquals(0, pool.getNumIdle());
-
-            final String obj = pool.borrowObject();
-
-            assertEquals(1, pool.getNumActive());
-            assertEquals(0, pool.getNumIdle());
-
-            pool.returnObject(obj);
-
-            assertEquals(0, pool.getNumActive());
-            assertEquals(1, pool.getNumIdle());
-
-            assertThrows(IllegalStateException.class,
-                    () -> pool.returnObject(obj));
-
-            assertEquals(0, pool.getNumActive());
-            assertEquals(1, pool.getNumIdle());
-        }
-    }
-
-    /**
-     * Verifies that when a borrowed object is mutated in a way that does not
-     * preserve equality and hashcode, the pool can recognized it on return.
-     *
-     * JIRA: POOL-284
-     */
-    @Test
-    public void testMutable() throws Exception {
-        final HashSetFactory factory = new HashSetFactory();
-        try (final GenericObjectPool<HashSet<String>, RuntimeException> pool = new GenericObjectPool<>(factory,
-                new GenericObjectPoolConfig<>())) {
-            final HashSet<String> s1 = pool.borrowObject();
-            final HashSet<String> s2 = pool.borrowObject();
-            s1.add("One");
-            s2.add("One");
-            pool.returnObject(s1);
-            pool.returnObject(s2);
-        }
-    }
-
-    @Test
-    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
-    public void testNegativeMaxTotal() throws Exception {
-        genericObjectPool.setMaxTotal(-1);
-        genericObjectPool.setBlockWhenExhausted(false);
-        final String obj = genericObjectPool.borrowObject();
-        assertEquals(getNthObject(0),obj);
-        genericObjectPool.returnObject(obj);
-    }
-
-    /**
-     * Verifies that concurrent threads never "share" instances
-     */
-    @Test
-    public void testNoInstanceOverlap() {
-        final int maxTotal = 5;
-        final int numThreads = 100;
-        final int delay = 1;
-        final int iterations = 1000;
-        final AtomicIntegerFactory factory = new AtomicIntegerFactory();
-        try (final GenericObjectPool<AtomicInteger, RuntimeException> pool = new GenericObjectPool<>(factory)) {
-            pool.setMaxTotal(maxTotal);
-            pool.setMaxIdle(maxTotal);
-            pool.setTestOnBorrow(true);
-            pool.setBlockWhenExhausted(true);
-            pool.setMaxWaitMillis(-1);
-            runTestThreads(numThreads, iterations, delay, pool);
-            assertEquals(0, pool.getDestroyedByBorrowValidationCount());
-        }
-    }
-
-    /**
-     * POOL-376
-     */
-    @Test
-    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
-    public void testNoInvalidateNPE() throws Exception {
-        genericObjectPool.setMaxTotal(1);
-        genericObjectPool.setTestOnCreate(true);
-        genericObjectPool.setMaxWaitMillis(-1);
-        final String obj = genericObjectPool.borrowObject();
-        // Make validation fail - this will cause create() to return null
-        simpleFactory.setValid(false);
-        // Create a take waiter
-        final WaitingTestThread<TestException> wtt = new WaitingTestThread<>(genericObjectPool, 200);
-        wtt.start();
-        // Give wtt time to start
-        Thread.sleep(200);
-        genericObjectPool.invalidateObject(obj);
-        // Now allow create to succeed so waiter can be served
-        simpleFactory.setValid(true);
-    }
-
-    public void testPreparePool() throws Exception {
-        genericObjectPool.setMinIdle(1);
-        genericObjectPool.setMaxTotal(1);
-        genericObjectPool.preparePool();
-        assertEquals(1, genericObjectPool.getNumIdle());
-        final String obj = genericObjectPool.borrowObject();
-        genericObjectPool.preparePool();
-        assertEquals(0, genericObjectPool.getNumIdle());
-        genericObjectPool.setMinIdle(0);
-        genericObjectPool.returnObject(obj);
-        genericObjectPool.preparePool();
-        assertEquals(0, genericObjectPool.getNumIdle());
-    }
-
-    @Test/* maxWaitMillis x2 + padding */
-    @Timeout(value = 1200, unit = TimeUnit.MILLISECONDS)
-    public void testReturnBorrowObjectWithingMaxWaitMillis() throws Exception {
-        final long maxWaitMillis = 500;
-
-        try (final GenericObjectPool<String, InterruptedException> createSlowObjectFactoryPool = new GenericObjectPool<>(
-                createSlowObjectFactory(60000))) {
-            createSlowObjectFactoryPool.setMaxTotal(1);
-            createSlowObjectFactoryPool.setMaxWaitMillis(maxWaitMillis);
-
-            // thread1 tries creating a slow object to make pool full.
-            final WaitingTestThread<InterruptedException> thread1 = new WaitingTestThread<>(createSlowObjectFactoryPool, 0);
-            thread1.start();
-
-            // Wait for thread1's reaching to create().
-            Thread.sleep(100);
-
-            // another one tries borrowObject. It should return within maxWaitMillis.
-            assertThrows(NoSuchElementException.class, () -> createSlowObjectFactoryPool.borrowObject(maxWaitMillis),
-                    "borrowObject must fail due to timeout by maxWaitMillis");
-
-            assertTrue(thread1.isAlive());
-        }
-    }
-
-    /**
-     * This is the test case for POOL-263. It is disabled since it will always
-     * pass without artificial delay being injected into GOP.returnObject() and
-     * a way to this hasn't currently been found that doesn't involve
-     * polluting the GOP implementation. The artificial delay needs to be
-     * inserted just before the final call to isLifo() in the returnObject()
-     * method.
-     */
-    //@Test
-    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
-    public void testReturnObject() throws Exception {
-
-        genericObjectPool.setMaxTotal(1);
-        genericObjectPool.setMaxIdle(-1);
-        final String active = genericObjectPool.borrowObject();
-
-        assertEquals(1, genericObjectPool.getNumActive());
-        assertEquals(0, genericObjectPool.getNumIdle());
-
-        final Thread t = new Thread(() -> genericObjectPool.close());
-        t.start();
-
-        genericObjectPool.returnObject(active);
-
-        // Wait for the close() thread to complete
-        while (t.isAlive()) {
-            Thread.sleep(50);
-        }
-
-        assertEquals(0, genericObjectPool.getNumIdle());
-    }
-
-    @Test
-    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
-    public void testSetConfig() throws Exception {
-        final GenericObjectPoolConfig<String> expected = new GenericObjectPoolConfig<>();
-        assertConfiguration(expected,genericObjectPool);
-        expected.setMaxTotal(2);
-        expected.setMaxIdle(3);
-        expected.setMaxWait(Duration.ofMillis(5));
-        expected.setMinEvictableIdleTime(Duration.ofMillis(7L));
-        expected.setNumTestsPerEvictionRun(9);
-        expected.setTestOnCreate(true);
-        expected.setTestOnBorrow(true);
-        expected.setTestOnReturn(true);
-        expected.setTestWhileIdle(true);
-        expected.setTimeBetweenEvictionRuns(Duration.ofMillis(11L));
-        expected.setBlockWhenExhausted(false);
-        genericObjectPool.setConfig(expected);
-        assertConfiguration(expected,genericObjectPool);
-    }
-
-    @Test
-    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
-    public void testSettersAndGetters() throws Exception {
-        {
-            // The object receives an Exception during its creation to prevent
-            // memory leaks. See BaseGenericObjectPool constructor for more details.
-            assertNotEquals("", genericObjectPool.getCreationStackTrace());
-        }
-        {
-            assertEquals(0, genericObjectPool.getBorrowedCount());
-        }
-        {
-            assertEquals(0, genericObjectPool.getReturnedCount());
-        }
-        {
-            assertEquals(0, genericObjectPool.getCreatedCount());
-        }
-        {
-            assertEquals(0, genericObjectPool.getDestroyedCount());
-        }
-        {
-            assertEquals(0, genericObjectPool.getDestroyedByEvictorCount());
-        }
-        {
-            assertEquals(0, genericObjectPool.getDestroyedByBorrowValidationCount());
-        }
-        {
-            assertEquals(0, genericObjectPool.getMeanActiveTimeMillis());
-        }
-        {
-            assertEquals(0, genericObjectPool.getMeanIdleTimeMillis());
-        }
-        {
-            assertEquals(0, genericObjectPool.getMeanBorrowWaitTimeMillis());
-        }
-        {
-            assertEquals(0, genericObjectPool.getMaxBorrowWaitTimeMillis());
-        }
-        {
-            assertEquals(0, genericObjectPool.getNumIdle());
-        }
-        {
-            genericObjectPool.setMaxTotal(123);
-            assertEquals(123,genericObjectPool.getMaxTotal());
-        }
-        {
-            genericObjectPool.setMaxIdle(12);
-            assertEquals(12,genericObjectPool.getMaxIdle());
-        }
-        {
-            genericObjectPool.setMaxWaitMillis(1234L);
-            assertEquals(1234L,genericObjectPool.getMaxWaitMillis());
-        }
-        {
-            genericObjectPool.setMinEvictableIdleTimeMillis(12345L);
-            assertEquals(12345L,genericObjectPool.getMinEvictableIdleDuration().toMillis());
-            assertEquals(12345L,genericObjectPool.getMinEvictableIdleTimeMillis());
-            assertEquals(12345L,genericObjectPool.getMinEvictableIdleTime().toMillis());
-        }
-        {
-            genericObjectPool.setNumTestsPerEvictionRun(11);
-            assertEquals(11,genericObjectPool.getNumTestsPerEvictionRun());
-        }
-        {
-            genericObjectPool.setTestOnBorrow(true);
-            assertTrue(genericObjectPool.getTestOnBorrow());
-            genericObjectPool.setTestOnBorrow(false);
-            assertFalse(genericObjectPool.getTestOnBorrow());
-        }
-        {
-            genericObjectPool.setTestOnReturn(true);
-            assertTrue(genericObjectPool.getTestOnReturn());
-            genericObjectPool.setTestOnReturn(false);
-            assertFalse(genericObjectPool.getTestOnReturn());
-        }
-        {
-            genericObjectPool.setTestWhileIdle(true);
-            assertTrue(genericObjectPool.getTestWhileIdle());
-            genericObjectPool.setTestWhileIdle(false);
-            assertFalse(genericObjectPool.getTestWhileIdle());
-        }
-        {
-            genericObjectPool.setTimeBetweenEvictionRunsMillis(11235L);
-            assertEquals(11235L,genericObjectPool.getDurationBetweenEvictionRuns().toMillis());
-            assertEquals(11235L,genericObjectPool.getTimeBetweenEvictionRunsMillis());
-            assertEquals(11235L,genericObjectPool.getTimeBetweenEvictionRuns().toMillis());
-        }
-        {
-            genericObjectPool.setSoftMinEvictableIdleTimeMillis(12135L);
-            assertEquals(12135L,genericObjectPool.getSoftMinEvictableIdleDuration().toMillis());
-            assertEquals(12135L,genericObjectPool.getSoftMinEvictableIdleTimeMillis());
-            assertEquals(12135L,genericObjectPool.getSoftMinEvictableIdleTime().toMillis());
-        }
-        {
-            genericObjectPool.setBlockWhenExhausted(true);
-            assertTrue(genericObjectPool.getBlockWhenExhausted());
-            genericObjectPool.setBlockWhenExhausted(false);
-            assertFalse(genericObjectPool.getBlockWhenExhausted());
-        }
-    }
-
-    @Test
-    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
-    public void testStartAndStopEvictor() throws Exception {
-        // set up pool without evictor
-        genericObjectPool.setMaxIdle(6);
-        genericObjectPool.setMaxTotal(6);
-        genericObjectPool.setNumTestsPerEvictionRun(6);
-        genericObjectPool.setMinEvictableIdleTime(Duration.ofMillis(100));
-
-        for (int j = 0; j < 2; j++) {
-            // populate the pool
-            {
-                final String[] active = new String[6];
-                for (int i = 0; i < 6; i++) {
-                    active[i] = genericObjectPool.borrowObject();
-                }
-                for (int i = 0; i < 6; i++) {
-                    genericObjectPool.returnObject(active[i]);
-                }
-            }
-
-            // note that it stays populated
-            assertEquals(6,genericObjectPool.getNumIdle(),"Should have 6 idle");
-
-            // start the evictor
-            genericObjectPool.setTimeBetweenEvictionRuns(Duration.ofMillis(50));
-
-            // wait a second (well, .2 seconds)
-            Waiter.sleepQuietly(200L);
-
-            // assert that the evictor has cleared out the pool
-            assertEquals(0,genericObjectPool.getNumIdle(),"Should have 0 idle");
-
-            // stop the evictor
-            genericObjectPool.startEvictor(Duration.ZERO);
-        }
-    }
-
-    @Test
-    public void testSwallowedExceptionListener() {
-        genericObjectPool.setSwallowedExceptionListener(null); // must simply return
-        final List<Exception> swallowedExceptions = new ArrayList<>();
-        /*
-         * A simple listener, that will throw a OOM on 3rd exception.
-         */
-        final SwallowedExceptionListener listener = e -> {
-            if (swallowedExceptions.size() == 2) {
-                throw new OutOfMemoryError();
-            }
-            swallowedExceptions.add(e);
-        };
-        genericObjectPool.setSwallowedExceptionListener(listener);
-
-        final Exception e1 = new Exception();
-        final Exception e2 = new ArrayIndexOutOfBoundsException();
-
-        genericObjectPool.swallowException(e1);
-        genericObjectPool.swallowException(e2);
-
-        assertThrows(OutOfMemoryError.class, () -> genericObjectPool.swallowException(e1));
-
-        assertEquals(2, swallowedExceptions.size());
-    }
-
-    @Test
-    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
-    public void testThreaded1() throws Exception {
-        genericObjectPool.setMaxTotal(15);
-        genericObjectPool.setMaxIdle(15);
-        genericObjectPool.setMaxWaitMillis(1000L);
-        runTestThreads(20, 100, 50, genericObjectPool);
-    }
-
-    @Test
-    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
-    public void testTimeoutNoLeak() throws Exception {
-        genericObjectPool.setMaxTotal(2);
-        genericObjectPool.setMaxWaitMillis(10);
-        genericObjectPool.setBlockWhenExhausted(true);
-        final String obj = genericObjectPool.borrowObject();
-        final String obj2 = genericObjectPool.borrowObject();
-        assertThrows(NoSuchElementException.class, () -> genericObjectPool.borrowObject());
-        genericObjectPool.returnObject(obj2);
-        genericObjectPool.returnObject(obj);
-
-        genericObjectPool.borrowObject();
-        genericObjectPool.borrowObject();
-    }
-
-    /**
-     * Tests POOL-361
-     */
-    @Test
-    public void testValidateOnCreate() throws Exception {
-        genericObjectPool.setTestOnCreate(true);
-        genericObjectPool.addObject();
-        assertEquals(1, simpleFactory.validateCounter);
-    }
-
-    /**
-     * Tests POOL-361
-     */
-    @Test
-    public void testValidateOnCreateFailure() throws Exception {
-        genericObjectPool.setTestOnCreate(true);
-        genericObjectPool.setTestOnBorrow(false);
-        genericObjectPool.setMaxTotal(2);
-        simpleFactory.setValid(false);
-        // Make sure failed validations do not leak capacity
-        genericObjectPool.addObject();
-        genericObjectPool.addObject();
-        assertEquals(0, genericObjectPool.getNumIdle());
-        assertEquals(0, genericObjectPool.getNumActive());
-        simpleFactory.setValid(true);
-        final String obj = genericObjectPool.borrowObject();
-        assertNotNull(obj);
-        genericObjectPool.addObject();
-        // Should have one idle, one out now
-        assertEquals(1, genericObjectPool.getNumIdle());
-        assertEquals(1, genericObjectPool.getNumActive());
-	}
-
-    /**
-     * Verify that threads waiting on a depleted pool get served when a returning object fails
-     * validation.
-     *
-     * JIRA: POOL-240
-     *
-     * @throws Exception May occur in some failure modes
-     */
-    @Test
-    public void testValidationFailureOnReturnFreesCapacity() throws Exception {
-        final SimpleFactory factory = new SimpleFactory();
-        factory.setValid(false); // Validate will always fail
-        factory.setValidationEnabled(true);
-        try (final GenericObjectPool<String, TestException> pool = new GenericObjectPool<>(factory)) {
-            pool.setMaxTotal(2);
-            pool.setMaxWaitMillis(1500);
-            pool.setTestOnReturn(true);
-            pool.setTestOnBorrow(false);
-            // Borrow an instance and hold if for 5 seconds
-            final WaitingTestThread<TestException> thread1 = new WaitingTestThread<>(pool, 5000);
-            thread1.start();
-            // Borrow another instance and return it after 500 ms (validation will fail)
-            final WaitingTestThread<TestException> thread2 = new WaitingTestThread<>(pool, 500);
-            thread2.start();
-            Thread.sleep(50);
-            // Try to borrow an object
-            final String obj = pool.borrowObject();
-            pool.returnObject(obj);
-        }
-    }
-
-    // POOL-276
-    @Test
-    public void testValidationOnCreateOnly() throws Exception {
-        genericObjectPool.setMaxTotal(1);
-        genericObjectPool.setTestOnCreate(true);
-        genericObjectPool.setTestOnBorrow(false);
-        genericObjectPool.setTestOnReturn(false);
-        genericObjectPool.setTestWhileIdle(false);
-
-        final String o1 = genericObjectPool.borrowObject();
-        assertEquals("0", o1);
-        final Timer t = new Timer();
-        t.schedule(
-                new TimerTask() {
-                    @Override
-                    public void run() {
-                        genericObjectPool.returnObject(o1);
-                    }
-                }, 3000);
-
-        final String o2 = genericObjectPool.borrowObject();
-        assertEquals("0", o2);
-
-        assertEquals(1, simpleFactory.validateCounter);
-    }
-
-    @Test
-    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
-    public void testWhenExhaustedBlock() throws Exception {
-        genericObjectPool.setMaxTotal(1);
-        genericObjectPool.setBlockWhenExhausted(true);
-        genericObjectPool.setMaxWaitMillis(10L);
-        final String obj1 = genericObjectPool.borrowObject();
-        assertNotNull(obj1);
-        assertThrows(NoSuchElementException.class, () -> genericObjectPool.borrowObject());
-        genericObjectPool.returnObject(obj1);
-        genericObjectPool.close();
-    }
-
-    /**
-     * POOL-189
-     *
-     * @throws Exception May occur in some failure modes
-     */
-    @Test
-    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
-    public void testWhenExhaustedBlockClosePool() throws Exception {
-        genericObjectPool.setMaxTotal(1);
-        genericObjectPool.setBlockWhenExhausted(true);
-        genericObjectPool.setMaxWaitMillis(-1);
-        final Object obj1 = genericObjectPool.borrowObject();
-
-        // Make sure an object was obtained
-        assertNotNull(obj1);
-
-        // Create a separate thread to try and borrow another object
-        final WaitingTestThread<TestException> wtt = new WaitingTestThread<TestException>(genericObjectPool, 200);
-        wtt.start();
-        // Give wtt time to start
-        Thread.sleep(200);
-
-        // close the pool (Bug POOL-189)
-        genericObjectPool.close();
-
-        // Give interrupt time to take effect
-        Thread.sleep(200);
-
-        // Check thread was interrupted
-        assertTrue(wtt.thrown instanceof InterruptedException);
-    }
-
-    @Test
-    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
-    public void testWhenExhaustedBlockInterrupt() throws Exception {
-        genericObjectPool.setMaxTotal(1);
-        genericObjectPool.setBlockWhenExhausted(true);
-        genericObjectPool.setMaxWaitMillis(-1);
-        final String obj1 = genericObjectPool.borrowObject();
-
-        // Make sure on object was obtained
-        assertNotNull(obj1);
-
-        // Create a separate thread to try and borrow another object
-        final WaitingTestThread<TestException> wtt = new WaitingTestThread<>(genericObjectPool, 200000);
-        wtt.start();
-        // Give wtt time to start
-        Thread.sleep(200);
-        wtt.interrupt();
-
-        // Give interrupt time to take effect
-        Thread.sleep(200);
-
-        // Check thread was interrupted
-        assertTrue(wtt.thrown instanceof InterruptedException);
-
-        // Return object to the pool
-        genericObjectPool.returnObject(obj1);
-
-        // Bug POOL-162 - check there is now an object in the pool
-        genericObjectPool.setMaxWaitMillis(10L);
-        String obj2 = null;
-        try {
-            obj2 = genericObjectPool.borrowObject();
-            assertNotNull(obj2);
-        } catch (final NoSuchElementException e) {
-            // Not expected
-            fail("NoSuchElementException not expected");
-        }
-        genericObjectPool.returnObject(obj2);
-        genericObjectPool.close();
-
-    }
-
-    @Test
-    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
-    public void testWhenExhaustedFail() throws Exception {
-        genericObjectPool.setMaxTotal(1);
-        genericObjectPool.setBlockWhenExhausted(false);
-        final String obj1 = genericObjectPool.borrowObject();
-        assertNotNull(obj1);
-        assertThrows(NoSuchElementException.class, () -> genericObjectPool.borrowObject());
-        genericObjectPool.returnObject(obj1);
-        assertEquals(1, genericObjectPool.getNumIdle());
-        genericObjectPool.close();
-    }
-
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.pool2.impl;
+
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.lessThanOrEqualTo;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import java.lang.management.ManagementFactory;
+import java.lang.ref.WeakReference;
+import java.nio.charset.UnsupportedCharsetException;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+import java.util.Random;
+import java.util.Set;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+
+import org.apache.commons.pool2.BasePooledObjectFactory;
+import org.apache.commons.pool2.ObjectPool;
+import org.apache.commons.pool2.PoolUtils;
+import org.apache.commons.pool2.PooledObject;
+import org.apache.commons.pool2.PooledObjectFactory;
+import org.apache.commons.pool2.SwallowedExceptionListener;
+import org.apache.commons.pool2.TestBaseObjectPool;
+import org.apache.commons.pool2.TestException;
+import org.apache.commons.pool2.VisitTracker;
+import org.apache.commons.pool2.VisitTrackerFactory;
+import org.apache.commons.pool2.Waiter;
+import org.apache.commons.pool2.WaiterFactory;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.Timeout;
+
+/**
+ */
+public class TestGenericObjectPool extends TestBaseObjectPool {
+
+    private class ConcurrentBorrowAndEvictThread extends Thread {
+        private final boolean borrow;
+        public String obj;
+
+        public ConcurrentBorrowAndEvictThread(final boolean borrow) {
+            this.borrow = borrow;
+        }
+
+        @Override
+        public void run() {
+            try {
+                if (borrow) {
+                    obj = genericObjectPool.borrowObject();
+                } else {
+                    genericObjectPool.evict();
+                }
+            } catch (final Exception e) {
+                // Ignore.
+            }
+        }
+    }
+
+    private static class CreateErrorFactory extends BasePooledObjectFactory<String, InterruptedException> {
+
+        private final Semaphore semaphore = new Semaphore(0);
+
+        @Override
+        public String create() throws InterruptedException {
+            semaphore.acquire();
+            throw new UnknownError("wiggle");
+        }
+
+        public boolean hasQueuedThreads() {
+            return semaphore.hasQueuedThreads();
+        }
+
+        public void release() {
+            semaphore.release();
+        }
+
+        @Override
+        public PooledObject<String> wrap(final String obj) {
+            return new DefaultPooledObject<>(obj);
+        }
+    }
+
+    private static class CreateFailFactory extends BasePooledObjectFactory<String, InterruptedException> {
+
+        private final Semaphore semaphore = new Semaphore(0);
+
+        @Override
+        public String create() throws InterruptedException {
+            semaphore.acquire();
+            throw new UnsupportedCharsetException("wibble");
+        }
+
+        public boolean hasQueuedThreads() {
+            return semaphore.hasQueuedThreads();
+        }
+
+        public void release() {
+            semaphore.release();
+        }
+
+        @Override
+        public PooledObject<String> wrap(final String obj) {
+            return new DefaultPooledObject<>(obj);
+        }
+    }
+
+    private static final class DummyFactory
+            extends BasePooledObjectFactory<Object, RuntimeException> {
+        @Override
+        public Object create() {
+            return null;
+        }
+        @Override
+        public PooledObject<Object> wrap(final Object value) {
+            return new DefaultPooledObject<>(value);
+        }
+    }
+
+    private static class EvictionThread<T, E extends Exception> extends Thread {
+
+        private final GenericObjectPool<T, E> pool;
+
+        public EvictionThread(final GenericObjectPool<T, E> pool) {
+            this.pool = pool;
+        }
+
+        @Override
+        public void run() {
+            try {
+                pool.evict();
+            } catch (final Exception e) {
+                // Ignore
+            }
+        }
+    }
+
+    /**
+     * Factory that creates HashSets.  Note that this means
+     *  0) All instances are initially equal (not discernible by equals)
+     *  1) Instances are mutable and mutation can cause change in identity / hashcode.
+     */
+    private static final class HashSetFactory
+            extends BasePooledObjectFactory<HashSet<String>, RuntimeException> {
+        @Override
+        public HashSet<String> create() {
+            return new HashSet<>();
+        }
+        @Override
+        public PooledObject<HashSet<String>> wrap(final HashSet<String> value) {
+            return new DefaultPooledObject<>(value);
+        }
+    }
+
+    /**
+     * Attempts to invalidate an object, swallowing IllegalStateException.
+     */
+    static class InvalidateThread implements Runnable {
+        private final String obj;
+        private final ObjectPool<String, ? extends Exception> pool;
+        private boolean done;
+        public InvalidateThread(final ObjectPool<String, ? extends Exception> pool, final String obj) {
+            this.obj = obj;
+            this.pool = pool;
+        }
+        public boolean complete() {
+            return done;
+        }
+        @Override
+        public void run() {
+            try {
+                pool.invalidateObject(obj);
+            } catch (final IllegalStateException ex) {
+                // Ignore
+            } catch (final Exception ex) {
+                fail("Unexpected exception " + ex.toString());
+            } finally {
+                done = true;
+            }
+        }
+    }
+
+    private static class InvalidFactory
+            extends BasePooledObjectFactory<Object, RuntimeException> {
+
+        @Override
+        public Object create() {
+            return new Object();
+        }
+        @Override
+        public boolean validateObject(final PooledObject<Object> obj) {
+            Waiter.sleepQuietly(1000);
+            return false;
+        }
+
+        @Override
+        public PooledObject<Object> wrap(final Object value) {
+            return new DefaultPooledObject<>(value);
+        }
+    }
+
+    public static class SimpleFactory implements PooledObjectFactory<String, TestException> {
+        int makeCounter;
+
+        int activationCounter;
+
+        int validateCounter;
+
+        int activeCount;
+
+        boolean evenValid = true;
+
+        boolean oddValid = true;
+
+        boolean exceptionOnPassivate;
+
+        boolean exceptionOnActivate;
+
+        boolean exceptionOnDestroy;
+
+        boolean exceptionOnValidate;
+
+        boolean enableValidation = true;
+
+        long destroyLatency;
+
+        long makeLatency;
+
+        long validateLatency;
+
+        int maxTotal = Integer.MAX_VALUE;
+
+        public SimpleFactory() {
+            this(true);
+        }
+
+        public SimpleFactory(final boolean valid) {
+            this(valid,valid);
+        }
+
+        public SimpleFactory(final boolean evalid, final boolean ovalid) {
+            evenValid = evalid;
+            oddValid = ovalid;
+        }
+
+        @Override
+        public void activateObject(final PooledObject<String> obj) throws TestException {
+            final boolean hurl;
+            final boolean evenTest;
+            final boolean oddTest;
+            final int counter;
+            synchronized(this) {
+                hurl = exceptionOnActivate;
+                evenTest = evenValid;
+                oddTest = oddValid;
+                counter = activationCounter++;
+            }
+            if (hurl && !(counter%2 == 0 ? evenTest : oddTest)) {
+                throw new TestException();
+            }
+        }
+
+        @Override
+        public void destroyObject(final PooledObject<String> obj) throws TestException {
+            final long waitLatency;
+            final boolean hurl;
+            synchronized(this) {
+                waitLatency = destroyLatency;
+                hurl = exceptionOnDestroy;
+            }
+            if (waitLatency > 0) {
+                doWait(waitLatency);
+            }
+            synchronized(this) {
+                activeCount--;
+            }
+            if (hurl) {
+                throw new TestException();
+            }
+        }
+
+        private void doWait(final long latency) {
+            Waiter.sleepQuietly(latency);
+        }
+
+        public synchronized int getMakeCounter() {
+            return makeCounter;
+        }
+
+        public synchronized boolean isThrowExceptionOnActivate() {
+            return exceptionOnActivate;
+        }
+
+        public synchronized boolean isValidationEnabled() {
+            return enableValidation;
+        }
+
+        @Override
+        public PooledObject<String> makeObject() {
+            final long waitLatency;
+            synchronized(this) {
+                activeCount++;
+                if (activeCount > maxTotal) {
+                    throw new IllegalStateException(
+                        "Too many active instances: " + activeCount);
+                }
+                waitLatency = makeLatency;
+            }
+            if (waitLatency > 0) {
+                doWait(waitLatency);
+            }
+            final int counter;
+            synchronized(this) {
+                counter = makeCounter++;
+            }
+            return new DefaultPooledObject<>(String.valueOf(counter));
+        }
+
+        @Override
+        public void passivateObject(final PooledObject<String> obj) throws TestException {
+            final boolean hurl;
+            synchronized(this) {
+                hurl = exceptionOnPassivate;
+            }
+            if (hurl) {
+                throw new TestException();
+            }
+        }
+
+        public synchronized void setDestroyLatency(final long destroyLatency) {
+            this.destroyLatency = destroyLatency;
+        }
+
+        public synchronized void setEvenValid(final boolean valid) {
+            evenValid = valid;
+        }
+
+        public synchronized void setMakeLatency(final long makeLatency) {
+            this.makeLatency = makeLatency;
+        }
+
+        public synchronized void setMaxTotal(final int maxTotal) {
+            this.maxTotal = maxTotal;
+        }
+
+        public synchronized void setOddValid(final boolean valid) {
+            oddValid = valid;
+        }
+
+        public synchronized void setThrowExceptionOnActivate(final boolean b) {
+            exceptionOnActivate = b;
+        }
+
+        public synchronized void setThrowExceptionOnDestroy(final boolean b) {
+            exceptionOnDestroy = b;
+        }
+
+        public synchronized void setThrowExceptionOnPassivate(final boolean bool) {
+            exceptionOnPassivate = bool;
+        }
+
+        public synchronized void setThrowExceptionOnValidate(final boolean bool) {
+            exceptionOnValidate = bool;
+        }
+
+        public synchronized void setValid(final boolean valid) {
+            setEvenValid(valid);
+            setOddValid(valid);
+        }
+
+        public synchronized void setValidateLatency(final long validateLatency) {
+            this.validateLatency = validateLatency;
+        }
+
+        public synchronized void setValidationEnabled(final boolean b) {
+            enableValidation = b;
+        }
+
+        @Override
+        public boolean validateObject(final PooledObject<String> obj) {
+            final boolean validate;
+            final boolean throwException;
+            final boolean evenTest;
+            final boolean oddTest;
+            final long waitLatency;
+            final int counter;
+            synchronized(this) {
+                validate = enableValidation;
+                throwException = exceptionOnValidate;
+                evenTest = evenValid;
+                oddTest = oddValid;
+                counter = validateCounter++;
+                waitLatency = validateLatency;
+            }
+            if (waitLatency > 0) {
+                doWait(waitLatency);
+            }
+            if (throwException) {
+                throw new RuntimeException("validation failed");
+            }
+            if (validate) {
+                return counter%2 == 0 ? evenTest : oddTest;
+            }
+            return true;
+        }
+    }
+
+    public static class TestEvictionPolicy<T> implements EvictionPolicy<T> {
+
+        private final AtomicInteger callCount = new AtomicInteger(0);
+
+        @Override
+        public boolean evict(final EvictionConfig config, final PooledObject<T> underTest,
+                final int idleCount) {
+            return callCount.incrementAndGet() > 1500;
+        }
+    }
+
+    static class TestThread<T, E extends Exception> implements Runnable {
+
+        /** source of random delay times */
+        private final java.util.Random random;
+
+        /** pool to borrow from */
+        private final ObjectPool<T, E> pool;
+
+        /** number of borrow attempts */
+        private final int iter;
+
+        /** delay before each borrow attempt */
+        private final int startDelay;
+
+        /** time to hold each borrowed object before returning it */
+        private final int holdTime;
+
+        /** whether or not start and hold time are randomly generated */
+        private final boolean randomDelay;
+
+        /** object expected to be borrowed (fail otherwise) */
+        private final Object expectedObject;
+
+        private volatile boolean complete;
+        private volatile boolean failed;
+        private volatile Throwable error;
+
+        public TestThread(final ObjectPool<T, E> pool) {
+            this(pool, 100, 50, true, null);
+        }
+
+        public TestThread(final ObjectPool<T, E> pool, final int iter) {
+            this(pool, iter, 50, true, null);
+        }
+
+        public TestThread(final ObjectPool<T, E> pool, final int iter, final int delay) {
+            this(pool, iter, delay, true, null);
+        }
+
+        public TestThread(final ObjectPool<T, E> pool, final int iter, final int delay,
+                final boolean randomDelay) {
+            this(pool, iter, delay, randomDelay, null);
+        }
+
+        public TestThread(final ObjectPool<T, E> pool, final int iter, final int delay,
+                final boolean randomDelay, final Object obj) {
+            this(pool, iter, delay, delay, randomDelay, obj);
+        }
+
+        public TestThread(final ObjectPool<T, E> pool, final int iter, final int startDelay,
+            final int holdTime, final boolean randomDelay, final Object obj) {
+       this.pool = pool;
+       this.iter = iter;
+       this.startDelay = startDelay;
+       this.holdTime = holdTime;
+       this.randomDelay = randomDelay;
+       this.random = this.randomDelay ? new Random() : null;
+       this.expectedObject = obj;
+    }
+
+        public boolean complete() {
+            return complete;
+        }
+
+        public boolean failed() {
+            return failed;
+        }
+
+        @Override
+        public void run() {
+            for (int i = 0; i < iter; i++) {
+                final long actualStartDelay = randomDelay ? (long) random.nextInt(startDelay) : startDelay;
+                final long actualHoldTime = randomDelay ? (long) random.nextInt(holdTime) : holdTime;
+                Waiter.sleepQuietly(actualStartDelay);
+                T obj = null;
+                try {
+                    obj = pool.borrowObject();
+                } catch (final Exception e) {
+                    error = e;
+                    failed = true;
+                    complete = true;
+                    break;
+                }
+
+                if (expectedObject != null && !expectedObject.equals(obj)) {
+                    error = new Throwable("Expected: " + expectedObject + " found: " + obj);
+                    failed = true;
+                    complete = true;
+                    break;
+                }
+
+                Waiter.sleepQuietly(actualHoldTime);
+                try {
+                    pool.returnObject(obj);
+                } catch (final Exception e) {
+                    error = e;
+                    failed = true;
+                    complete = true;
+                    break;
+                }
+            }
+            complete = true;
+        }
+    }
+
+    /*
+     * Very simple test thread that just tries to borrow an object from
+     * the provided pool returns it after a wait
+     */
+    static class WaitingTestThread<E extends Exception> extends Thread {
+        private final GenericObjectPool<String, E> pool;
+        private final long pause;
+        private Throwable thrown;
+
+        private long preBorrowMillis; // just before borrow
+        private long postBorrowMillis; //  borrow returned
+        private long postReturnMillis; // after object was returned
+        private long endedMillis;
+        private String objectId;
+
+        public WaitingTestThread(final GenericObjectPool<String, E> pool, final long pause) {
+            this.pool = pool;
+            this.pause = pause;
+            this.thrown = null;
+        }
+
+        @Override
+        public void run() {
+            try {
+                preBorrowMillis = System.currentTimeMillis();
+                final String obj = pool.borrowObject();
+                objectId = obj;
+                postBorrowMillis = System.currentTimeMillis();
+                Thread.sleep(pause);
+                pool.returnObject(obj);
+                postReturnMillis = System.currentTimeMillis();
+            } catch (final Throwable e) {
+                thrown = e;
+            } finally{
+                endedMillis = System.currentTimeMillis();
+            }
+        }
+    }
+
+    private static final boolean DISPLAY_THREAD_DETAILS=
+    Boolean.getBoolean("TestGenericObjectPool.display.thread.details");
+    // To pass this to a Maven test, use:
+    // mvn test -DargLine="-DTestGenericObjectPool.display.thread.details=true"
+    // @see https://issues.apache.org/jira/browse/SUREFIRE-121
+
+    protected GenericObjectPool<String, TestException> genericObjectPool;
+
+    private SimpleFactory simpleFactory;
+
+    @SuppressWarnings("deprecation")
+    private void assertConfiguration(final GenericObjectPoolConfig<?> expected, final GenericObjectPool<?, ?> actual) {
+        assertEquals(Boolean.valueOf(expected.getTestOnCreate()), Boolean.valueOf(actual.getTestOnCreate()),
+                "testOnCreate");
+        assertEquals(Boolean.valueOf(expected.getTestOnBorrow()), Boolean.valueOf(actual.getTestOnBorrow()),
+                "testOnBorrow");
+        assertEquals(Boolean.valueOf(expected.getTestOnReturn()), Boolean.valueOf(actual.getTestOnReturn()),
+                "testOnReturn");
+        assertEquals(Boolean.valueOf(expected.getTestWhileIdle()), Boolean.valueOf(actual.getTestWhileIdle()),
+                "testWhileIdle");
+        assertEquals(Boolean.valueOf(expected.getBlockWhenExhausted()), Boolean.valueOf(actual.getBlockWhenExhausted()),
+                "whenExhaustedAction");
+        assertEquals(expected.getMaxTotal(), actual.getMaxTotal(), "maxTotal");
+        assertEquals(expected.getMaxIdle(), actual.getMaxIdle(), "maxIdle");
+        assertEquals(expected.getMaxWaitMillis(), actual.getMaxWaitMillis(), "maxWaitDuration");
+        assertEquals(expected.getMaxWaitDuration(), actual.getMaxWaitDuration(), "maxWaitDuration");
+        assertEquals(expected.getMinEvictableIdleTimeMillis(), actual.getMinEvictableIdleTimeMillis(),
+                "minEvictableIdleTimeMillis");
+        assertEquals(expected.getMinEvictableIdleTime(), actual.getMinEvictableIdleTime(),
+                "minEvictableIdleTime");
+        assertEquals(expected.getMinEvictableIdleDuration(), actual.getMinEvictableIdleDuration(),
+                "minEvictableIdleDuration");
+        assertEquals(expected.getNumTestsPerEvictionRun(), actual.getNumTestsPerEvictionRun(),
+                "numTestsPerEvictionRun");
+        assertEquals(expected.getEvictorShutdownTimeoutDuration(), actual.getEvictorShutdownTimeoutDuration(),
+                "evictorShutdownTimeoutDuration");
+        assertEquals(expected.getEvictorShutdownTimeoutMillis(), actual.getEvictorShutdownTimeoutMillis(),
+                "evictorShutdownTimeoutMillis");
+        assertEquals(expected.getEvictorShutdownTimeout(), actual.getEvictorShutdownTimeout(),
+                "evictorShutdownTimeout");
+        assertEquals(expected.getTimeBetweenEvictionRunsMillis(), actual.getTimeBetweenEvictionRunsMillis(),
+                "timeBetweenEvictionRunsMillis");
+        assertEquals(expected.getDurationBetweenEvictionRuns(), actual.getTimeBetweenEvictionRuns(),
+                "timeBetweenEvictionRuns");
+        assertEquals(expected.getTimeBetweenEvictionRuns(), actual.getTimeBetweenEvictionRuns(),
+                "timeBetweenEvictionRuns");
+    }
+
+    private void checkEvict(final boolean lifo) throws Exception {
+        // yea this is hairy but it tests all the code paths in GOP.evict()
+        genericObjectPool.setSoftMinEvictableIdle(Duration.ofMillis(10));
+        genericObjectPool.setSoftMinEvictableIdleTime(Duration.ofMillis(10));
+        genericObjectPool.setMinIdle(2);
+        genericObjectPool.setTestWhileIdle(true);
+        genericObjectPool.setLifo(lifo);
+        genericObjectPool.addObjects(5);
+        genericObjectPool.evict();
+        simpleFactory.setEvenValid(false);
+        simpleFactory.setOddValid(false);
+        simpleFactory.setThrowExceptionOnActivate(true);
+        genericObjectPool.evict();
+        genericObjectPool.addObjects(5);
+        simpleFactory.setThrowExceptionOnActivate(false);
+        simpleFactory.setThrowExceptionOnPassivate(true);
+        genericObjectPool.evict();
+        simpleFactory.setThrowExceptionOnPassivate(false);
+        simpleFactory.setEvenValid(true);
+        simpleFactory.setOddValid(true);
+        Thread.sleep(125);
+        genericObjectPool.evict();
+        assertEquals(2, genericObjectPool.getNumIdle());
+    }
+
+    private void checkEvictionOrder(final boolean lifo) throws Exception {
+        checkEvictionOrderPart1(lifo);
+        tearDown();
+        setUp();
+        checkEvictionOrderPart2(lifo);
+    }
+
+    private void checkEvictionOrderPart1(final boolean lifo) throws Exception {
+        genericObjectPool.setNumTestsPerEvictionRun(2);
+        genericObjectPool.setMinEvictableIdleTime(Duration.ofMillis(100));
+        genericObjectPool.setLifo(lifo);
+        for (int i = 0; i < 5; i++) {
+            genericObjectPool.addObject();
+            Thread.sleep(100);
+        }
+        // Order, oldest to youngest, is "0", "1", ...,"4"
+        genericObjectPool.evict(); // Should evict "0" and "1"
+        final Object obj = genericObjectPool.borrowObject();
+        assertNotEquals("0", obj, "oldest not evicted");
+        assertNotEquals("1", obj, "second oldest not evicted");
+        // 2 should be next out for FIFO, 4 for LIFO
+        assertEquals(lifo ? "4" : "2" , obj,"Wrong instance returned");
+    }
+
+    private void checkEvictionOrderPart2(final boolean lifo) throws Exception {
+        // Two eviction runs in sequence
+        genericObjectPool.setNumTestsPerEvictionRun(2);
+        genericObjectPool.setMinEvictableIdleTime(Duration.ofMillis(100));
+        genericObjectPool.setLifo(lifo);
+        for (int i = 0; i < 5; i++) {
+            genericObjectPool.addObject();
+            Thread.sleep(100);
+        }
+        genericObjectPool.evict(); // Should evict "0" and "1"
+        genericObjectPool.evict(); // Should evict "2" and "3"
+        final Object obj = genericObjectPool.borrowObject();
+        assertEquals("4", obj,"Wrong instance remaining in pool");
+    }
+
+    private void checkEvictorVisiting(final boolean lifo) throws Exception {
+        VisitTracker<Object> obj;
+        VisitTrackerFactory<Object> trackerFactory = new VisitTrackerFactory<>();
+        try (GenericObjectPool<VisitTracker<Object>,RuntimeException> trackerPool = new GenericObjectPool<>(trackerFactory)) {
+            trackerPool.setNumTestsPerEvictionRun(2);
+            trackerPool.setMinEvictableIdleTime(Duration.ofMillis(-1));
+            trackerPool.setTestWhileIdle(true);
+            trackerPool.setLifo(lifo);
+            trackerPool.setTestOnReturn(false);
+            trackerPool.setTestOnBorrow(false);
+            for (int i = 0; i < 8; i++) {
+                trackerPool.addObject();
+            }
+            trackerPool.evict(); // Visit oldest 2 - 0 and 1
+            obj = trackerPool.borrowObject();
+            trackerPool.returnObject(obj);
+            obj = trackerPool.borrowObject();
+            trackerPool.returnObject(obj);
+            // borrow, return, borrow, return
+            // FIFO will move 0 and 1 to end
+            // LIFO, 7 out, then in, then out, then in
+            trackerPool.evict(); // Should visit 2 and 3 in either case
+            for (int i = 0; i < 8; i++) {
+                final VisitTracker<Object> tracker = trackerPool.borrowObject();
+                if (tracker.getId() >= 4) {
+                    assertEquals( 0, tracker.getValidateCount(),"Unexpected instance visited " + tracker.getId());
+                } else {
+                    assertEquals( 1, tracker.getValidateCount(),
+                            "Instance " + tracker.getId() + " visited wrong number of times.");
+                }
+            }
+        }
+
+        trackerFactory = new VisitTrackerFactory<>();
+        try (GenericObjectPool<VisitTracker<Object>, RuntimeException> trackerPool = new GenericObjectPool<>(trackerFactory)) {
+            trackerPool.setNumTestsPerEvictionRun(3);
+            trackerPool.setMinEvictableIdleTime(Duration.ofMillis(-1));
+            trackerPool.setTestWhileIdle(true);
+            trackerPool.setLifo(lifo);
+            trackerPool.setTestOnReturn(false);
+            trackerPool.setTestOnBorrow(false);
+            for (int i = 0; i < 8; i++) {
+                trackerPool.addObject();
+            }
+            trackerPool.evict(); // 0, 1, 2
+            trackerPool.evict(); // 3, 4, 5
+            obj = trackerPool.borrowObject();
+            trackerPool.returnObject(obj);
+            obj = trackerPool.borrowObject();
+            trackerPool.returnObject(obj);
+            obj = trackerPool.borrowObject();
+            trackerPool.returnObject(obj);
+            // borrow, return, borrow, return
+            // FIFO 3,4,5,6,7,0,1,2
+            // LIFO 7,6,5,4,3,2,1,0
+            // In either case, pointer should be at 6
+            trackerPool.evict();
+            // Should hit 6,7,0 - 0 for second time
+            for (int i = 0; i < 8; i++) {
+                final VisitTracker<Object> tracker = trackerPool.borrowObject();
+                if (tracker.getId() != 0) {
+                    assertEquals( 1, tracker.getValidateCount(),
+                            "Instance " + tracker.getId() + " visited wrong number of times.");
+                } else {
+                    assertEquals( 2, tracker.getValidateCount(),
+                            "Instance " + tracker.getId() + " visited wrong number of times.");
+                }
+            }
+        }
+
+        // Randomly generate a pools with random numTests
+        // and make sure evictor cycles through elements appropriately
+        final int[] smallPrimes = { 2, 3, 5, 7 };
+        final Random random = new Random();
+        random.setSeed(System.currentTimeMillis());
+        for (int i = 0; i < 4; i++) {
+            for (int j = 0; j < 5; j++) {
+                try (GenericObjectPool<VisitTracker<Object>, RuntimeException> trackerPool = new GenericObjectPool<>(trackerFactory)) {
+                    trackerPool.setNumTestsPerEvictionRun(smallPrimes[i]);
+                    trackerPool.setMinEvictableIdleTime(Duration.ofMillis(-1));
+                    trackerPool.setTestWhileIdle(true);
+                    trackerPool.setLifo(lifo);
+                    trackerPool.setTestOnReturn(false);
+                    trackerPool.setTestOnBorrow(false);
+                    trackerPool.setMaxIdle(-1);
+                    final int instanceCount = 10 + random.nextInt(20);
+                    trackerPool.setMaxTotal(instanceCount);
+                    for (int k = 0; k < instanceCount; k++) {
+                        trackerPool.addObject();
+                    }
+
+                    // Execute a random number of evictor runs
+                    final int runs = 10 + random.nextInt(50);
+                    for (int k = 0; k < runs; k++) {
+                        trackerPool.evict();
+                    }
+
+                    // Number of times evictor should have cycled through the pool
+                    final int cycleCount = runs * trackerPool.getNumTestsPerEvictionRun() / instanceCount;
+
+                    // Look at elements and make sure they are visited cycleCount
+                    // or cycleCount + 1 times
+                    VisitTracker<Object> tracker = null;
+                    int visitCount = 0;
+                    for (int k = 0; k < instanceCount; k++) {
+                        tracker = trackerPool.borrowObject();
+                        assertTrue(trackerPool.getNumActive() <= trackerPool.getMaxTotal());
+                        visitCount = tracker.getValidateCount();
+                        assertTrue(visitCount >= cycleCount && visitCount <= cycleCount + 1);
+                    }
+                }
+            }
+        }
+    }
+
+    private BasePooledObjectFactory<String, RuntimeException> createDefaultPooledObjectFactory() {
+        return new BasePooledObjectFactory<String, RuntimeException>() {
+            @Override
+            public String create() {
+                // fake
+                return null;
+            }
+
+            @Override
+            public PooledObject<String> wrap(final String obj) {
+                // fake
+                return new DefaultPooledObject<>(obj);
+            }
+        };
+    }
+
+    private BasePooledObjectFactory<String, RuntimeException> createNullPooledObjectFactory() {
+        return new BasePooledObjectFactory<String, RuntimeException>() {
+            @Override
+            public String create() {
+                // fake
+                return null;
+            }
+
+            @Override
+            public PooledObject<String> wrap(final String obj) {
+                // fake
+                return null;
+            }
+        };
+    }
+
+    private BasePooledObjectFactory<String, InterruptedException> createSlowObjectFactory(final long elapsedTimeMillis) {
+        return new BasePooledObjectFactory<String, InterruptedException>() {
+            @Override
+            public String create() throws InterruptedException {
+                Thread.sleep(elapsedTimeMillis);
+                return "created";
+            }
+
+            @Override
+            public PooledObject<String> wrap(final String obj) {
+                // fake
+                return new DefaultPooledObject<>(obj);
+            }
+        };
+    }
+
+    @Override
+    protected Object getNthObject(final int n) {
+        return String.valueOf(n);
+    }
+
+    @Override
+    protected boolean isFifo() {
+        return false;
+    }
+
+    @Override
+    protected boolean isLifo() {
+        return true;
+    }
+
+    @Override
+    protected ObjectPool<String, TestException> makeEmptyPool(final int minCap) {
+        final GenericObjectPool<String, TestException> mtPool = new GenericObjectPool<>(new SimpleFactory());
+        mtPool.setMaxTotal(minCap);
+       mtPool.setMaxIdle(minCap);
+       return mtPool;
+    }
+
+    @Override
+    protected <E extends Exception> ObjectPool<Object, E> makeEmptyPool(final PooledObjectFactory<Object, E> fac) {
+        return new GenericObjectPool<>(fac);
+    }
+
+    /**
+     * Kicks off <numThreads> test threads, each of which will go through
+     * <iterations> borrow-return cycles with random delay times <= delay
+     * in between.
+     */
+    private <T, E extends Exception> void runTestThreads(final int numThreads, final int iterations, final int delay, final GenericObjectPool<T, E> testPool) {
+        final TestThread<T, E>[] threads = new TestThread[numThreads];
+        for (int i = 0; i < numThreads; i++) {
+            threads[i] = new TestThread<>(testPool, iterations, delay);
+            final Thread t = new Thread(threads[i]);
+            t.start();
+        }
+        for (int i = 0; i < numThreads; i++) {
+            while (!threads[i].complete()) {
+                Waiter.sleepQuietly(500L);
+            }
+            if (threads[i].failed()) {
+                fail("Thread " + i + " failed: " + threads[i].error.toString());
+            }
+        }
+    }
+
+    @BeforeEach
+    public void setUp() {
+        simpleFactory = new SimpleFactory();
+        genericObjectPool = new GenericObjectPool<>(simpleFactory);
+    }
+
+    @AfterEach
+    public void tearDown() throws Exception {
+        final ObjectName jmxName = genericObjectPool.getJmxName();
+        final String poolName = Objects.toString(jmxName, null);
+
+        genericObjectPool.clear();
+        genericObjectPool.close();
+        genericObjectPool = null;
+        simpleFactory = null;
+
+        final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
+        final Set<ObjectName> result = mbs.queryNames(new ObjectName("org.apache.commoms.pool2:type=GenericObjectPool,*"), null);
+        // There should be no registered pools at this point
+        final int registeredPoolCount = result.size();
+        final StringBuilder msg = new StringBuilder("Current pool is: ");
+        msg.append(poolName);
+        msg.append("  Still open pools are: ");
+        for (final ObjectName name : result) {
+            // Clean these up ready for the next test
+            msg.append(name.toString());
+            msg.append(" created via\n");
+            msg.append(mbs.getAttribute(name, "CreationStackTrace"));
+            msg.append('\n');
+            mbs.unregisterMBean(name);
+        }
+        assertEquals(0, registeredPoolCount, msg.toString());
+
+        // Make sure that EvictionTimer executor is shut down.
+        Thread.yield();
+        if (EvictionTimer.getExecutor() != null) {
+            Thread.sleep(1000);
+        }
+        assertNull(EvictionTimer.getExecutor(), "EvictionTimer.getExecutor()");
+    }
+
+    /**
+     * Check that a pool that starts an evictor, but is never closed does not leave EvictionTimer executor running. Confirmation check is in
+     * {@link #tearDown()}.
+     *
+     * @throws TestException Custom exception
+     * @throws InterruptedException if any thread has interrupted the current thread. The <em>interrupted status</em> of the current thread is cleared when this
+     *         exception is thrown.
+     */
+    @SuppressWarnings("deprecation")
+    @Test
+    public void testAbandonedPool() throws TestException, InterruptedException {
+        final GenericObjectPoolConfig<String> config = new GenericObjectPoolConfig<>();
+        config.setJmxEnabled(false);
+        GenericObjectPool<String, TestException> abandoned = new GenericObjectPool<>(simpleFactory, config);
+        abandoned.setTimeBetweenEvictionRuns(Duration.ofMillis(100)); // Starts evictor
+        assertEquals(abandoned.getRemoveAbandonedTimeout(), abandoned.getRemoveAbandonedTimeoutDuration().getSeconds());
+
+        // This is ugly, but forces GC to hit the pool
+        final WeakReference<GenericObjectPool<String, TestException>> ref = new WeakReference<>(abandoned);
+        abandoned = null;
+        while (ref.get() != null) {
+            System.gc();
+            Thread.sleep(100);
+        }
+    }
+
+
+    @Test
+    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
+    public void testAddObject() throws Exception {
+        assertEquals( 0, genericObjectPool.getNumIdle(),"should be zero idle");
+        genericObjectPool.addObject();
+        assertEquals( 1, genericObjectPool.getNumIdle(),"should be one idle");
+        assertEquals( 0, genericObjectPool.getNumActive(),"should be zero active");
+        final String obj = genericObjectPool.borrowObject();
+        assertEquals( 0, genericObjectPool.getNumIdle(),"should be zero idle");
+        assertEquals( 1, genericObjectPool.getNumActive(),"should be one active");
+        genericObjectPool.returnObject(obj);
+        assertEquals( 1, genericObjectPool.getNumIdle(),"should be one idle");
+        assertEquals( 0, genericObjectPool.getNumActive(),"should be zero active");
+    }
+
+    @Test
+    public void testAppendStats() {
+        assertFalse(genericObjectPool.getMessageStatistics());
+        assertEquals("foo", genericObjectPool.appendStats("foo"));
+        try (final GenericObjectPool<?, TestException> pool = new GenericObjectPool<>(new SimpleFactory())) {
+            pool.setMessagesStatistics(true);
+            assertNotEquals("foo", pool.appendStats("foo"));
+            pool.setMessagesStatistics(false);
+            assertEquals("foo", pool.appendStats("foo"));
+        }
+    }
+
+    /*
+     * Note: This test relies on timing for correct execution. There *should* be
+     * enough margin for this to work correctly on most (all?) systems but be
+     * aware of this if you see a failure of this test.
+     */
+    @SuppressWarnings({
+        "rawtypes", "unchecked"
+    })
+    @Test
+    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
+    public void testBorrowObjectFairness() throws Exception {
+
+        final int numThreads = 40;
+        final int maxTotal = 40;
+
+        final GenericObjectPoolConfig config = new GenericObjectPoolConfig();
+        config.setMaxTotal(maxTotal);
+        config.setMaxIdle(maxTotal);
+        config.setFairness(true);
+        config.setLifo(false);
+
+        genericObjectPool = new GenericObjectPool(simpleFactory, config);
+
+        // Exhaust the pool
+        final String[] objects = new String[maxTotal];
+        for (int i = 0; i < maxTotal; i++) {
+            objects[i] = genericObjectPool.borrowObject();
+        }
+
+        // Start and park threads waiting to borrow objects
+        final TestThread[] threads = new TestThread[numThreads];
+        for(int i=0;i<numThreads;i++) {
+            threads[i] = new TestThread(genericObjectPool, 1, 0, 2000, false, String.valueOf(i % maxTotal));
+            final Thread t = new Thread(threads[i]);
+            t.start();
+            // Short delay to ensure threads start in correct order
+            try {
+                Thread.sleep(10);
+            } catch (final InterruptedException e) {
+                fail(e.toString());
+            }
+        }
+
+        // Return objects, other threads should get served in order
+        for (int i = 0; i < maxTotal; i++) {
+            genericObjectPool.returnObject(objects[i]);
+        }
+
+        // Wait for threads to finish
+        for (int i = 0; i < numThreads; i++) {
+            while (!threads[i].complete()) {
+                Waiter.sleepQuietly(500L);
+            }
+            if (threads[i].failed()) {
+                fail("Thread " + i + " failed: " + threads[i].error.toString());
+            }
+        }
+    }
+
+    @Test
+    public void testBorrowTimings() throws Exception {
+        // Borrow
+        final String object = genericObjectPool.borrowObject();
+        final PooledObject<String> po = genericObjectPool.getPooledObject(object);
+        // In the initial state, all instants are the creation instant: last borrow, last use, last return.
+        // In the initial state, the active duration is the time between "now" and the creation time.
+        // In the initial state, the idle duration is the time between "now" and the last return, which is the creation time.
+        // But... this PO might have already been used in other tests in this class.
+
+        final Instant lastBorrowInstant1 = po.getLastBorrowInstant();
+        final Instant lastReturnInstant1 = po.getLastReturnInstant();
+        final Instant lastUsedInstant1 = po.getLastUsedInstant();
+
+        assertThat(po.getCreateInstant(), lessThanOrEqualTo(lastBorrowInstant1));
+        assertThat(po.getCreateInstant(), lessThanOrEqualTo(lastReturnInstant1));
+        assertThat(po.getCreateInstant(), lessThanOrEqualTo(lastUsedInstant1));
+        assertThat(po.getCreateTime(), lessThanOrEqualTo(lastBorrowInstant1.toEpochMilli()));
+        assertThat(po.getCreateTime(), lessThanOrEqualTo(lastReturnInstant1.toEpochMilli()));
+        assertThat(po.getCreateTime(), lessThanOrEqualTo(lastUsedInstant1.toEpochMilli()));
+
+        // Sleep MUST be "long enough" to detect that more than 0 milliseconds have elapsed.
+        // Need an API in Java 8 to get the clock granularity.
+        Thread.sleep(200);
+
+        assertFalse(po.getActiveDuration().isNegative());
+        assertFalse(po.getActiveDuration().isZero());
+        // We use greaterThanOrEqualTo instead of equal because "now" many be different when each argument is evaluated.
+        assertThat(1L, lessThanOrEqualTo(2L)); // sanity check
+        assertThat(Duration.ZERO, lessThanOrEqualTo(Duration.ZERO.plusNanos(1))); // sanity check
+        assertThat(po.getActiveDuration(), lessThanOrEqualTo(po.getIdleDuration()));
+        // Deprecated
+        assertThat(po.getActiveDuration().toMillis(), lessThanOrEqualTo(po.getActiveTimeMillis()));
+        assertThat(po.getActiveDuration(), lessThanOrEqualTo(po.getActiveTime()));
+        //
+        // TODO How to compare ID with AD since other tests may have touched the PO?
+        assertThat(po.getActiveDuration(), lessThanOrEqualTo(po.getIdleTime()));
+        assertThat(po.getActiveDuration().toMillis(), lessThanOrEqualTo(po.getIdleTimeMillis()));
+        //
+        assertThat(po.getCreateInstant(), lessThanOrEqualTo(po.getLastBorrowInstant()));
+        assertThat(po.getCreateInstant(), lessThanOrEqualTo(po.getLastReturnInstant()));
+        assertThat(po.getCreateInstant(), lessThanOrEqualTo(po.getLastUsedInstant()));
+
+        assertThat(lastBorrowInstant1, lessThanOrEqualTo(po.getLastBorrowInstant()));
+        assertThat(lastReturnInstant1, lessThanOrEqualTo(po.getLastReturnInstant()));
+        assertThat(lastUsedInstant1, lessThanOrEqualTo(po.getLastUsedInstant()));
+
+        genericObjectPool.returnObject(object);
+
+        assertFalse(po.getActiveDuration().isNegative());
+        assertFalse(po.getActiveDuration().isZero());
+        assertThat(po.getActiveDuration().toMillis(), lessThanOrEqualTo(po.getActiveTimeMillis()));
+        assertThat(po.getActiveDuration(), lessThanOrEqualTo(po.getActiveTime()));
+
+        assertThat(lastBorrowInstant1, lessThanOrEqualTo(po.getLastBorrowInstant()));
+        assertThat(lastReturnInstant1, lessThanOrEqualTo(po.getLastReturnInstant()));
+        assertThat(lastUsedInstant1, lessThanOrEqualTo(po.getLastUsedInstant()));
+    }
+
+    /**
+     * On first borrow, first object fails validation, second object is OK.
+     * Subsequent borrows are OK. This was POOL-152.
+     * @throws Exception
+     */
+    @Test
+    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
+    public void testBrokenFactoryShouldNotBlockPool() throws Exception {
+        final int maxTotal = 1;
+
+        simpleFactory.setMaxTotal(maxTotal);
+        genericObjectPool.setMaxTotal(maxTotal);
+        genericObjectPool.setBlockWhenExhausted(true);
+        genericObjectPool.setTestOnBorrow(true);
+
+        // First borrow object will need to create a new object which will fail
+        // validation.
+        String obj = null;
+        Exception ex = null;
+        simpleFactory.setValid(false);
+        try {
+            obj = genericObjectPool.borrowObject();
+        } catch (final Exception e) {
+            ex = e;
+        }
+        // Failure expected
+        assertNotNull(ex);
+        assertTrue(ex instanceof NoSuchElementException);
+        assertNull(obj);
+
+        // Configure factory to create valid objects so subsequent borrows work
+        simpleFactory.setValid(true);
+
+        // Subsequent borrows should be OK
+        obj = genericObjectPool.borrowObject();
+        assertNotNull(obj);
+        genericObjectPool.returnObject(obj);
+    }
+
+    // POOL-259
+    @Test
+    public void testClientWaitStats() throws TestException {
+        final SimpleFactory factory = new SimpleFactory();
+        // Give makeObject a little latency
+        factory.setMakeLatency(200);
+        try (final GenericObjectPool<String, TestException> pool = new GenericObjectPool<>(factory, new GenericObjectPoolConfig<>())) {
+            final String s = pool.borrowObject();
+            // First borrow waits on create, so wait time should be at least 200 ms
+            // Allow 100ms error in clock times
+            assertTrue(pool.getMaxBorrowWaitTimeMillis() >= 100);
+            assertTrue(pool.getMeanBorrowWaitTimeMillis() >= 100);
+            pool.returnObject(s);
+            pool.borrowObject();
+            // Second borrow does not have to wait on create, average should be about 100
+            assertTrue(pool.getMaxBorrowWaitTimeMillis() > 100);
+            assertTrue(pool.getMeanBorrowWaitTimeMillis() < 200);
+            assertTrue(pool.getMeanBorrowWaitTimeMillis() > 20);
+        }
+    }
+
+    @Test
+    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
+    public void testCloseMultiplePools1() {
+        try (final GenericObjectPool<String, TestException> genericObjectPool2 = new GenericObjectPool<>(simpleFactory)) {
+            genericObjectPool.setTimeBetweenEvictionRuns(TestConstants.ONE_MILLISECOND_DURATION);
+            genericObjectPool2.setTimeBetweenEvictionRuns(TestConstants.ONE_MILLISECOND_DURATION);
+        }
+        genericObjectPool.close();
+    }
+
+    @Test
+    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
+    public void testCloseMultiplePools2() throws Exception {
+        try (final GenericObjectPool<String, TestException> genericObjectPool2 = new GenericObjectPool<>(simpleFactory)) {
+            // Ensure eviction takes a long time, during which time EvictionTimer.executor's queue is empty
+            simpleFactory.setDestroyLatency(1000L);
+            // Ensure there is an object to evict, so that above latency takes effect
+            genericObjectPool.setTimeBetweenEvictionRuns(TestConstants.ONE_MILLISECOND_DURATION);
+            genericObjectPool2.setTimeBetweenEvictionRuns(TestConstants.ONE_MILLISECOND_DURATION);
+            genericObjectPool.setMinEvictableIdleTime(TestConstants.ONE_MILLISECOND_DURATION);
+            genericObjectPool2.setMinEvictableIdleTime(TestConstants.ONE_MILLISECOND_DURATION);
+            genericObjectPool.addObject();
+            genericObjectPool2.addObject();
+            // Close both pools
+        }
+        genericObjectPool.close();
+    }
+
+
+    @Test
+    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
+    public void testConcurrentBorrowAndEvict() throws Exception {
+
+        genericObjectPool.setMaxTotal(1);
+        genericObjectPool.addObject();
+
+        for (int i = 0; i < 5000; i++) {
+            final ConcurrentBorrowAndEvictThread one =
+                    new ConcurrentBorrowAndEvictThread(true);
+            final ConcurrentBorrowAndEvictThread two =
+                    new ConcurrentBorrowAndEvictThread(false);
+
+            one.start();
+            two.start();
+            one.join();
+            two.join();
+
+            genericObjectPool.returnObject(one.obj);
+
+            /* Uncomment this for a progress indication
+            if (i % 10 == 0) {
+                System.out.println(i/10);
+            }
+            */
+        }
+    }
+
+    /**
+     * POOL-231 - verify that concurrent invalidates of the same object do not
+     * corrupt pool destroyCount.
+     *
+     * @throws Exception May occur in some failure modes
+     */
+    @Test
+    public void testConcurrentInvalidate() throws Exception {
+        // Get allObjects and idleObjects loaded with some instances
+        final int nObjects = 1000;
+        genericObjectPool.setMaxTotal(nObjects);
+        genericObjectPool.setMaxIdle(nObjects);
+        final String[] obj = new String[nObjects];
+        for (int i = 0; i < nObjects; i++) {
+            obj[i] = genericObjectPool.borrowObject();
+        }
+        for (int i = 0; i < nObjects; i++) {
+            if (i % 2 == 0) {
+                genericObjectPool.returnObject(obj[i]);
+            }
+        }
+        final int nThreads = 20;
+        final int nIterations = 60;
+        final InvalidateThread[] threads = new InvalidateThread[nThreads];
+        // Randomly generated list of distinct invalidation targets
+        final ArrayList<Integer> targets = new ArrayList<>();
+        final Random random = new Random();
+        for (int j = 0; j < nIterations; j++) {
+            // Get a random invalidation target
+            Integer targ = Integer.valueOf(random.nextInt(nObjects));
+            while (targets.contains(targ)) {
+                targ = Integer.valueOf(random.nextInt(nObjects));
+            }
+            targets.add(targ);
+            // Launch nThreads threads all trying to invalidate the target
+            for (int i = 0; i < nThreads; i++) {
+                threads[i] = new InvalidateThread(genericObjectPool, obj[targ.intValue()]);
+            }
+            for (int i = 0; i < nThreads; i++) {
+                new Thread(threads[i]).start();
+            }
+            boolean done = false;
+            while (!done) {
+                done = true;
+                for (int i = 0; i < nThreads; i++) {
+                    done = done && threads[i].complete();
+                }
+                Thread.sleep(100);
+            }
+        }
+        assertEquals(nIterations, genericObjectPool.getDestroyedCount());
+    }
+
+    @Test
+    public void testConstructorNullFactory() {
+        // add dummy assert (won't be invoked because of IAE) to avoid "unused" warning
+        assertThrows(IllegalArgumentException.class,
+                () -> new GenericObjectPool<>(null));
+    }
+
+    @Test
+    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
+    public void testConstructors() {
+
+        // Make constructor arguments all different from defaults
+        final int minIdle = 2;
+        final Duration maxWaitDuration = Duration.ofMillis(3);
+        final long maxWaitMillis = maxWaitDuration.toMillis();
+        final int maxIdle = 4;
+        final int maxTotal = 5;
+        final Duration minEvictableIdleDuration = Duration.ofMillis(6);
+        final long minEvictableIdleMillis = minEvictableIdleDuration.toMillis();
+        final int numTestsPerEvictionRun = 7;
+        final boolean testOnBorrow = true;
+        final boolean testOnReturn = true;
+        final boolean testWhileIdle = true;
+        final long timeBetweenEvictionRunsMillis = 8;
+        final boolean blockWhenExhausted = false;
+        final boolean lifo = false;
+        final PooledObjectFactory<Object, RuntimeException> dummyFactory = new DummyFactory();
+        try (GenericObjectPool<Object, RuntimeException> dummyPool = new GenericObjectPool<>(dummyFactory)) {
+            assertEquals(GenericObjectPoolConfig.DEFAULT_MAX_IDLE, dummyPool.getMaxIdle());
+            assertEquals(BaseObjectPoolConfig.DEFAULT_MAX_WAIT_MILLIS, dummyPool.getMaxWaitMillis());
+            assertEquals(GenericObjectPoolConfig.DEFAULT_MIN_IDLE, dummyPool.getMinIdle());
+            assertEquals(GenericObjectPoolConfig.DEFAULT_MAX_TOTAL, dummyPool.getMaxTotal());
+            assertEquals(BaseObjectPoolConfig.DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS,
+                    dummyPool.getMinEvictableIdleTimeMillis());
+            assertEquals(BaseObjectPoolConfig.DEFAULT_MIN_EVICTABLE_IDLE_TIME,
+                    dummyPool.getMinEvictableIdleTime());
+            assertEquals(BaseObjectPoolConfig.DEFAULT_MIN_EVICTABLE_IDLE_TIME,
+                    dummyPool.getMinEvictableIdleDuration());
+            assertEquals(BaseObjectPoolConfig.DEFAULT_NUM_TESTS_PER_EVICTION_RUN,
+                    dummyPool.getNumTestsPerEvictionRun());
+            assertEquals(Boolean.valueOf(BaseObjectPoolConfig.DEFAULT_TEST_ON_BORROW),
+                    Boolean.valueOf(dummyPool.getTestOnBorrow()));
+            assertEquals(Boolean.valueOf(BaseObjectPoolConfig.DEFAULT_TEST_ON_RETURN),
+                    Boolean.valueOf(dummyPool.getTestOnReturn()));
+            assertEquals(Boolean.valueOf(BaseObjectPoolConfig.DEFAULT_TEST_WHILE_IDLE),
+                    Boolean.valueOf(dummyPool.getTestWhileIdle()));
+            assertEquals(BaseObjectPoolConfig.DEFAULT_TIME_BETWEEN_EVICTION_RUNS,
+                    dummyPool.getDurationBetweenEvictionRuns());
+            assertEquals(BaseObjectPoolConfig.DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS,
+                    dummyPool.getTimeBetweenEvictionRunsMillis());
+            assertEquals(BaseObjectPoolConfig.DEFAULT_TIME_BETWEEN_EVICTION_RUNS,
+                    dummyPool.getTimeBetweenEvictionRuns());
+            assertEquals(Boolean.valueOf(BaseObjectPoolConfig.DEFAULT_BLOCK_WHEN_EXHAUSTED),
+                    Boolean.valueOf(dummyPool.getBlockWhenExhausted()));
+            assertEquals(Boolean.valueOf(BaseObjectPoolConfig.DEFAULT_LIFO), Boolean.valueOf(dummyPool.getLifo()));
+        }
+
+        final GenericObjectPoolConfig<Object> config = new GenericObjectPoolConfig<>();
+        config.setLifo(lifo);
+        config.setMaxIdle(maxIdle);
+        config.setMinIdle(minIdle);
+        config.setMaxTotal(maxTotal);
+        config.setMaxWait(maxWaitDuration);
+        config.setMinEvictableIdleTimeMillis(minEvictableIdleMillis);
+        assertEquals(minEvictableIdleMillis, config.getMinEvictableIdleTime().toMillis());
+        config.setNumTestsPerEvictionRun(numTestsPerEvictionRun);
+        config.setTestOnBorrow(testOnBorrow);
+        config.setTestOnReturn(testOnReturn);
+        config.setTestWhileIdle(testWhileIdle);
+        config.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
+        assertEquals(timeBetweenEvictionRunsMillis, config.getTimeBetweenEvictionRuns().toMillis());
+        config.setBlockWhenExhausted(blockWhenExhausted);
+        try (GenericObjectPool<Object, RuntimeException> dummyPool = new GenericObjectPool<>(dummyFactory, config)) {
+            assertEquals(maxIdle, dummyPool.getMaxIdle());
+            assertEquals(maxWaitDuration, dummyPool.getMaxWaitDuration());
+            assertEquals(maxWaitMillis, dummyPool.getMaxWaitMillis());
+            assertEquals(minIdle, dummyPool.getMinIdle());
+            assertEquals(maxTotal, dummyPool.getMaxTotal());
+            assertEquals(minEvictableIdleMillis, dummyPool.getMinEvictableIdleTimeMillis());
+            assertEquals(numTestsPerEvictionRun, dummyPool.getNumTestsPerEvictionRun());
+            assertEquals(Boolean.valueOf(testOnBorrow), Boolean.valueOf(dummyPool.getTestOnBorrow()));
+            assertEquals(Boolean.valueOf(testOnReturn), Boolean.valueOf(dummyPool.getTestOnReturn()));
+            assertEquals(Boolean.valueOf(testWhileIdle), Boolean.valueOf(dummyPool.getTestWhileIdle()));
+            assertEquals(timeBetweenEvictionRunsMillis, dummyPool.getTimeBetweenEvictionRunsMillis());
+            assertEquals(Boolean.valueOf(blockWhenExhausted), Boolean.valueOf(dummyPool.getBlockWhenExhausted()));
+            assertEquals(Boolean.valueOf(lifo), Boolean.valueOf(dummyPool.getLifo()));
+        }
+    }
+
+    @Test
+    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
+    public void testDefaultConfiguration() {
+        assertConfiguration(new GenericObjectPoolConfig<>(),genericObjectPool);
+    }
+
+    /**
+     * Verifies that when a factory's makeObject produces instances that are not
+     * discernible by equals, the pool can handle them.
+     *
+     * JIRA: POOL-283
+     */
+    @Test
+    public void testEqualsIndiscernible() throws Exception {
+        final HashSetFactory factory = new HashSetFactory();
+        try (final GenericObjectPool<HashSet<String>, RuntimeException> pool = new GenericObjectPool<>(factory,
+                new GenericObjectPoolConfig<>())) {
+            final HashSet<String> s1 = pool.borrowObject();
+            final HashSet<String> s2 = pool.borrowObject();
+            pool.returnObject(s1);
+            pool.returnObject(s2);
+        }
+    }
+
+    @Test
+    public void testErrorFactoryDoesNotBlockThreads() throws Exception {
+
+        final CreateErrorFactory factory = new CreateErrorFactory();
+        try (final GenericObjectPool<String, InterruptedException> createFailFactoryPool = new GenericObjectPool<>(factory)) {
+
+            createFailFactoryPool.setMaxTotal(1);
+
+            // Try and borrow the first object from the pool
+            final WaitingTestThread<InterruptedException> thread1 = new WaitingTestThread<>(createFailFactoryPool, 0);
+            thread1.start();
+
+            // Wait for thread to reach semaphore
+            while (!factory.hasQueuedThreads()) {
+                Thread.sleep(200);
+            }
+
+            // Try and borrow the second object from the pool
+            final WaitingTestThread<InterruptedException> thread2 = new WaitingTestThread<>(createFailFactoryPool, 0);
+            thread2.start();
+            // Pool will not call factory since maximum number of object creations
+            // are already queued.
+
+            // Thread 2 will wait on an object being returned to the pool
+            // Give thread 2 a chance to reach this state
+            Thread.sleep(1000);
+
+            // Release thread1
+            factory.release();
+            // Pre-release thread2
+            factory.release();
+
+            // Both threads should now complete.
+            boolean threadRunning = true;
+            int count = 0;
+            while (threadRunning && count < 15) {
+                threadRunning = thread1.isAlive();
+                threadRunning = thread2.isAlive();
+                Thread.sleep(200);
+                count++;
+            }
+            assertFalse(thread1.isAlive());
+            assertFalse(thread2.isAlive());
+
+            assertTrue(thread1.thrown instanceof UnknownError);
+            assertTrue(thread2.thrown instanceof UnknownError);
+        }
+    }
+
+    /**
+     * Tests addObject contention between ensureMinIdle triggered by
+     * the Evictor with minIdle &gt; 0 and borrowObject.
+     *
+     * @throws Exception May occur in some failure modes
+     */
+    @Test
+    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
+    public void testEvictAddObjects() throws Exception {
+        simpleFactory.setMakeLatency(300);
+        simpleFactory.setMaxTotal(2);
+        genericObjectPool.setMaxTotal(2);
+        genericObjectPool.setMinIdle(1);
+        genericObjectPool.borrowObject(); // numActive = 1, numIdle = 0
+        // Create a test thread that will run once and try a borrow after
+        // 150ms fixed delay
+        final TestThread<String, TestException> borrower = new TestThread<>(genericObjectPool, 1, 150, false);
+        final Thread borrowerThread = new Thread(borrower);
+        // Set evictor to run in 100 ms - will create idle instance
+        genericObjectPool.setTimeBetweenEvictionRuns(Duration.ofMillis(100));
+        borrowerThread.start();  // Off to the races
+        borrowerThread.join();
+        assertFalse(borrower.failed());
+    }
+
+    @Test
+    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
+    public void testEvictFIFO() throws Exception {
+        checkEvict(false);
+    }
+
+    @Test
+    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
+    public void testEviction() throws Exception {
+        genericObjectPool.setMaxIdle(500);
+        genericObjectPool.setMaxTotal(500);
+        genericObjectPool.setNumTestsPerEvictionRun(100);
+        genericObjectPool.setMinEvictableIdleTime(Duration.ofMillis(250));
+        genericObjectPool.setTimeBetweenEvictionRuns(Duration.ofMillis(500));
+        genericObjectPool.setTestWhileIdle(true);
+
+        final String[] active = new String[500];
+        for (int i = 0; i < 500; i++) {
+            active[i] = genericObjectPool.borrowObject();
+        }
+        for (int i = 0; i < 500; i++) {
+            genericObjectPool.returnObject(active[i]);
+        }
+
+        Waiter.sleepQuietly(1000L);
+        assertTrue(genericObjectPool.getNumIdle() < 500,"Should be less than 500 idle, found " + genericObjectPool.getNumIdle());
+        Waiter.sleepQuietly(600L);
+        assertTrue(genericObjectPool.getNumIdle() < 400,"Should be less than 400 idle, found " + genericObjectPool.getNumIdle());
+        Waiter.sleepQuietly(600L);
+        assertTrue(genericObjectPool.getNumIdle() < 300,"Should be less than 300 idle, found " + genericObjectPool.getNumIdle());
+        Waiter.sleepQuietly(600L);
+        assertTrue(genericObjectPool.getNumIdle() < 200,"Should be less than 200 idle, found " + genericObjectPool.getNumIdle());
+        Waiter.sleepQuietly(600L);
+        assertTrue(genericObjectPool.getNumIdle() < 100,"Should be less than 100 idle, found " + genericObjectPool.getNumIdle());
+        Waiter.sleepQuietly(600L);
+        assertEquals(0,genericObjectPool.getNumIdle(),"Should be zero idle, found " + genericObjectPool.getNumIdle());
+
+        for (int i = 0; i < 500; i++) {
+            active[i] = genericObjectPool.borrowObject();
+        }
+        for (int i = 0; i < 500; i++) {
+            genericObjectPool.returnObject(active[i]);
+        }
+
+        Waiter.sleepQuietly(1000L);
+        assertTrue(genericObjectPool.getNumIdle() < 500,"Should be less than 500 idle, found " + genericObjectPool.getNumIdle());
+        Waiter.sleepQuietly(600L);
+        assertTrue(genericObjectPool.getNumIdle() < 400,"Should be less than 400 idle, found " + genericObjectPool.getNumIdle());
+        Waiter.sleepQuietly(600L);
+        assertTrue(genericObjectPool.getNumIdle() < 300,"Should be less than 300 idle, found " + genericObjectPool.getNumIdle());
+        Waiter.sleepQuietly(600L);
+        assertTrue(genericObjectPool.getNumIdle() < 200,"Should be less than 200 idle, found " + genericObjectPool.getNumIdle());
+        Waiter.sleepQuietly(600L);
+        assertTrue(genericObjectPool.getNumIdle() < 100,"Should be less than 100 idle, found " + genericObjectPool.getNumIdle());
+        Waiter.sleepQuietly(600L);
+        assertEquals(0,genericObjectPool.getNumIdle(),"Should be zero idle, found " + genericObjectPool.getNumIdle());
+    }
+
+    @Test
+    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
+    public void testEvictionInvalid() throws Exception {
+
+        try (final GenericObjectPool<Object, RuntimeException> invalidFactoryPool = new GenericObjectPool<>(new InvalidFactory())) {
+
+            invalidFactoryPool.setMaxIdle(1);
+            invalidFactoryPool.setMaxTotal(1);
+            invalidFactoryPool.setTestOnBorrow(false);
+            invalidFactoryPool.setTestOnReturn(false);
+            invalidFactoryPool.setTestWhileIdle(true);
+            invalidFactoryPool.setMinEvictableIdleTime(Duration.ofSeconds(100));
+            invalidFactoryPool.setNumTestsPerEvictionRun(1);
+
+            final Object p = invalidFactoryPool.borrowObject();
+            invalidFactoryPool.returnObject(p);
+
+            // Run eviction in a separate thread
+            final Thread t = new EvictionThread<>(invalidFactoryPool);
+            t.start();
+
+            // Sleep to make sure evictor has started
+            Thread.sleep(300);
+
+            try {
+                invalidFactoryPool.borrowObject(1);
+            } catch (final NoSuchElementException nsee) {
+                // Ignore
+            }
+
+            // Make sure evictor has finished
+            Thread.sleep(1000);
+
+            // Should have an empty pool
+            assertEquals( 0, invalidFactoryPool.getNumIdle(),"Idle count different than expected.");
+            assertEquals( 0, invalidFactoryPool.getNumActive(),"Total count different than expected.");
+        }
+    }
+
+    /**
+     * Test to make sure evictor visits least recently used objects first,
+     * regardless of FIFO/LIFO.
+     *
+     * JIRA: POOL-86
+     *
+     * @throws Exception May occur in some failure modes
+     */
+    @Test
+    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
+    public void testEvictionOrder() throws Exception {
+        checkEvictionOrder(false);
+        tearDown();
+        setUp();
+        checkEvictionOrder(true);
+    }
+
+    @Test
+    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
+    public void testEvictionPolicy() throws Exception {
+        genericObjectPool.setMaxIdle(500);
+        genericObjectPool.setMaxTotal(500);
+        genericObjectPool.setNumTestsPerEvictionRun(500);
+        genericObjectPool.setMinEvictableIdleTime(Duration.ofMillis(250));
+        genericObjectPool.setTimeBetweenEvictionRuns(Duration.ofMillis(500));
+        genericObjectPool.setTestWhileIdle(true);
+
+        // ClassNotFoundException
+        assertThrows(IllegalArgumentException.class, () -> genericObjectPool.setEvictionPolicyClassName(Long.toString(System.currentTimeMillis())),
+                "setEvictionPolicyClassName must throw an error if the class name is invalid.");
+
+        // InstantiationException
+        assertThrows(IllegalArgumentException.class, () -> genericObjectPool.setEvictionPolicyClassName(java.io.Serializable.class.getName()),
+                "setEvictionPolicyClassName must throw an error if the class name is invalid.");
+
+        // IllegalAccessException
+        assertThrows(IllegalArgumentException.class, () -> genericObjectPool.setEvictionPolicyClassName(java.util.Collections.class.getName()),
+                "setEvictionPolicyClassName must throw an error if the class name is invalid.");
+
+        assertThrows(IllegalArgumentException.class, () -> genericObjectPool.setEvictionPolicyClassName(java.lang.String.class.getName()),
+                () -> "setEvictionPolicyClassName must throw an error if a class that does not implement EvictionPolicy is specified.");
+
+        genericObjectPool.setEvictionPolicy(new TestEvictionPolicy<>());
+        assertEquals(TestEvictionPolicy.class.getName(), genericObjectPool.getEvictionPolicyClassName());
+
+        genericObjectPool.setEvictionPolicyClassName(TestEvictionPolicy.class.getName());
+        assertEquals(TestEvictionPolicy.class.getName(), genericObjectPool.getEvictionPolicyClassName());
+
+        final String[] active = new String[500];
+        for (int i = 0; i < 500; i++) {
+            active[i] = genericObjectPool.borrowObject();
+        }
+        for (int i = 0; i < 500; i++) {
+            genericObjectPool.returnObject(active[i]);
+        }
+
+        // Eviction policy ignores first 1500 attempts to evict and then always
+        // evicts. After 1s, there should have been two runs of 500 tests so no
+        // evictions
+        Waiter.sleepQuietly(1000L);
+        assertEquals(500, genericObjectPool.getNumIdle(), "Should be 500 idle");
+        // A further 1s wasn't enough so allow 2s for the evictor to clear out
+        // all of the idle objects.
+        Waiter.sleepQuietly(2000L);
+        assertEquals(0, genericObjectPool.getNumIdle(), "Should be 0 idle");
+    }
+
+    @Test
+    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
+    public void testEvictionSoftMinIdle() throws Exception {
+        class TimeTest extends BasePooledObjectFactory<TimeTest, RuntimeException> {
+            private final long createTimeMillis;
+
+            public TimeTest() {
+                createTimeMillis = System.currentTimeMillis();
+            }
+
+            @Override
+            public TimeTest create() {
+                return new TimeTest();
+            }
+
+            public long getCreateTimeMillis() {
+                return createTimeMillis;
+            }
+
+            @Override
+            public PooledObject<TimeTest> wrap(final TimeTest value) {
+                return new DefaultPooledObject<>(value);
+            }
+        }
+
+        try (final GenericObjectPool<TimeTest, RuntimeException> timePool = new GenericObjectPool<>(new TimeTest())) {
+
+            timePool.setMaxIdle(5);
+            timePool.setMaxTotal(5);
+            timePool.setNumTestsPerEvictionRun(5);
+            timePool.setMinEvictableIdle(Duration.ofSeconds(3));
+            timePool.setMinEvictableIdleTime(Duration.ofSeconds(3));
+            timePool.setSoftMinEvictableIdleTime(TestConstants.ONE_SECOND_DURATION);
+            timePool.setMinIdle(2);
+
+            final TimeTest[] active = new TimeTest[5];
+            final Long[] creationTime = new Long[5];
+            for (int i = 0; i < 5; i++) {
+                active[i] = timePool.borrowObject();
+                creationTime[i] = Long.valueOf(active[i].getCreateTimeMillis());
+            }
+
+            for (int i = 0; i < 5; i++) {
+                timePool.returnObject(active[i]);
+            }
+
+            // Soft evict all but minIdle(2)
+            Thread.sleep(1500L);
+            timePool.evict();
+            assertEquals( 2, timePool.getNumIdle(),"Idle count different than expected.");
+
+            // Hard evict the rest.
+            Thread.sleep(2000L);
+            timePool.evict();
+            assertEquals( 0, timePool.getNumIdle(),"Idle count different than expected.");
+        }
+    }
+
+    @Test
+    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
+    public void testEvictionWithNegativeNumTests() throws Exception {
+        // when numTestsPerEvictionRun is negative, it represents a fraction of the idle objects to test
+        genericObjectPool.setMaxIdle(6);
+        genericObjectPool.setMaxTotal(6);
+        genericObjectPool.setNumTestsPerEvictionRun(-2);
+        genericObjectPool.setMinEvictableIdleTime(Duration.ofMillis(50));
+        genericObjectPool.setTimeBetweenEvictionRuns(Duration.ofMillis(100));
+
+        final String[] active = new String[6];
+        for (int i = 0; i < 6; i++) {
+            active[i] = genericObjectPool.borrowObject();
+        }
+        for (int i = 0; i < 6; i++) {
+            genericObjectPool.returnObject(active[i]);
+        }
+
+        Waiter.sleepQuietly(100L);
+        assertTrue(genericObjectPool.getNumIdle() <= 6,"Should at most 6 idle, found " + genericObjectPool.getNumIdle());
+        Waiter.sleepQuietly(100L);
+        assertTrue(genericObjectPool.getNumIdle() <= 3,"Should at most 3 idle, found " + genericObjectPool.getNumIdle());
+        Waiter.sleepQuietly(100L);
+        assertTrue(genericObjectPool.getNumIdle() <= 2,"Should be at most 2 idle, found " + genericObjectPool.getNumIdle());
+        Waiter.sleepQuietly(100L);
+        assertEquals(0,genericObjectPool.getNumIdle(),"Should be zero idle, found " + genericObjectPool.getNumIdle());
+    }
+
+    @Test
+    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
+    public void testEvictLIFO() throws Exception {
+        checkEvict(true);
+    }
+
+    /**
+     * Verifies that the evictor visits objects in expected order
+     * and frequency.
+     *
+     * @throws Exception May occur in some failure modes
+     */
+    @Test
+    public void testEvictorVisiting() throws Exception {
+        checkEvictorVisiting(true);
+        checkEvictorVisiting(false);
+    }
+
+    @Test
+    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
+    public void testEvictWhileEmpty() throws Exception {
+        genericObjectPool.evict();
+        genericObjectPool.evict();
+        genericObjectPool.close();
+    }
+
+    @Test
+    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
+    public void testExceptionInValidationDuringEviction() throws Exception {
+        genericObjectPool.setMaxIdle(1);
+        genericObjectPool.setMinEvictableIdleTime(Duration.ZERO);
+        genericObjectPool.setTestWhileIdle(true);
+
+        final String active = genericObjectPool.borrowObject();
+        genericObjectPool.returnObject(active);
+
+        simpleFactory.setThrowExceptionOnValidate(true);
+
+        assertThrows(RuntimeException.class, () -> genericObjectPool.evict());
+        assertEquals(0, genericObjectPool.getNumActive());
+        assertEquals(0, genericObjectPool.getNumIdle());
+    }
+
+    @Test
+    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
+    public void testExceptionOnActivateDuringBorrow() throws Exception {
+        final String obj1 = genericObjectPool.borrowObject();
+        final String obj2 = genericObjectPool.borrowObject();
+        genericObjectPool.returnObject(obj1);
+        genericObjectPool.returnObject(obj2);
+        simpleFactory.setThrowExceptionOnActivate(true);
+        simpleFactory.setEvenValid(false);
+        // Activation will now throw every other time
+        // First attempt throws, but loop continues and second succeeds
+        final String obj = genericObjectPool.borrowObject();
+        assertEquals(1, genericObjectPool.getNumActive());
+        assertEquals(0, genericObjectPool.getNumIdle());
+
+        genericObjectPool.returnObject(obj);
+        simpleFactory.setValid(false);
+        // Validation will now fail on activation when borrowObject returns
+        // an idle instance, and then when attempting to create a new instance
+        assertThrows(NoSuchElementException.class, () -> genericObjectPool.borrowObject());
+        assertEquals(0, genericObjectPool.getNumActive());
+        assertEquals(0, genericObjectPool.getNumIdle());
+    }
+
+    @Test
+    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
+    public void testExceptionOnDestroyDuringBorrow() throws Exception {
+        simpleFactory.setThrowExceptionOnDestroy(true);
+        genericObjectPool.setTestOnBorrow(true);
+        genericObjectPool.borrowObject();
+        simpleFactory.setValid(false); // Make validation fail on next borrow attempt
+        assertThrows(NoSuchElementException.class, () -> genericObjectPool.borrowObject());
+        assertEquals(1, genericObjectPool.getNumActive());
+        assertEquals(0, genericObjectPool.getNumIdle());
+    }
+
+    @Test
+    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
+    public void testExceptionOnDestroyDuringReturn() throws Exception {
+        simpleFactory.setThrowExceptionOnDestroy(true);
+        genericObjectPool.setTestOnReturn(true);
+        final String obj1 = genericObjectPool.borrowObject();
+        genericObjectPool.borrowObject();
+        simpleFactory.setValid(false); // Make validation fail
+        genericObjectPool.returnObject(obj1);
+        assertEquals(1, genericObjectPool.getNumActive());
+        assertEquals(0, genericObjectPool.getNumIdle());
+    }
+
+    @Test
+    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
+    public void testExceptionOnPassivateDuringReturn() throws Exception {
+        final String obj = genericObjectPool.borrowObject();
+        simpleFactory.setThrowExceptionOnPassivate(true);
+        genericObjectPool.returnObject(obj);
+        assertEquals(0,genericObjectPool.getNumIdle());
+    }
+
+    @Test
+    public void testFailingFactoryDoesNotBlockThreads() throws Exception {
+
+        final CreateFailFactory factory = new CreateFailFactory();
+        try (final GenericObjectPool<String, InterruptedException> createFailFactoryPool = new GenericObjectPool<>(factory)) {
+
+            createFailFactoryPool.setMaxTotal(1);
+
+            // Try and borrow the first object from the pool
+            final WaitingTestThread<InterruptedException> thread1 = new WaitingTestThread<>(createFailFactoryPool, 0);
+            thread1.start();
+
+            // Wait for thread to reach semaphore
+            while (!factory.hasQueuedThreads()) {
+                Thread.sleep(200);
+            }
+
+            // Try and borrow the second object from the pool
+            final WaitingTestThread<InterruptedException> thread2 = new WaitingTestThread<>(createFailFactoryPool, 0);
+            thread2.start();
+            // Pool will not call factory since maximum number of object creations
+            // are already queued.
+
+            // Thread 2 will wait on an object being returned to the pool
+            // Give thread 2 a chance to reach this state
+            Thread.sleep(1000);
+
+            // Release thread1
+            factory.release();
+            // Pre-release thread2
+            factory.release();
+
+            // Both threads should now complete.
+            boolean threadRunning = true;
+            int count = 0;
+            while (threadRunning && count < 15) {
+                threadRunning = thread1.isAlive();
+                threadRunning = thread2.isAlive();
+                Thread.sleep(200);
+                count++;
+            }
+            assertFalse(thread1.isAlive());
+            assertFalse(thread2.isAlive());
+
+            assertTrue(thread1.thrown instanceof UnsupportedCharsetException);
+            assertTrue(thread2.thrown instanceof UnsupportedCharsetException);
+        }
+    }
+
+    @Test
+    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
+    public void testFIFO() throws Exception {
+        genericObjectPool.setLifo(false);
+        genericObjectPool.addObject(); // "0"
+        genericObjectPool.addObject(); // "1"
+        genericObjectPool.addObject(); // "2"
+        assertEquals( "0", genericObjectPool.borrowObject(),"Oldest");
+        assertEquals( "1", genericObjectPool.borrowObject(),"Middle");
+        assertEquals( "2", genericObjectPool.borrowObject(),"Youngest");
+        final String o = genericObjectPool.borrowObject();
+        assertEquals( "3", o,"new-3");
+        genericObjectPool.returnObject(o);
+        assertEquals( o, genericObjectPool.borrowObject(),"returned-3");
+        assertEquals( "4", genericObjectPool.borrowObject(),"new-4");
+    }
+
+    @Test
+    public void testGetFactoryType_DefaultPooledObjectFactory() {
+        try (final GenericObjectPool<String, RuntimeException> pool = new GenericObjectPool<>(createDefaultPooledObjectFactory())) {
+            assertNotNull(pool.getFactoryType());
+        }
+    }
+
+    @Test
+    public void testGetFactoryType_NullPooledObjectFactory() {
+        try (final GenericObjectPool<String, RuntimeException> pool = new GenericObjectPool<>(createNullPooledObjectFactory())) {
+            assertNotNull(pool.getFactoryType());
+        }
+    }
+
+    @Test
+    public void testGetFactoryType_PoolUtilsSynchronizedDefaultPooledFactory() {
+        try (final GenericObjectPool<String, RuntimeException> pool = new GenericObjectPool<>(
+                PoolUtils.synchronizedPooledFactory(createDefaultPooledObjectFactory()))) {
+            assertNotNull(pool.getFactoryType());
+        }
+    }
+
+    @Test
+    public void testGetFactoryType_PoolUtilsSynchronizedNullPooledFactory() {
+        try (final GenericObjectPool<String, RuntimeException> pool = new GenericObjectPool<>(
+                PoolUtils.synchronizedPooledFactory(createNullPooledObjectFactory()))) {
+            assertNotNull(pool.getFactoryType());
+        }
+    }
+
+    @Test
+    public void testGetFactoryType_SynchronizedDefaultPooledObjectFactory() {
+        try (final GenericObjectPool<String, RuntimeException> pool = new GenericObjectPool<>(
+                new TestSynchronizedPooledObjectFactory<>(createDefaultPooledObjectFactory()))) {
+            assertNotNull(pool.getFactoryType());
+        }
+    }
+
+    @Test
+    public void testGetFactoryType_SynchronizedNullPooledObjectFactory() {
+        try (final GenericObjectPool<String, RuntimeException> pool = new GenericObjectPool<>(
+                new TestSynchronizedPooledObjectFactory<>(createNullPooledObjectFactory()))) {
+            assertNotNull(pool.getFactoryType());
+        }
+    }
+
+    @Test
+    public void testGetStatsString() {
+        try (final GenericObjectPool<String, RuntimeException> pool = new GenericObjectPool<>(
+                new TestSynchronizedPooledObjectFactory<>(createNullPooledObjectFactory()))) {
+            assertNotNull(pool.getStatsString());
+        }
+    }
+
+    /**
+     * Verify that threads waiting on a depleted pool get served when a checked out object is
+     * invalidated.
+     *
+     * JIRA: POOL-240
+     *
+     * @throws Exception May occur in some failure modes
+     */
+    @Test
+    public void testInvalidateFreesCapacity() throws Exception {
+        final SimpleFactory factory = new SimpleFactory();
+        try (final GenericObjectPool<String, TestException> pool = new GenericObjectPool<>(factory)) {
+            pool.setMaxTotal(2);
+            pool.setMaxWaitMillis(500);
+            // Borrow an instance and hold if for 5 seconds
+            final WaitingTestThread<TestException> thread1 = new WaitingTestThread<>(pool, 5000);
+            thread1.start();
+            // Borrow another instance
+            final String obj = pool.borrowObject();
+            // Launch another thread - will block, but fail in 500 ms
+            final WaitingTestThread<TestException> thread2 = new WaitingTestThread<>(pool, 100);
+            thread2.start();
+            // Invalidate the object borrowed by this thread - should allow thread2 to create
+            Thread.sleep(20);
+            pool.invalidateObject(obj);
+            Thread.sleep(600); // Wait for thread2 to timeout
+            if (thread2.thrown != null) {
+                fail(thread2.thrown.toString());
+            }
+        }
+    }
+
+    /**
+     * Ensure the pool is registered.
+     */
+    @Test
+    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
+    public void testJmxRegistration() {
+        final ObjectName oname = genericObjectPool.getJmxName();
+        final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
+        final Set<ObjectName> result = mbs.queryNames(oname, null);
+        assertEquals(1, result.size());
+        genericObjectPool.jmxUnregister();
+
+        final GenericObjectPoolConfig<String> config = new GenericObjectPoolConfig<>();
+        config.setJmxEnabled(false);
+        try (final GenericObjectPool<String, TestException> poolWithoutJmx = new GenericObjectPool<>(simpleFactory, config)) {
+            assertNull(poolWithoutJmx.getJmxName());
+            config.setJmxEnabled(true);
+            poolWithoutJmx.jmxUnregister();
+        }
+
+        config.setJmxNameBase(null);
+        try (final GenericObjectPool<String, TestException> poolWithDefaultJmxNameBase = new GenericObjectPool<>(simpleFactory, config)) {
+            assertNotNull(poolWithDefaultJmxNameBase.getJmxName());
+        }
+    }
+
+    @Test
+    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
+    public void testLIFO() throws Exception {
+        final String o;
+        genericObjectPool.setLifo(true);
+        genericObjectPool.addObject(); // "0"
+        genericObjectPool.addObject(); // "1"
+        genericObjectPool.addObject(); // "2"
+        assertEquals( "2", genericObjectPool.borrowObject(),"Youngest");
+        assertEquals( "1", genericObjectPool.borrowObject(),"Middle");
+        assertEquals( "0", genericObjectPool.borrowObject(),"Oldest");
+        o = genericObjectPool.borrowObject();
+        assertEquals( "3", o,"new-3");
+        genericObjectPool.returnObject(o);
+        assertEquals( o, genericObjectPool.borrowObject(),"returned-3");
+        assertEquals( "4", genericObjectPool.borrowObject(),"new-4");
+    }
+
+    /**
+     * Test the following scenario:
+     *   Thread 1 borrows an instance
+     *   Thread 2 starts to borrow another instance before thread 1 returns its instance
+     *   Thread 1 returns its instance while thread 2 is validating its newly created instance
+     * The test verifies that the instance created by Thread 2 is not leaked.
+     *
+     * @throws Exception May occur in some failure modes
+     */
+    @Test
+    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
+    public void testMakeConcurrentWithReturn() throws Exception {
+        genericObjectPool.setTestOnBorrow(true);
+        simpleFactory.setValid(true);
+        // Borrow and return an instance, with a short wait
+        final WaitingTestThread<TestException> thread1 = new WaitingTestThread<>(genericObjectPool, 200);
+        thread1.start();
+        Thread.sleep(50); // wait for validation to succeed
+        // Slow down validation and borrow an instance
+        simpleFactory.setValidateLatency(400);
+        final String instance = genericObjectPool.borrowObject();
+        // Now make sure that we have not leaked an instance
+        assertEquals(simpleFactory.getMakeCounter(), genericObjectPool.getNumIdle() + 1);
+        genericObjectPool.returnObject(instance);
+        assertEquals(simpleFactory.getMakeCounter(), genericObjectPool.getNumIdle());
+    }
+
+    @Test
+    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
+    public void testMaxIdle() throws Exception {
+        genericObjectPool.setMaxTotal(100);
+        genericObjectPool.setMaxIdle(8);
+        final String[] active = new String[100];
+        for(int i=0;i<100;i++) {
+            active[i] = genericObjectPool.borrowObject();
+        }
+        assertEquals(100,genericObjectPool.getNumActive());
+        assertEquals(0,genericObjectPool.getNumIdle());
+        for(int i=0;i<100;i++) {
+            genericObjectPool.returnObject(active[i]);
+            assertEquals(99 - i,genericObjectPool.getNumActive());
+            assertEquals(i < 8 ? i+1 : 8,genericObjectPool.getNumIdle());
+        }
+    }
+
+    @Test
+    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
+    public void testMaxIdleZero() throws Exception {
+        genericObjectPool.setMaxTotal(100);
+        genericObjectPool.setMaxIdle(0);
+        final String[] active = new String[100];
+        for(int i=0;i<100;i++) {
+            active[i] = genericObjectPool.borrowObject();
+        }
+        assertEquals(100,genericObjectPool.getNumActive());
+        assertEquals(0,genericObjectPool.getNumIdle());
+        for(int i=0;i<100;i++) {
+            genericObjectPool.returnObject(active[i]);
+            assertEquals(99 - i,genericObjectPool.getNumActive());
+            assertEquals(0, genericObjectPool.getNumIdle());
+        }
+    }
+
+    /**
+     * Showcasing a possible deadlock situation as reported in POOL-356
+     */
+    @Test
+    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
+    @SuppressWarnings("rawtypes")
+    public void testMaxIdleZeroUnderLoad() {
+        // Config
+        final int numThreads = 199; // And main thread makes a round 200.
+        final int numIter = 20;
+        final int delay = 25;
+        final int maxTotal = 10;
+
+        simpleFactory.setMaxTotal(maxTotal);
+        genericObjectPool.setMaxTotal(maxTotal);
+        genericObjectPool.setBlockWhenExhausted(true);
+        genericObjectPool.setTimeBetweenEvictionRuns(Duration.ofMillis(-1));
+
+        // this is important to trigger POOL-356
+        genericObjectPool.setMaxIdle(0);
+
+        // Start threads to borrow objects
+        final TestThread[] threads = new TestThread[numThreads];
+        for(int i=0;i<numThreads;i++) {
+            // Factor of 2 on iterations so main thread does work whilst other
+            // threads are running. Factor of 2 on delay so average delay for
+            // other threads == actual delay for main thread
+            threads[i] = new TestThread<>(genericObjectPool, numIter * 2, delay * 2);
+            final Thread t = new Thread(threads[i]);
+            t.start();
+        }
+        // Give the threads a chance to start doing some work
+        Waiter.sleepQuietly(100L);
+
+        for (int i = 0; i < numIter; i++) {
+            String obj = null;
+            try {
+                Waiter.sleepQuietly(delay);
+                obj = genericObjectPool.borrowObject();
+                // Under load, observed numActive > maxTotal
+                if (genericObjectPool.getNumActive() > genericObjectPool.getMaxTotal()) {
+                    throw new IllegalStateException("Too many active objects");
+                }
+                Waiter.sleepQuietly(delay);
+            } catch (final Exception e) {
+                // Shouldn't happen
+                e.printStackTrace();
+                fail("Exception on borrow");
+            } finally {
+                if (obj != null) {
+                    try {
+                        genericObjectPool.returnObject(obj);
+                    } catch (final Exception e) {
+                        // Ignore
+                    }
+                }
+            }
+        }
+
+        for (int i = 0; i < numThreads; i++) {
+            while (!threads[i].complete()) {
+                Waiter.sleepQuietly(500L);
+            }
+            if (threads[i].failed()) {
+                threads[i].error.printStackTrace();
+                fail("Thread " + i + " failed: " + threads[i].error.toString());
+            }
+        }
+    }
+
+    @Test
+    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
+    public void testMaxTotal() throws Exception {
+        genericObjectPool.setMaxTotal(3);
+        genericObjectPool.setBlockWhenExhausted(false);
+
+        genericObjectPool.borrowObject();
+        genericObjectPool.borrowObject();
+        genericObjectPool.borrowObject();
+        assertThrows(NoSuchElementException.class, () -> genericObjectPool.borrowObject());
+    }
+
+    /**
+     * Verifies that maxTotal is not exceeded when factory destroyObject
+     * has high latency, testOnReturn is set and there is high incidence of
+     * validation failures.
+     */
+    @Test
+    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
+    public void testMaxTotalInvariant() {
+        final int maxTotal = 15;
+        simpleFactory.setEvenValid(false);     // Every other validation fails
+        simpleFactory.setDestroyLatency(100);  // Destroy takes 100 ms
+        simpleFactory.setMaxTotal(maxTotal); // (makes - destroys) bound
+        simpleFactory.setValidationEnabled(true);
+        genericObjectPool.setMaxTotal(maxTotal);
+        genericObjectPool.setMaxIdle(-1);
+        genericObjectPool.setTestOnReturn(true);
+        genericObjectPool.setMaxWaitMillis(1000L);
+        runTestThreads(5, 10, 50, genericObjectPool);
+    }
+
+    @Test
+    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
+    @SuppressWarnings("rawtypes")
+    public void testMaxTotalUnderLoad() {
+        // Config
+        final int numThreads = 199; // And main thread makes a round 200.
+        final int numIter = 20;
+        final int delay = 25;
+        final int maxTotal = 10;
+
+        simpleFactory.setMaxTotal(maxTotal);
+        genericObjectPool.setMaxTotal(maxTotal);
+        genericObjectPool.setBlockWhenExhausted(true);
+        genericObjectPool.setTimeBetweenEvictionRuns(Duration.ofMillis(-1));
+
+        // Start threads to borrow objects
+        final TestThread[] threads = new TestThread[numThreads];
+        for(int i=0;i<numThreads;i++) {
+            // Factor of 2 on iterations so main thread does work whilst other
+            // threads are running. Factor of 2 on delay so average delay for
+            // other threads == actual delay for main thread
+            threads[i] = new TestThread<>(genericObjectPool, numIter * 2, delay * 2);
+            final Thread t = new Thread(threads[i]);
+            t.start();
+        }
+        // Give the threads a chance to start doing some work
+        Waiter.sleepQuietly(5000);
+
+        for (int i = 0; i < numIter; i++) {
+            String obj = null;
+            try {
+                Waiter.sleepQuietly(delay);
+                obj = genericObjectPool.borrowObject();
+                // Under load, observed numActive > maxTotal
+                if (genericObjectPool.getNumActive() > genericObjectPool.getMaxTotal()) {
+                    throw new IllegalStateException("Too many active objects");
+                }
+                Waiter.sleepQuietly(delay);
+            } catch (final Exception e) {
+                // Shouldn't happen
+                e.printStackTrace();
+                fail("Exception on borrow");
+            } finally {
+                if (obj != null) {
+                    try {
+                        genericObjectPool.returnObject(obj);
+                    } catch (final Exception e) {
+                        // Ignore
+                    }
+                }
+            }
+        }
+
+        for (int i = 0; i < numThreads; i++) {
+            while(!threads[i].complete()) {
+                Waiter.sleepQuietly(500L);
+            }
+            if(threads[i].failed()) {
+                fail("Thread " + i + " failed: " + threads[i].error.toString());
+            }
+        }
+    }
+
+    @Test
+    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
+    public void testMaxTotalZero() throws Exception {
+        genericObjectPool.setMaxTotal(0);
+        genericObjectPool.setBlockWhenExhausted(false);
+        assertThrows(NoSuchElementException.class, () -> genericObjectPool.borrowObject());
+    }
+
+    /*
+     * Test multi-threaded pool access.
+     * Multiple threads, but maxTotal only allows half the threads to succeed.
+     *
+     * This test was prompted by Continuum build failures in the Commons DBCP test case:
+     * TestPerUserPoolDataSource.testMultipleThreads2()
+     * Let's see if the this fails on Continuum too!
+     */
+    @Test
+    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
+    public void testMaxWaitMultiThreaded() throws Exception {
+        final long maxWait = 500; // wait for connection
+        final long holdTime = 2 * maxWait; // how long to hold connection
+        final int threads = 10; // number of threads to grab the object initially
+        genericObjectPool.setBlockWhenExhausted(true);
+        genericObjectPool.setMaxWaitMillis(maxWait);
+        genericObjectPool.setMaxTotal(threads);
+        // Create enough threads so half the threads will have to wait
+        final WaitingTestThread<TestException>[] wtt = new WaitingTestThread[threads * 2];
+        for (int i = 0; i < wtt.length; i++) {
+            wtt[i] = new WaitingTestThread<>(genericObjectPool, holdTime);
+        }
+        final long originMillis = System.currentTimeMillis() - 1000;
+        for (final WaitingTestThread<TestException> element : wtt) {
+            element.start();
+        }
+        int failed = 0;
+        for (final WaitingTestThread<TestException> element : wtt) {
+            element.join();
+            if (element.thrown != null){
+                failed++;
+            }
+        }
+        if (DISPLAY_THREAD_DETAILS || wtt.length/2 != failed){
+            System.out.println(
+                    "MaxWait: " + maxWait +
+                    " HoldTime: " + holdTime +
+                     " MaxTotal: " + threads +
+                    " Threads: " + wtt.length +
+                    " Failed: " + failed
+                    );
+            for (final WaitingTestThread<TestException> wt : wtt) {
+                System.out.println(
+                        "PreBorrow: " + (wt.preBorrowMillis - originMillis) +
+                        " PostBorrow: " + (wt.postBorrowMillis != 0 ? wt.postBorrowMillis - originMillis : -1) +
+                        " BorrowTime: " + (wt.postBorrowMillis != 0 ? wt.postBorrowMillis - wt.preBorrowMillis : -1) +
+                        " PostReturn: " + (wt.postReturnMillis != 0 ? wt.postReturnMillis - originMillis : -1) +
+                        " Ended: " + (wt.endedMillis - originMillis) +
+                        " ObjId: " + wt.objectId
+                        );
+            }
+        }
+        assertEquals(wtt.length / 2, failed,"Expected half the threads to fail");
+    }
+
+    @Test
+    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
+    public void testMinIdle() throws Exception {
+        genericObjectPool.setMaxIdle(500);
+        genericObjectPool.setMinIdle(5);
+        genericObjectPool.setMaxTotal(10);
+        genericObjectPool.setNumTestsPerEvictionRun(0);
+        genericObjectPool.setMinEvictableIdleTime(Duration.ofMillis(50));
+        genericObjectPool.setTimeBetweenEvictionRuns(Duration.ofMillis(100));
+        genericObjectPool.setTestWhileIdle(true);
+
+        Waiter.sleepQuietly(150L);
+        assertEquals(5, genericObjectPool.getNumIdle(), "Should be 5 idle, found " + genericObjectPool.getNumIdle());
+
+        final String[] active = new String[5];
+        active[0] = genericObjectPool.borrowObject();
+
+        Waiter.sleepQuietly(150L);
+        assertEquals(5, genericObjectPool.getNumIdle(), "Should be 5 idle, found " + genericObjectPool.getNumIdle());
+
+        for (int i = 1; i < 5; i++) {
+            active[i] = genericObjectPool.borrowObject();
+        }
+
+        Waiter.sleepQuietly(150L);
+        assertEquals(5, genericObjectPool.getNumIdle(), "Should be 5 idle, found " + genericObjectPool.getNumIdle());
+
+        for (int i = 0; i < 5; i++) {
+            genericObjectPool.returnObject(active[i]);
+        }
+
+        Waiter.sleepQuietly(150L);
+        assertEquals(10, genericObjectPool.getNumIdle(), "Should be 10 idle, found " + genericObjectPool.getNumIdle());
+    }
+
+    @Test
+    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
+    public void testMinIdleMaxTotal() throws Exception {
+        genericObjectPool.setMaxIdle(500);
+        genericObjectPool.setMinIdle(5);
+        genericObjectPool.setMaxTotal(10);
+        genericObjectPool.setNumTestsPerEvictionRun(0);
+        genericObjectPool.setMinEvictableIdleTime(Duration.ofMillis(50));
+        genericObjectPool.setTimeBetweenEvictionRuns(Duration.ofMillis(100));
+        genericObjectPool.setTestWhileIdle(true);
+
+        Waiter.sleepQuietly(150L);
+        assertEquals(5, genericObjectPool.getNumIdle(), "Should be 5 idle, found " + genericObjectPool.getNumIdle());
+
+        final String[] active = new String[10];
+
+        Waiter.sleepQuietly(150L);
+        assertEquals(5, genericObjectPool.getNumIdle(), "Should be 5 idle, found " + genericObjectPool.getNumIdle());
+
+        for (int i = 0; i < 5; i++) {
+            active[i] = genericObjectPool.borrowObject();
+        }
+
+        Waiter.sleepQuietly(150L);
+        assertEquals(5, genericObjectPool.getNumIdle(), "Should be 5 idle, found " + genericObjectPool.getNumIdle());
+
+        for(int i = 0 ; i < 5 ; i++) {
+            genericObjectPool.returnObject(active[i]);
+        }
+
+        Waiter.sleepQuietly(150L);
+        assertEquals(10, genericObjectPool.getNumIdle(), "Should be 10 idle, found " + genericObjectPool.getNumIdle());
+
+        for (int i = 0; i < 10; i++) {
+            active[i] = genericObjectPool.borrowObject();
+        }
+
+        Waiter.sleepQuietly(150L);
+        assertEquals(0, genericObjectPool.getNumIdle(), "Should be 0 idle, found " + genericObjectPool.getNumIdle());
+
+        for (int i = 0; i < 10; i++) {
+            genericObjectPool.returnObject(active[i]);
+        }
+
+        Waiter.sleepQuietly(150L);
+        assertEquals(10, genericObjectPool.getNumIdle(), "Should be 10 idle, found " + genericObjectPool.getNumIdle());
+    }
+
+    /**
+     * Verifies that returning an object twice (without borrow in between) causes ISE
+     * but does not re-validate or re-passivate the instance.
+     *
+     * JIRA: POOL-285
+     */
+    @Test
+    public void testMultipleReturn() throws Exception {
+        final WaiterFactory<String> factory = new WaiterFactory<>(0, 0, 0, 0, 0, 0);
+        try (final GenericObjectPool<Waiter, IllegalStateException> pool = new GenericObjectPool<>(factory)) {
+            pool.setTestOnReturn(true);
+            final Waiter waiter = pool.borrowObject();
+            pool.returnObject(waiter);
+            assertEquals(1, waiter.getValidationCount());
+            assertEquals(1, waiter.getPassivationCount());
+            try {
+                pool.returnObject(waiter);
+                fail("Expecting IllegalStateException from multiple return");
+            } catch (final IllegalStateException ex) {
+                // Exception is expected, now check no repeat validation/passivation
+                assertEquals(1, waiter.getValidationCount());
+                assertEquals(1, waiter.getPassivationCount());
+            }
+        }
+    }
+
+    // POOL-248
+    @Test
+    public void testMultipleReturnOfSameObject() throws Exception {
+        try (final GenericObjectPool<String, TestException> pool = new GenericObjectPool<>(simpleFactory, new GenericObjectPoolConfig<>())) {
+
+            assertEquals(0, pool.getNumActive());
+            assertEquals(0, pool.getNumIdle());
+
+            final String obj = pool.borrowObject();
+
+            assertEquals(1, pool.getNumActive());
+            assertEquals(0, pool.getNumIdle());
+
+            pool.returnObject(obj);
+
+            assertEquals(0, pool.getNumActive());
+            assertEquals(1, pool.getNumIdle());
+
+            assertThrows(IllegalStateException.class,
+                    () -> pool.returnObject(obj));
+
+            assertEquals(0, pool.getNumActive());
+            assertEquals(1, pool.getNumIdle());
+        }
+    }
+
+    /**
+     * Verifies that when a borrowed object is mutated in a way that does not
+     * preserve equality and hashcode, the pool can recognized it on return.
+     *
+     * JIRA: POOL-284
+     */
+    @Test
+    public void testMutable() throws Exception {
+        final HashSetFactory factory = new HashSetFactory();
+        try (final GenericObjectPool<HashSet<String>, RuntimeException> pool = new GenericObjectPool<>(factory,
+                new GenericObjectPoolConfig<>())) {
+            final HashSet<String> s1 = pool.borrowObject();
+            final HashSet<String> s2 = pool.borrowObject();
+            s1.add("One");
+            s2.add("One");
+            pool.returnObject(s1);
+            pool.returnObject(s2);
+        }
+    }
+
+    @Test
+    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
+    public void testNegativeMaxTotal() throws Exception {
+        genericObjectPool.setMaxTotal(-1);
+        genericObjectPool.setBlockWhenExhausted(false);
+        final String obj = genericObjectPool.borrowObject();
+        assertEquals(getNthObject(0),obj);
+        genericObjectPool.returnObject(obj);
+    }
+
+    /**
+     * Verifies that concurrent threads never "share" instances
+     */
+    @Test
+    public void testNoInstanceOverlap() {
+        final int maxTotal = 5;
+        final int numThreads = 100;
+        final int delay = 1;
+        final int iterations = 1000;
+        final AtomicIntegerFactory factory = new AtomicIntegerFactory();
+        try (final GenericObjectPool<AtomicInteger, RuntimeException> pool = new GenericObjectPool<>(factory)) {
+            pool.setMaxTotal(maxTotal);
+            pool.setMaxIdle(maxTotal);
+            pool.setTestOnBorrow(true);
+            pool.setBlockWhenExhausted(true);
+            pool.setMaxWaitMillis(-1);
+            runTestThreads(numThreads, iterations, delay, pool);
+            assertEquals(0, pool.getDestroyedByBorrowValidationCount());
+        }
+    }
+
+    /**
+     * POOL-376
+     */
+    @Test
+    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
+    public void testNoInvalidateNPE() throws Exception {
+        genericObjectPool.setMaxTotal(1);
+        genericObjectPool.setTestOnCreate(true);
+        genericObjectPool.setMaxWaitMillis(-1);
+        final String obj = genericObjectPool.borrowObject();
+        // Make validation fail - this will cause create() to return null
+        simpleFactory.setValid(false);
+        // Create a take waiter
+        final WaitingTestThread<TestException> wtt = new WaitingTestThread<>(genericObjectPool, 200);
+        wtt.start();
+        // Give wtt time to start
+        Thread.sleep(200);
+        genericObjectPool.invalidateObject(obj);
+        // Now allow create to succeed so waiter can be served
+        simpleFactory.setValid(true);
+    }
+
+    public void testPreparePool() throws Exception {
+        genericObjectPool.setMinIdle(1);
+        genericObjectPool.setMaxTotal(1);
+        genericObjectPool.preparePool();
+        assertEquals(1, genericObjectPool.getNumIdle());
+        final String obj = genericObjectPool.borrowObject();
+        genericObjectPool.preparePool();
+        assertEquals(0, genericObjectPool.getNumIdle());
+        genericObjectPool.setMinIdle(0);
+        genericObjectPool.returnObject(obj);
+        genericObjectPool.preparePool();
+        assertEquals(0, genericObjectPool.getNumIdle());
+    }
+
+    @Test/* maxWaitMillis x2 + padding */
+    @Timeout(value = 1200, unit = TimeUnit.MILLISECONDS)
+    public void testReturnBorrowObjectWithingMaxWaitMillis() throws Exception {
+        final long maxWaitMillis = 500;
+
+        try (final GenericObjectPool<String, InterruptedException> createSlowObjectFactoryPool = new GenericObjectPool<>(
+                createSlowObjectFactory(60000))) {
+            createSlowObjectFactoryPool.setMaxTotal(1);
+            createSlowObjectFactoryPool.setMaxWaitMillis(maxWaitMillis);
+
+            // thread1 tries creating a slow object to make pool full.
+            final WaitingTestThread<InterruptedException> thread1 = new WaitingTestThread<>(createSlowObjectFactoryPool, 0);
+            thread1.start();
+
+            // Wait for thread1's reaching to create().
+            Thread.sleep(100);
+
+            // another one tries borrowObject. It should return within maxWaitMillis.
+            assertThrows(NoSuchElementException.class, () -> createSlowObjectFactoryPool.borrowObject(maxWaitMillis),
+                    "borrowObject must fail due to timeout by maxWaitMillis");
+
+            assertTrue(thread1.isAlive());
+        }
+    }
+
+    /**
+     * This is the test case for POOL-263. It is disabled since it will always
+     * pass without artificial delay being injected into GOP.returnObject() and
+     * a way to this hasn't currently been found that doesn't involve
+     * polluting the GOP implementation. The artificial delay needs to be
+     * inserted just before the final call to isLifo() in the returnObject()
+     * method.
+     */
+    //@Test
+    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
+    public void testReturnObject() throws Exception {
+
+        genericObjectPool.setMaxTotal(1);
+        genericObjectPool.setMaxIdle(-1);
+        final String active = genericObjectPool.borrowObject();
+
+        assertEquals(1, genericObjectPool.getNumActive());
+        assertEquals(0, genericObjectPool.getNumIdle());
+
+        final Thread t = new Thread(() -> genericObjectPool.close());
+        t.start();
+
+        genericObjectPool.returnObject(active);
+
+        // Wait for the close() thread to complete
+        while (t.isAlive()) {
+            Thread.sleep(50);
+        }
+
+        assertEquals(0, genericObjectPool.getNumIdle());
+    }
+
+    @Test
+    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
+    public void testSetConfig() throws Exception {
+        final GenericObjectPoolConfig<String> expected = new GenericObjectPoolConfig<>();
+        assertConfiguration(expected,genericObjectPool);
+        expected.setMaxTotal(2);
+        expected.setMaxIdle(3);
+        expected.setMaxWait(Duration.ofMillis(5));
+        expected.setMinEvictableIdleTime(Duration.ofMillis(7L));
+        expected.setNumTestsPerEvictionRun(9);
+        expected.setTestOnCreate(true);
+        expected.setTestOnBorrow(true);
+        expected.setTestOnReturn(true);
+        expected.setTestWhileIdle(true);
+        expected.setTimeBetweenEvictionRuns(Duration.ofMillis(11L));
+        expected.setBlockWhenExhausted(false);
+        genericObjectPool.setConfig(expected);
+        assertConfiguration(expected,genericObjectPool);
+    }
+
+    @Test
+    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
+    public void testSettersAndGetters() throws Exception {
+        {
+            // The object receives an Exception during its creation to prevent
+            // memory leaks. See BaseGenericObjectPool constructor for more details.
+            assertNotEquals("", genericObjectPool.getCreationStackTrace());
+        }
+        {
+            assertEquals(0, genericObjectPool.getBorrowedCount());
+        }
+        {
+            assertEquals(0, genericObjectPool.getReturnedCount());
+        }
+        {
+            assertEquals(0, genericObjectPool.getCreatedCount());
+        }
+        {
+            assertEquals(0, genericObjectPool.getDestroyedCount());
+        }
+        {
+            assertEquals(0, genericObjectPool.getDestroyedByEvictorCount());
+        }
+        {
+            assertEquals(0, genericObjectPool.getDestroyedByBorrowValidationCount());
+        }
+        {
+            assertEquals(0, genericObjectPool.getMeanActiveTimeMillis());
+        }
+        {
+            assertEquals(0, genericObjectPool.getMeanIdleTimeMillis());
+        }
+        {
+            assertEquals(0, genericObjectPool.getMeanBorrowWaitTimeMillis());
+        }
+        {
+            assertEquals(0, genericObjectPool.getMaxBorrowWaitTimeMillis());
+        }
+        {
+            assertEquals(0, genericObjectPool.getNumIdle());
+        }
+        {
+            genericObjectPool.setMaxTotal(123);
+            assertEquals(123,genericObjectPool.getMaxTotal());
+        }
+        {
+            genericObjectPool.setMaxIdle(12);
+            assertEquals(12,genericObjectPool.getMaxIdle());
+        }
+        {
+            genericObjectPool.setMaxWaitMillis(1234L);
+            assertEquals(1234L,genericObjectPool.getMaxWaitMillis());
+        }
+        {
+            genericObjectPool.setMinEvictableIdleTimeMillis(12345L);
+            assertEquals(12345L,genericObjectPool.getMinEvictableIdleDuration().toMillis());
+            assertEquals(12345L,genericObjectPool.getMinEvictableIdleTimeMillis());
+            assertEquals(12345L,genericObjectPool.getMinEvictableIdleTime().toMillis());
+        }
+        {
+            genericObjectPool.setNumTestsPerEvictionRun(11);
+            assertEquals(11,genericObjectPool.getNumTestsPerEvictionRun());
+        }
+        {
+            genericObjectPool.setTestOnBorrow(true);
+            assertTrue(genericObjectPool.getTestOnBorrow());
+            genericObjectPool.setTestOnBorrow(false);
+            assertFalse(genericObjectPool.getTestOnBorrow());
+        }
+        {
+            genericObjectPool.setTestOnReturn(true);
+            assertTrue(genericObjectPool.getTestOnReturn());
+            genericObjectPool.setTestOnReturn(false);
+            assertFalse(genericObjectPool.getTestOnReturn());
+        }
+        {
+            genericObjectPool.setTestWhileIdle(true);
+            assertTrue(genericObjectPool.getTestWhileIdle());
+            genericObjectPool.setTestWhileIdle(false);
+            assertFalse(genericObjectPool.getTestWhileIdle());
+        }
+        {
+            genericObjectPool.setTimeBetweenEvictionRunsMillis(11235L);
+            assertEquals(11235L,genericObjectPool.getDurationBetweenEvictionRuns().toMillis());
+            assertEquals(11235L,genericObjectPool.getTimeBetweenEvictionRunsMillis());
+            assertEquals(11235L,genericObjectPool.getTimeBetweenEvictionRuns().toMillis());
+        }
+        {
+            genericObjectPool.setSoftMinEvictableIdleTimeMillis(12135L);
+            assertEquals(12135L,genericObjectPool.getSoftMinEvictableIdleDuration().toMillis());
+            assertEquals(12135L,genericObjectPool.getSoftMinEvictableIdleTimeMillis());
+            assertEquals(12135L,genericObjectPool.getSoftMinEvictableIdleTime().toMillis());
+        }
+        {
+            genericObjectPool.setBlockWhenExhausted(true);
+            assertTrue(genericObjectPool.getBlockWhenExhausted());
+            genericObjectPool.setBlockWhenExhausted(false);
+            assertFalse(genericObjectPool.getBlockWhenExhausted());
+        }
+    }
+
+    @Test
+    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
+    public void testStartAndStopEvictor() throws Exception {
+        // set up pool without evictor
+        genericObjectPool.setMaxIdle(6);
+        genericObjectPool.setMaxTotal(6);
+        genericObjectPool.setNumTestsPerEvictionRun(6);
+        genericObjectPool.setMinEvictableIdleTime(Duration.ofMillis(100));
+
+        for (int j = 0; j < 2; j++) {
+            // populate the pool
+            {
+                final String[] active = new String[6];
+                for (int i = 0; i < 6; i++) {
+                    active[i] = genericObjectPool.borrowObject();
+                }
+                for (int i = 0; i < 6; i++) {
+                    genericObjectPool.returnObject(active[i]);
+                }
+            }
+
+            // note that it stays populated
+            assertEquals(6,genericObjectPool.getNumIdle(),"Should have 6 idle");
+
+            // start the evictor
+            genericObjectPool.setTimeBetweenEvictionRuns(Duration.ofMillis(50));
+
+            // wait a second (well, .2 seconds)
+            Waiter.sleepQuietly(200L);
+
+            // assert that the evictor has cleared out the pool
+            assertEquals(0,genericObjectPool.getNumIdle(),"Should have 0 idle");
+
+            // stop the evictor
+            genericObjectPool.startEvictor(Duration.ZERO);
+        }
+    }
+
+    @Test
+    public void testSwallowedExceptionListener() {
+        genericObjectPool.setSwallowedExceptionListener(null); // must simply return
+        final List<Exception> swallowedExceptions = new ArrayList<>();
+        /*
+         * A simple listener, that will throw a OOM on 3rd exception.
+         */
+        final SwallowedExceptionListener listener = e -> {
+            if (swallowedExceptions.size() == 2) {
+                throw new OutOfMemoryError();
+            }
+            swallowedExceptions.add(e);
+        };
+        genericObjectPool.setSwallowedExceptionListener(listener);
+
+        final Exception e1 = new Exception();
+        final Exception e2 = new ArrayIndexOutOfBoundsException();
+
+        genericObjectPool.swallowException(e1);
+        genericObjectPool.swallowException(e2);
+
+        assertThrows(OutOfMemoryError.class, () -> genericObjectPool.swallowException(e1));
+
+        assertEquals(2, swallowedExceptions.size());
+    }
+
+    @Test
+    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
+    public void testThreaded1() throws Exception {
+        genericObjectPool.setMaxTotal(15);
+        genericObjectPool.setMaxIdle(15);
+        genericObjectPool.setMaxWaitMillis(1000L);
+        runTestThreads(20, 100, 50, genericObjectPool);
+    }
+
+    @Test
+    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
+    public void testTimeoutNoLeak() throws Exception {
+        genericObjectPool.setMaxTotal(2);
+        genericObjectPool.setMaxWaitMillis(10);
+        genericObjectPool.setBlockWhenExhausted(true);
+        final String obj = genericObjectPool.borrowObject();
+        final String obj2 = genericObjectPool.borrowObject();
+        assertThrows(NoSuchElementException.class, () -> genericObjectPool.borrowObject());
+        genericObjectPool.returnObject(obj2);
+        genericObjectPool.returnObject(obj);
+
+        genericObjectPool.borrowObject();
+        genericObjectPool.borrowObject();
+    }
+
+    /**
+     * Tests POOL-361
+     */
+    @Test
+    public void testValidateOnCreate() throws Exception {
+        genericObjectPool.setTestOnCreate(true);
+        genericObjectPool.addObject();
+        assertEquals(1, simpleFactory.validateCounter);
+    }
+
+    /**
+     * Tests POOL-361
+     */
+    @Test
+    public void testValidateOnCreateFailure() throws Exception {
+        genericObjectPool.setTestOnCreate(true);
+        genericObjectPool.setTestOnBorrow(false);
+        genericObjectPool.setMaxTotal(2);
+        simpleFactory.setValid(false);
+        // Make sure failed validations do not leak capacity
+        genericObjectPool.addObject();
+        genericObjectPool.addObject();
+        assertEquals(0, genericObjectPool.getNumIdle());
+        assertEquals(0, genericObjectPool.getNumActive());
+        simpleFactory.setValid(true);
+        final String obj = genericObjectPool.borrowObject();
+        assertNotNull(obj);
+        genericObjectPool.addObject();
+        // Should have one idle, one out now
+        assertEquals(1, genericObjectPool.getNumIdle());
+        assertEquals(1, genericObjectPool.getNumActive());
+	}
+
+    /**
+     * Verify that threads waiting on a depleted pool get served when a returning object fails
+     * validation.
+     *
+     * JIRA: POOL-240
+     *
+     * @throws Exception May occur in some failure modes
+     */
+    @Test
+    public void testValidationFailureOnReturnFreesCapacity() throws Exception {
+        final SimpleFactory factory = new SimpleFactory();
+        factory.setValid(false); // Validate will always fail
+        factory.setValidationEnabled(true);
+        try (final GenericObjectPool<String, TestException> pool = new GenericObjectPool<>(factory)) {
+            pool.setMaxTotal(2);
+            pool.setMaxWaitMillis(1500);
+            pool.setTestOnReturn(true);
+            pool.setTestOnBorrow(false);
+            // Borrow an instance and hold if for 5 seconds
+            final WaitingTestThread<TestException> thread1 = new WaitingTestThread<>(pool, 5000);
+            thread1.start();
+            // Borrow another instance and return it after 500 ms (validation will fail)
+            final WaitingTestThread<TestException> thread2 = new WaitingTestThread<>(pool, 500);
+            thread2.start();
+            Thread.sleep(50);
+            // Try to borrow an object
+            final String obj = pool.borrowObject();
+            pool.returnObject(obj);
+        }
+    }
+
+    // POOL-276
+    @Test
+    public void testValidationOnCreateOnly() throws Exception {
+        genericObjectPool.setMaxTotal(1);
+        genericObjectPool.setTestOnCreate(true);
+        genericObjectPool.setTestOnBorrow(false);
+        genericObjectPool.setTestOnReturn(false);
+        genericObjectPool.setTestWhileIdle(false);
+
+        final String o1 = genericObjectPool.borrowObject();
+        assertEquals("0", o1);
+        final Timer t = new Timer();
+        t.schedule(
+                new TimerTask() {
+                    @Override
+                    public void run() {
+                        genericObjectPool.returnObject(o1);
+                    }
+                }, 3000);
+
+        final String o2 = genericObjectPool.borrowObject();
+        assertEquals("0", o2);
+
+        assertEquals(1, simpleFactory.validateCounter);
+    }
+
+    @Test
+    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
+    public void testWhenExhaustedBlock() throws Exception {
+        genericObjectPool.setMaxTotal(1);
+        genericObjectPool.setBlockWhenExhausted(true);
+        genericObjectPool.setMaxWaitMillis(10L);
+        final String obj1 = genericObjectPool.borrowObject();
+        assertNotNull(obj1);
+        assertThrows(NoSuchElementException.class, () -> genericObjectPool.borrowObject());
+        genericObjectPool.returnObject(obj1);
+        genericObjectPool.close();
+    }
+
+    /**
+     * POOL-189
+     *
+     * @throws Exception May occur in some failure modes
+     */
+    @Test
+    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
+    public void testWhenExhaustedBlockClosePool() throws Exception {
+        genericObjectPool.setMaxTotal(1);
+        genericObjectPool.setBlockWhenExhausted(true);
+        genericObjectPool.setMaxWaitMillis(-1);
+        final Object obj1 = genericObjectPool.borrowObject();
+
+        // Make sure an object was obtained
+        assertNotNull(obj1);
+
+        // Create a separate thread to try and borrow another object
+        final WaitingTestThread<TestException> wtt = new WaitingTestThread<>(genericObjectPool, 200);
+        wtt.start();
+        // Give wtt time to start
+        Thread.sleep(200);
+
+        // close the pool (Bug POOL-189)
+        genericObjectPool.close();
+
+        // Give interrupt time to take effect
+        Thread.sleep(200);
+
+        // Check thread was interrupted
+        assertTrue(wtt.thrown instanceof InterruptedException);
+    }
+
+    @Test
+    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
+    public void testWhenExhaustedBlockInterrupt() throws Exception {
+        genericObjectPool.setMaxTotal(1);
+        genericObjectPool.setBlockWhenExhausted(true);
+        genericObjectPool.setMaxWaitMillis(-1);
+        final String obj1 = genericObjectPool.borrowObject();
+
+        // Make sure on object was obtained
+        assertNotNull(obj1);
+
+        // Create a separate thread to try and borrow another object
+        final WaitingTestThread<TestException> wtt = new WaitingTestThread<>(genericObjectPool, 200000);
+        wtt.start();
+        // Give wtt time to start
+        Thread.sleep(200);
+        wtt.interrupt();
+
+        // Give interrupt time to take effect
+        Thread.sleep(200);
+
+        // Check thread was interrupted
+        assertTrue(wtt.thrown instanceof InterruptedException);
+
+        // Return object to the pool
+        genericObjectPool.returnObject(obj1);
+
+        // Bug POOL-162 - check there is now an object in the pool
+        genericObjectPool.setMaxWaitMillis(10L);
+        String obj2 = null;
+        try {
+            obj2 = genericObjectPool.borrowObject();
+            assertNotNull(obj2);
+        } catch (final NoSuchElementException e) {
+            // Not expected
+            fail("NoSuchElementException not expected");
+        }
+        genericObjectPool.returnObject(obj2);
+        genericObjectPool.close();
+
+    }
+
+    @Test
+    @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
+    public void testWhenExhaustedFail() throws Exception {
+        genericObjectPool.setMaxTotal(1);
+        genericObjectPool.setBlockWhenExhausted(false);
+        final String obj1 = genericObjectPool.borrowObject();
+        assertNotNull(obj1);
+        assertThrows(NoSuchElementException.class, () -> genericObjectPool.borrowObject());
+        genericObjectPool.returnObject(obj1);
+        assertEquals(1, genericObjectPool.getNumIdle());
+        genericObjectPool.close();
+    }
+
+}


[commons-pool] 04/04: Add PMD check to default Maven goal

Posted by gg...@apache.org.
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 4c86763f00c908aef5703352e8fb7d9bdddf6525
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Sat Jul 2 20:08:59 2022 -0400

    Add PMD check to default Maven goal
    
    PMD ignore empty control statement.
---
 pom.xml                                                                | 2 +-
 .../java/org/apache/commons/pool2/impl/SoftReferenceObjectPool.java    | 3 +--
 2 files changed, 2 insertions(+), 3 deletions(-)

diff --git a/pom.xml b/pom.xml
index 886f3262..5da5b9b8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -210,7 +210,7 @@
   </properties> 
 
   <build>
-      <defaultGoal>clean verify apache-rat:check checkstyle:check japicmp:cmp javadoc:javadoc spotbugs:check</defaultGoal>
+      <defaultGoal>clean verify apache-rat:check checkstyle:check japicmp:cmp javadoc:javadoc spotbugs:check pmd:check</defaultGoal>
       <pluginManagement>
         <plugins>
           <plugin>
diff --git a/src/main/java/org/apache/commons/pool2/impl/SoftReferenceObjectPool.java b/src/main/java/org/apache/commons/pool2/impl/SoftReferenceObjectPool.java
index bc5ad180..1093cbc5 100644
--- a/src/main/java/org/apache/commons/pool2/impl/SoftReferenceObjectPool.java
+++ b/src/main/java/org/apache/commons/pool2/impl/SoftReferenceObjectPool.java
@@ -353,8 +353,7 @@ public class SoftReferenceObjectPool<T, E extends Exception> extends BaseObjectP
         // Remove wrappers for enqueued references from idle and allReferences lists
         removeClearedReferences(idleReferences.iterator());
         removeClearedReferences(allReferences.iterator());
-        while (refQueue.poll() != null) {
-            // draining queue
+        while (refQueue.poll() != null) { // NOPMD
         }
     }
 


[commons-pool] 03/04: Bump pmd from 6.44.0 to 6.47.0

Posted by gg...@apache.org.
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 eeaddae47a0de4fb52edc81cc66097d832c3c73a
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Sat Jul 2 19:30:19 2022 -0400

    Bump pmd from 6.44.0 to 6.47.0
---
 pom.xml                 | 2 ++
 src/changes/changes.xml | 3 +++
 2 files changed, 5 insertions(+)

diff --git a/pom.xml b/pom.xml
index a826960e..886f3262 100644
--- a/pom.xml
+++ b/pom.xml
@@ -193,6 +193,8 @@
     <commons.felix.version>5.1.6</commons.felix.version>
     <spotbugs.plugin.version>4.7.0.0</spotbugs.plugin.version>
     <spotbugs.impl.version>4.7.1</spotbugs.impl.version>
+    <commons.pmd.version>3.17.0</commons.pmd.version>
+    <commons.pmd-impl.version>6.47.0</commons.pmd-impl.version>
 
     <!-- Commons Release Plugin -->
     <commons.bc.version>2.11.1</commons.bc.version>
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 4ce6857d..fc0d83c6 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -101,6 +101,9 @@ The <action> type attribute can be add,update,fix,remove.
     <action dev="ggregory" type="update" due-to="Dependabot">
       Bump maven-pmd-plugin from 3.14.0 to 3.17.0 #101, #153.
     </action>
+    <action dev="ggregory" type="update" due-to="Gary Gregory">
+      Bump pmd from 6.44.0 to 6.47.0.
+    </action>
     <action dev="ggregory" type="update" due-to="Dependabot">
       Bump biz.aQute.bndlib from 5.3.0 to 6.3.1 #105, #118, #135, #151, #154.
     </action>