/*
 * 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.pool.impl;

import java.util.ArrayList;
import java.util.BitSet;
import java.util.List;
import java.util.NoSuchElementException;

import org.apache.commons.pool.ObjectPool;
import org.apache.commons.pool.PoolableObjectFactory;
import org.apache.commons.pool.TestBaseObjectPool;

/**
 * @author Rodney Waldhoff
 * @author Dirk Verbeeck
 * @author Sandy McArthur
 * @version $Revision: 1222396 $ $Date: 2011-12-22 14:02:25 -0500 (Thu, 22 Dec 2011) $
 */
public class TestStackObjectPool extends TestBaseObjectPool<String> {
    public TestStackObjectPool(String testName) {
        super(testName);
    }

    @Override
    protected ObjectPool<String> makeEmptyPool(int mincap) {
        return new StackObjectPool<String>(new SimpleFactory());
    }

    @Override
    protected ObjectPool<Integer> makeEmptyPool(final PoolableObjectFactory<Integer> factory) {
        return new StackObjectPool<Integer>(factory);
    }

    @Override
    protected String getNthObject(int n) {
        return String.valueOf(n);
    }

    public void testIdleCap() throws Exception {
        ObjectPool<String> pool = makeEmptyPool(8);
        String[] active = new String[100];
        for(int i=0;i<100;i++) {
            active[i] = pool.borrowObject();
        }
        assertEquals(100,pool.getNumActive());
        assertEquals(0,pool.getNumIdle());
        for(int i=0;i<100;i++) {
            pool.returnObject(active[i]);
            assertEquals(99 - i,pool.getNumActive());
            assertEquals((i < 8 ? i+1 : 8),pool.getNumIdle());
        }
    }

    /**
     * @deprecated - to be removed in pool 2.0
     */
    @Deprecated
    public void testPoolWithNullFactory() throws Exception {
        ObjectPool<Integer> pool = new StackObjectPool<Integer>(10);
        for(int i=0;i<10;i++) {
            pool.returnObject(new Integer(i));
        }
        for(int j=0;j<3;j++) {
            Integer[] borrowed = new Integer[10];
            BitSet found = new BitSet();
            for(int i=0;i<10;i++) {
                borrowed[i] = pool.borrowObject();
                assertNotNull(borrowed);
                assertTrue(!found.get(borrowed[i].intValue()));
                found.set(borrowed[i].intValue());
            }
            for(int i=0;i<10;i++) {
                pool.returnObject(borrowed[i]);
            }
        }
        pool.invalidateObject(pool.borrowObject());
        pool.invalidateObject(pool.borrowObject());
        pool.clear();        
    }
    
    /**
     * @deprecated - to be removed in pool 2.0
     */
    @Deprecated
    public void testBorrowFromEmptyPoolWithNullFactory() throws Exception {
        ObjectPool<Object> pool = new StackObjectPool<Object>();
        try {
            pool.borrowObject();
            fail("Expected NoSuchElementException");
        } catch(NoSuchElementException e) {
            // expected
        }
    }
    
    /**
     * @deprecated - to be removed in pool 2.0
     */
    @Deprecated
    @Override
    public void testSetFactory() throws Exception {
        ObjectPool<String> pool = new StackObjectPool<String>();
        try {
            pool.borrowObject();
            fail("Expected NoSuchElementException");
        } catch(NoSuchElementException e) {
            // expected
        }
        pool.setFactory(new SimpleFactory());
        String obj = pool.borrowObject();
        assertNotNull(obj);
        pool.returnObject(obj);
    }

    /**
     * @deprecated - to be removed in pool 2.0
     */
    @Deprecated
    public void testCantResetFactoryWithActiveObjects() throws Exception {
        ObjectPool<String> pool = new StackObjectPool<String>();
        pool.setFactory(new SimpleFactory());
        String obj = pool.borrowObject();
        assertNotNull(obj);

        try {
            pool.setFactory(new SimpleFactory());
            fail("Expected IllegalStateException");
        } catch(IllegalStateException e) {
            // expected
        }        
    }
    
