/**
   * {@inheritDoc}
   *
   * <p>Successive activations of this method examine objects in sequence, cycling through objects
   * in oldest-to-youngest order.
   */
  @Override
  public void evict() throws Exception {
    assertOpen();

    if (idleObjects.size() > 0) {

      PooledObject<T> underTest = null;
      EvictionPolicy<T> evictionPolicy = getEvictionPolicy();

      synchronized (evictionLock) {
        EvictionConfig evictionConfig =
            new EvictionConfig(
                getMinEvictableIdleTimeMillis(), getSoftMinEvictableIdleTimeMillis(), getMinIdle());

        boolean testWhileIdle = getTestWhileIdle();

        for (int i = 0, m = getNumTests(); i < m; i++) {
          if (evictionIterator == null || !evictionIterator.hasNext()) {
            evictionIterator = new EvictionIterator(idleObjects);
          }
          if (!evictionIterator.hasNext()) {
            // Pool exhausted, nothing to do here
            return;
          }

          try {
            underTest = evictionIterator.next();
          } catch (NoSuchElementException nsee) {
            // Object was borrowed in another thread
            // Don't count this as an eviction test so reduce i;
            i--;
            evictionIterator = null;
            continue;
          }

          if (!underTest.startEvictionTest()) {
            // Object was borrowed in another thread
            // Don't count this as an eviction test so reduce i;
            i--;
            continue;
          }

          // User provided eviction policy could throw all sorts of crazy
          // exceptions. Protect against such an exception killing the
          // eviction thread.
          boolean evict;
          try {
            evict = evictionPolicy.evict(evictionConfig, underTest, idleObjects.size());
          } catch (Throwable t) {
            // Slightly convoluted as SwallowedExceptionListener uses
            // Exception rather than Throwable
            PoolUtils.checkRethrow(t);
            swallowException(new Exception(t));
            // Don't evict on error conditions
            evict = false;
          }

          if (evict) {
            destroy(underTest);
            destroyedByEvictorCount.incrementAndGet();
          } else {
            if (testWhileIdle) {
              boolean active = false;
              try {
                factory.activateObject(underTest);
                active = true;
              } catch (Exception e) {
                destroy(underTest);
                destroyedByEvictorCount.incrementAndGet();
              }
              if (active) {
                if (!factory.validateObject(underTest)) {
                  destroy(underTest);
                  destroyedByEvictorCount.incrementAndGet();
                } else {
                  try {
                    factory.passivateObject(underTest);
                  } catch (Exception e) {
                    destroy(underTest);
                    destroyedByEvictorCount.incrementAndGet();
                  }
                }
              }
            }
            if (!underTest.endEvictionTest(idleObjects)) {
              // TODO - May need to add code here once additional
              // states are used
            }
          }
        }
      }
    }
    AbandonedConfig ac = this.abandonedConfig;
    if (ac != null && ac.getRemoveAbandonedOnMaintenance()) {
      removeAbandoned(ac);
    }
  }
  /**
   * Borrow an object from the pool using the specific waiting time which only applies if {@link
   * #getBlockWhenExhausted()} is true.
   *
   * <p>If there is one or more idle instance available in the pool, then an idle instance will be
   * selected based on the value of {@link #getLifo()}, activated and returned. If activation fails,
   * or {@link #getTestOnBorrow() testOnBorrow} is set to <code>true</code> and validation fails,
   * the instance is destroyed and the next available instance is examined. This continues until
   * either a valid instance is returned or there are no more idle instances available.
   *
   * <p>If there are no idle instances available in the pool, behavior depends on the {@link
   * #getMaxTotal() maxTotal}, (if applicable) {@link #getBlockWhenExhausted()} and the value passed
   * in to the <code>borrowMaxWaitMillis</code> parameter. If the number of instances checked out
   * from the pool is less than <code>maxTotal,</code> a new instance is created, activated and (if
   * applicable) validated and returned to the caller. If validation fails, a <code>
   * NoSuchElementException</code> is thrown.
   *
   * <p>If the pool is exhausted (no available idle instances and no capacity to create new ones),
   * this method will either block (if {@link #getBlockWhenExhausted()} is true) or throw a <code>
   * NoSuchElementException</code> (if {@link #getBlockWhenExhausted()} is false). The length of
   * time that this method will block when {@link #getBlockWhenExhausted()} is true is determined by
   * the value passed in to the <code>borrowMaxWaitMillis</code> parameter.
   *
   * <p>When the pool is exhausted, multiple calling threads may be simultaneously blocked waiting
   * for instances to become available. A "fairness" algorithm has been implemented to ensure that
   * threads receive available instances in request arrival order.
   *
   * @param borrowMaxWaitMillis The time to wait in milliseconds for an object to become available
   * @return object instance from the pool
   * @throws NoSuchElementException if an instance cannot be returned
   * @throws Exception if an object instance cannot be returned due to an error
   */
  public T borrowObject(long borrowMaxWaitMillis) throws Exception {
    assertOpen();

    AbandonedConfig ac = this.abandonedConfig;
    if (ac != null
        && ac.getRemoveAbandonedOnBorrow()
        && (getNumIdle() < 2)
        && (getNumActive() > getMaxTotal() - 3)) {
      removeAbandoned(ac);
    }

    PooledObject<T> p = null;

    // Get local copy of current config so it is consistent for entire
    // method execution
    boolean blockWhenExhausted = getBlockWhenExhausted();

    boolean create;
    long waitTime = System.currentTimeMillis();

    while (p == null) {
      create = false;
      if (blockWhenExhausted) {
        p = idleObjects.pollFirst();
        if (p == null) {
          p = create();
          if (p != null) {
            create = true;
          }
        }
        if (p == null) {
          if (borrowMaxWaitMillis < 0) {
            p = idleObjects.takeFirst();
          } else {
            p = idleObjects.pollFirst(borrowMaxWaitMillis, TimeUnit.MILLISECONDS);
          }
        }
        if (p == null) {
          throw new NoSuchElementException("Timeout waiting for idle object");
        }
        if (!p.allocate()) {
          p = null;
        }
      } else {
        p = idleObjects.pollFirst();
        if (p == null) {
          p = create();
          if (p != null) {
            create = true;
          }
        }
        if (p == null) {
          throw new NoSuchElementException("Pool exhausted");
        }
        if (!p.allocate()) {
          p = null;
        }
      }

      if (p != null) {
        try {
          factory.activateObject(p);
        } catch (Exception e) {
          try {
            destroy(p);
          } catch (Exception e1) {
            // Ignore - activation failure is more important
          }
          p = null;
          if (create) {
            NoSuchElementException nsee = new NoSuchElementException("Unable to activate object");
            nsee.initCause(e);
            throw nsee;
          }
        }
        if (p != null && (getTestOnBorrow() || create && getTestOnCreate())) {
          boolean validate = false;
          Throwable validationThrowable = null;
          try {
            validate = factory.validateObject(p);
          } catch (Throwable t) {
            PoolUtils.checkRethrow(t);
            validationThrowable = t;
          }
          if (!validate) {
            try {
              destroy(p);
              destroyedByBorrowValidationCount.incrementAndGet();
            } catch (Exception e) {
              // Ignore - validation failure is more important
            }
            p = null;
            if (create) {
              NoSuchElementException nsee = new NoSuchElementException("Unable to validate object");
              nsee.initCause(validationThrowable);
              throw nsee;
            }
          }
        }
      }
    }

    updateStatsBorrow(p, System.currentTimeMillis() - waitTime);

    return p.getObject();
  }