    /**
     * @deprecated - to be removed in pool 2.0
     */
    @Deprecated
    public void testCanResetFactoryWithoutActiveObjects() throws Exception {
        ObjectPool<String> pool = new StackObjectPool<String>();
        {
            pool.setFactory(new SimpleFactory());
            String obj = pool.borrowObject();        
            assertNotNull(obj);
            pool.returnObject(obj);
        }
        {
            pool.setFactory(new SimpleFactory());
            String obj = pool.borrowObject();        
            assertNotNull(obj);
            pool.returnObject(obj);
        }
    }

    /**
     * Verifies that validation failures when borrowing newly created instances
     * from the pool result in NoSuchElementExceptions and passivation failures
     * result in instances not being returned to the pool.
     */
    public void testBorrowWithSometimesInvalidObjects() throws Exception {
        SelectiveFactory factory = new SelectiveFactory();
        factory.setValidateSelectively(true);  // Even numbers fail validation
        factory.setPassivateSelectively(true); // Multiples of 3 fail passivation
        ObjectPool<Integer> pool = new StackObjectPool<Integer>(factory, 20);
        Integer[] obj = new Integer[10];
        for(int i=0;i<10;i++) {
            Integer object = null;
            int k = 0;
            while (object == null && k < 100) { // bound not really needed
                try {
                    k++;
                    object = pool.borrowObject();
                    if (object.intValue() % 2 == 0) {
                        fail("Expecting NoSuchElementException");
                    } else {
                        obj[i] = object; 
                    }
                } catch (NoSuchElementException ex) {
                    // Should fail for evens
                }
            }
            assertEquals("Each time we borrow, get one more active.", i+1, pool.getNumActive());
        }
        // 1,3,5,...,19 pass validation, get checked out
        for(int i=0;i<10;i++) {
            pool.returnObject(obj[i]);
            assertEquals("Each time we return, get one less active.", 9-i, pool.getNumActive());
        }
        // 3, 9, 15 fail passivation.  
        assertEquals(7,pool.getNumIdle());
        assertEquals(new Integer(19), pool.borrowObject());
        assertEquals(new Integer(17), pool.borrowObject());
        assertEquals(new Integer(13), pool.borrowObject());
        assertEquals(new Integer(11), pool.borrowObject());
        assertEquals(new Integer(7), pool.borrowObject());
        assertEquals(new Integer(5), pool.borrowObject());
        assertEquals(new Integer(1), pool.borrowObject());     
    }
    
    /**
     * Verifies that validation and passivation failures returning objects are handled
     * properly - instances destroyed and not returned to the pool, but no exceptions propagated.
     */
    public void testBorrowReturnWithSometimesInvalidObjects() throws Exception {
        SelectiveFactory factory = new SelectiveFactory();
        ObjectPool<Integer> pool = new StackObjectPool<Integer>(factory, 20);

        Integer[] obj = new Integer[10];
        for(int i=0;i<10;i++) {
            obj[i] = pool.borrowObject();
            assertEquals("Each time we borrow, get one more active.", i+1, pool.getNumActive());
            
        }
        
        factory.setValidateSelectively(true);  // Even numbers fail validation
        factory.setPassivateSelectively(true); // Multiples of 3 fail passivation

        for(int i=0;i<10;i++) {
            pool.returnObject(obj[i]);
            assertEquals("Each time we return, get one less active.", 9-i, pool.getNumActive());
        }
        // 0,2,4,6,8 fail validation, 3, 9 fail passivation - 3 left.
        assertEquals(3,pool.getNumIdle());
    }
     
    public void testVariousConstructors() throws Exception {
        {
            StackObjectPool<Object> pool = new StackObjectPool<Object>();
            assertNotNull(pool);
        }
        {
            StackObjectPool<Object> pool = new StackObjectPool<Object>(10);
            assertNotNull(pool);
        }
        {
            StackObjectPool<Object> pool = new StackObjectPool<Object>(10,5);
            assertNotNull(pool);
        }
        {
            StackObjectPool<Object> pool = new StackObjectPool<Object>(null);
            assertNotNull(pool);
        }
        {
            StackObjectPool<Object> pool = new StackObjectPool<Object>(null,10);
            assertNotNull(pool);
        }
        {
            StackObjectPool<Object> pool = new StackObjectPool<Object>(null,10,5);
            assertNotNull(pool);
        }
    }
    
    /**
     * Verify that out of range constructor arguments are ignored.
     */
    public void testMaxIdleInitCapacityOutOfRange() throws Exception {
        SimpleFactory factory = new SimpleFactory();
        StackObjectPool<String> pool = new StackObjectPool<String>(factory, -1, 0);
        assertEquals(pool.getMaxSleeping(), StackObjectPool.DEFAULT_MAX_SLEEPING);
        pool.addObject();
        pool.close();
    }

    /**
     * Verifies that when returning objects cause maxSleeping exceeded, oldest instances
     * are destroyed to make room for returning objects.
     */
    public void testReturnObjectDiscardOrder() throws Exception {
        SelectiveFactory factory = new SelectiveFactory();
        ObjectPool<Integer> pool = new StackObjectPool<Integer>(factory, 3);

        // borrow more objects than the pool can hold
        Integer i0 = pool.borrowObject();
        Integer i1 = pool.borrowObject();
        Integer i2 = pool.borrowObject();
        Integer i3 = pool.borrowObject();

        // tests
        // return as many as the pool will hold.
        pool.returnObject(i0);
        pool.returnObject(i1);
        pool.returnObject(i2);

        // the pool should now be full.
        assertEquals("No returned objects should have been destroyed yet.", 0,  factory.getDestroyed().size());

        // cause the pool to discard a stale object.
        pool.returnObject(i3);
        assertEquals("One object should have been destroyed.", 1, factory.getDestroyed().size());

        // check to see what object was destroyed
        Integer d = factory.getDestroyed().get(0);
        assertEquals("Destoryed object should be the stalest object.", i0, d);
    }
    
    /**
     * Verifies that exceptions thrown by factory activate method are not propagated to
     * the caller.  Objects that throw on activate are destroyed and if none succeed,
     * the caller gets NoSuchElementException.
     */
    public void testExceptionOnActivate() throws Exception {
        SelectiveFactory factory = new SelectiveFactory();
        ObjectPool<Integer> pool = new StackObjectPool<Integer>(factory);
        pool.addObject();
        pool.addObject();
        factory.setThrowOnActivate(true);
        try {
            pool.borrowObject();
            fail("Expecting NoSuchElementException");
        } catch (NoSuchElementException ex) {
            // expected
        }
        assertEquals(0, pool.getNumIdle());
        assertEquals(0, pool.getNumActive());
    }
    
    /**
     * Verifies that exceptions thrown by factory destroy are swallowed
     * by both addObject and returnObject.
     */
    public void testExceptionOnDestroy() throws Exception {
        SelectiveFactory factory = new SelectiveFactory();
        ObjectPool<Integer> pool = new StackObjectPool<Integer>(factory, 2);
        factory.setThrowOnDestroy(true);
        for (int i = 0; i < 3; i++) {
            pool.addObject(); // Third one will destroy, exception should be swallowed
        }
        assertEquals(2, pool.getNumIdle());
        
        Integer[] objects = new Integer[3];
        for (int i = 0; i < 3; i++) {
            objects[i] = pool.borrowObject();
        }
        for (int i = 0; i < 3; i++) {
            pool.returnObject(objects[i]); // Third triggers destroy
        } 
        assertEquals(2, pool.getNumIdle());
    }
    
    /**
     * Verifies that addObject propagates exceptions thrown by
     * factory passivate, but returnObject swallows these.
     */
    public void testExceptionOnPassivate() throws Exception {
        SelectiveFactory factory = new SelectiveFactory();
        ObjectPool<Integer> pool = new StackObjectPool<Integer>(factory, 2);
        factory.setThrowOnPassivate(true);
        
        // addObject propagates
        try {
            pool.addObject();
            fail("Expecting IntegerFactoryException");
        } catch (IntegerFactoryException ex) {
            assertEquals("passivateObject", ex.getType());
            assertEquals(0, ex.getValue());
        }
        assertEquals(0, pool.getNumIdle());
        
        // returnObject swallows 
        Integer obj = pool.borrowObject();
        pool.returnObject(obj);
        assertEquals(0, pool.getNumIdle());
    }
    
    /**
     * Verifies that validation exceptions always propagate
     */
    public void testExceptionOnValidate() throws Exception {
        SelectiveFactory factory = new SelectiveFactory();
        ObjectPool<Integer> pool = new StackObjectPool<Integer>(factory, 2);
        factory.setThrowOnValidate(true);
        
        // addObject
        try {
            pool.addObject();
            fail("Expecting IntegerFactoryException");
        } catch (IntegerFactoryException ex) {
            assertEquals("validateObject", ex.getType());
        }
        assertEquals(0, pool.getNumIdle());
        
        // returnObject 
        factory.setThrowOnValidate(false);
        Integer obj = pool.borrowObject();
        factory.setThrowOnValidate(true);
        try {
            pool.returnObject(obj);
            fail("Expecting IntegerFactoryException");
        } catch (IntegerFactoryException ex) {
            assertEquals("validateObject", ex.getType());
        }
        assertEquals(0, pool.getNumIdle());
        
        // borrowObject - throws NoSuchElementException
        try {
            pool.borrowObject();
            fail("Expecting NoSuchElementException");
        } catch (NoSuchElementException ex) {
            // Expected
        }
    }
    
    /**
     * Verifies that exceptions thrown by makeObject are propagated.
     */
    public void testExceptionOnMake() throws Exception {
        SelectiveFactory factory = new SelectiveFactory();
        factory.setThrowOnMake(true);
        ObjectPool<Integer> pool = new StackObjectPool<Integer>(factory);
        try {
            pool.borrowObject();
            fail("Expecting IntegerFactoryException");
        } catch (IntegerFactoryException ex) {
            assertEquals("makeObject", ex.getType());
        }
        try {
            pool.addObject();
            fail("Expecting IntegerFactoryException");
        } catch (IntegerFactoryException ex) {
            assertEquals("makeObject", ex.getType());
        }
    }
    
    /**
     * Verifies NoSuchElementException when the factory returns a null object in borrowObject
     */
    public void testMakeNull() throws Exception {
        SelectiveFactory factory = new SelectiveFactory();
        ObjectPool<Integer> pool = new StackObjectPool<Integer>(factory);
        factory.setMakeNull(true);
        try {
            pool.borrowObject();
            fail("Expecting NoSuchElementException");
        } catch (NoSuchElementException ex) {
            // Expected
        }
    }
    
    /**
     * Verifies that initIdleCapacity is not a hard limit, but maxIdle is.
     */
    public void testInitIdleCapacityExceeded() throws Exception {
        PoolableObjectFactory<String> factory = new SimpleFactory();
        ObjectPool<String> pool = new StackObjectPool<String>(factory, 2, 1);
        pool.addObject();
        pool.addObject();
        assertEquals(2, pool.getNumIdle());
        pool.close();
        pool = new StackObjectPool<String>(factory, 1, 2);
        pool.addObject();
        pool.addObject();
        assertEquals(1, pool.getNumIdle());
    }
    
    /**
     * Verifies close contract - idle instances are destroyed, returning instances
     * are destroyed, add/borrowObject throw IllegalStateException.
     */
    @Override
    public void testClose() throws Exception {
        SelectiveFactory factory = new SelectiveFactory();
        ObjectPool<Integer> pool = new StackObjectPool<Integer>(factory);
        pool.addObject(); // 0
        pool.addObject(); // 1
        pool.addObject(); // 2
        Integer two = pool.borrowObject();
        assertEquals(2, two.intValue());
        pool.close();
        assertEquals(0, pool.getNumIdle());
        assertEquals(1, pool.getNumActive());
        List<Integer> destroyed = factory.getDestroyed();
        assertEquals(2, destroyed.size());
        assertTrue(destroyed.contains(new Integer(0)));
        assertTrue(destroyed.contains(new Integer(0)));
        pool.returnObject(two);
        assertTrue(destroyed.contains(two));
        try {
            pool.addObject();
            fail("Expecting IllegalStateException");
        } catch (IllegalStateException ex) {
            // Expected
        }
        try {
            pool.borrowObject();
            fail("Expecting IllegalStateException");
        } catch (IllegalStateException ex) {
            // Expected
        }
    }

    /**
     * Simple factory that creates Integers. Validation and other factory methods
     * always succeed.
     */
    static class SimpleFactory implements PoolableObjectFactory<String> {
        int counter = 0;
        public String makeObject() { return String.valueOf(counter++); }
        public void destroyObject(String obj) { }
        public boolean validateObject(String obj) { return true; }
        public void activateObject(String obj) { }
        public void passivateObject(String obj) { }
    }
    
    /**
     * Integer factory that fails validation and other factory methods "selectively" and
     * tracks object destruction.
     */
    static class SelectiveFactory implements PoolableObjectFactory<Integer> {
        private List<Integer> destroyed = new ArrayList<Integer>();
        private int counter = 0;
        private boolean validateSelectively = false;  // true <-> validate returns false for even Integers
        private boolean passivateSelectively = false; // true <-> passivate throws RTE if Integer = 0 mod 3
        private boolean throwOnDestroy = false;       // true <-> destroy throws RTE (always)
        private boolean throwOnActivate = false;      // true <-> activate throws RTE (always)
        private boolean throwOnMake = false;          // true <-> make throws RTE (always)
        private boolean throwOnValidate= false;       // true <-> validate throws RTE (always)
        private boolean throwOnPassivate = false;     // true <-> passivate throws RTE (always)
        private boolean makeNull = false;             // true <-> make returns null
        public Integer makeObject() {
            if (throwOnMake) {
                final int next = counter + 1;
                throw new IntegerFactoryException("makeObject", next);
            } else {
                return makeNull? null : new Integer(counter++);
            }
        }
        public void destroyObject(Integer obj) {
            if (throwOnDestroy) {
                final Integer integer = obj;
                throw new IntegerFactoryException("destroyObject", integer.intValue());
            }
            destroyed.add(obj);
        }
        public boolean validateObject(Integer obj) {
            if (throwOnValidate) {
                final Integer integer = obj;
                throw new IntegerFactoryException("validateObject", integer.intValue());
            }
            if (validateSelectively) {
                // only odd objects are valid
                return ((obj.intValue() % 2) == 1);
            }
            return true;
        }
        public void activateObject(Integer obj) {
            if (throwOnActivate) {
                final Integer integer = obj;
                throw new IntegerFactoryException("activateObject", integer.intValue());
            }
        }
        public void passivateObject(Integer obj) { 
            if (throwOnPassivate) {
                final Integer integer = obj;
                throw new IntegerFactoryException("passivateObject", integer.intValue());
            }
            if (passivateSelectively) {
                final Integer integer = obj;
                if (integer.intValue() % 3 == 0) {
                    throw new IntegerFactoryException("passivateObject", integer.intValue());
                }
            }
        }
        public List<Integer> getDestroyed() {
            return destroyed;
        }
        public void setCounter(int counter) {
            this.counter = counter;
        }
        public void setValidateSelectively(boolean validateSelectively) {
            this.validateSelectively = validateSelectively;
        }
        public void setPassivateSelectively(boolean passivateSelectively) {
            this.passivateSelectively = passivateSelectively;
        }
        public void setThrowOnDestroy(boolean throwOnDestroy) {
            this.throwOnDestroy = throwOnDestroy;
        }
        public void setThrowOnActivate(boolean throwOnActivate) {
            this.throwOnActivate = throwOnActivate;
        }
        public void setThrowOnMake(boolean throwOnMake) {
            this.throwOnMake = throwOnMake;
        }
        public void setThrowOnPassivate(boolean throwOnPassivate) {
            this.throwOnPassivate = throwOnPassivate;
        }
        public void setThrowOnValidate(boolean throwOnValidate) {
            this.throwOnValidate = throwOnValidate;
        }
        public void setMakeNull(boolean makeNull) {
            this.makeNull = makeNull;
        }
    }
    
    static class IntegerFactoryException extends RuntimeException {
        private String type;
        private int value;
        public IntegerFactoryException(String type, int value) {
            super(type + " failed. Value: " + value);
            this.type = type;
            this.value = value;
        }
        public String getType() {
            return type;
        }
        public int getValue() {
            return value;
        }
    }

    @Override
    protected boolean isLifo() {
        return true;
    }

    @Override
    protected boolean isFifo() {
        return false;
    }
}

