/* $Id: DigesterTestCase.java 728879 2008-12-23 05:44:07Z rahul $
 *
 * 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.digester;

import java.io.File;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.math.BigDecimal;
import java.net.URL;
import java.util.ArrayList;
import java.util.EmptyStackException;
import java.util.Iterator;
import java.util.Map;

import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;

import org.xml.sax.Attributes;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.helpers.AttributesImpl;


/**
 * <p>Test Case for the Digester class.  These tests exercise the individual
 * methods of a Digester, but do not attempt to process complete documents.
 * </p>
 *
 * @author Craig R. McClanahan
 * @version $Revision: 728879 $ $Date: 2008-12-23 00:44:07 -0500 (Tue, 23 Dec 2008) $
 */

public class DigesterTestCase extends TestCase {


    // ----------------------------------------------------- Instance Variables


    /**
     * The digester instance we will be processing.
     */
    protected Digester digester = null;


    /**
     * The set of public identifiers, and corresponding resource names,
     * for the versions of the DTDs that we know about.  There
     * <strong>MUST</strong> be an even number of Strings in this array.
     */
    protected static final String registrations[] = {
        "-//Netscape Communications//DTD RSS 0.9//EN",
        "/org/apache/commons/digester/rss/rss-0.9.dtd",
        "-//Netscape Communications//DTD RSS 0.91//EN",
        "/org/apache/commons/digester/rss/rss-0.91.dtd",
    };


    // ----------------------------------------------------------- Constructors


    /**
     * Construct a new instance of this test case.
     *
     * @param name Name of the test case
     */
    public DigesterTestCase(String name) {

        super(name);

    }


    // -------------------------------------------------- Overall Test Methods


    /**
     * Set up instance variables required by this test case.
     */
    public void setUp() {

        digester = new Digester();
        digester.setRules(new RulesBase());

    }


    /**
     * Return the tests included in this test suite.
     */
    public static Test suite() {

        return (new TestSuite(DigesterTestCase.class));

    }


    /**
     * Tear down instance variables required by this test case.
     */
    public void tearDown() {

        digester = null;

    }



    // ------------------------------------------------ Individual Test Methods


    /**
     * Test <code>null</code> parsing.
     * (should lead to <code>IllegalArgumentException</code>s)
     */
    public void testNullFileParse() throws Exception {

        try {
            digester.parse((File) null);
            fail("Expected IllegalArgumentException with null argument");
        } catch (IllegalArgumentException e) {
            // expected
        }

    }

    public void testNullInputSourceParse() throws Exception {

        try {
            digester.parse((InputSource) null);
            fail("Expected IllegalArgumentException with null argument");
        } catch (IllegalArgumentException e) {
            // expected
        }

    }

    public void testNullInputStreamParse() throws Exception {

        try {
            digester.parse((InputStream) null);
            fail("Expected IllegalArgumentException with null argument");
        } catch (IllegalArgumentException e) {
            // expected
        }

    }

    public void testNullReaderParse() throws Exception {

        try {
            digester.parse((Reader) null);
            fail("Expected IllegalArgumentException with null argument");
        } catch (IllegalArgumentException e) {
            // expected
        }

    }

    public void testNullStringParse() throws Exception {

        try {
            digester.parse((String) null);
            fail("Expected IllegalArgumentException with null argument");
        } catch (IllegalArgumentException e) {
            // expected
        }

    }

    public void testNullURLParse() throws Exception {

        try {
            digester.parse((URL) null);
            fail("Expected IllegalArgumentException with null argument");
        } catch (IllegalArgumentException e) {
            // expected
        }

    }


    /**
     * Test the basic property getters and setters.
     */
    public void testProperties() {

        assertNull("Initial error handler is null",
                digester.getErrorHandler());
        digester.setErrorHandler((ErrorHandler) digester);
        assertTrue("Set error handler is digester",
                digester.getErrorHandler() == digester);
        digester.setErrorHandler(null);
        assertNull("Reset error handler is null",
                digester.getErrorHandler());

        assertTrue("Initial namespace aware is false",
                !digester.getNamespaceAware());
        digester.setNamespaceAware(true);
        assertTrue("Set namespace aware is true",
                digester.getNamespaceAware());
        digester.setNamespaceAware(false);
        assertTrue("Reset namespace aware is false",
                !digester.getNamespaceAware());

        assertTrue("Initial validating is false",
                !digester.getValidating());
        digester.setValidating(true);
        assertTrue("Set validating is true",
                digester.getValidating());
        digester.setValidating(false);
        assertTrue("Reset validating is false",
                !digester.getValidating());

    }


    /**
     * Test registration of URLs for specified public identifiers.
     */
    public void testRegistrations() {

        Map map = digester.getRegistrations();
        assertEquals("Initially zero registrations", 0, map.size());
        int n = 0;
        for (int i = 0; i < registrations.length; i += 2) {
            URL url = this.getClass().getResource(registrations[i + 1]);
            if (url != null) {
                digester.register(registrations[i], url);
                n++;
            }
        }
        map = digester.getRegistrations();
        assertEquals("Registered two URLs", n, map.size());

        int count[] = new int[n];
        for (int i = 0; i < n; i++)
            count[i] = 0;
        Iterator keys = map.keySet().iterator();
        while (keys.hasNext()) {
            String key = (String) keys.next();
            for (int i = 0; i < n; i++) {
                if (key.equals(registrations[i * 2])) {
                    count[i]++;
                    break;
                }
            }
        }
        for (int i = 0; i < n; i++)
            assertEquals("Count for key " + registrations[i * 2],
                    1, count[i]);

    }


    /**
     * Basic test for rule creation and matching.
     */
    public void testRules() {

        assertEquals("Initial rules list is empty",
                0, digester.getRules().match(null, "a").size());
        digester.addSetProperties("a");
        assertEquals("Add a matching rule",
                1, digester.getRules().match(null, "a").size());
        digester.addSetProperties("b");
        assertEquals("Add a non-matching rule",
                1, digester.getRules().match(null, "a").size());
        digester.addSetProperties("a/b");
        assertEquals("Add a non-matching nested rule",
                1, digester.getRules().match(null, "a").size());
        digester.addSetProperties("a/b");
        assertEquals("Add a second matching rule",
                2, digester.getRules().match(null, "a/b").size());

    }


    /**
     * <p>Test matching rules in {@link RulesBase}.</p>
     *
     * <p>Tests:</p>
     * <ul>
     * <li>exact match</li>
     * <li>tail match</li>
     * <li>longest pattern rule</li>
     * </ul>
     */
    public void testRulesBase() {

        assertEquals("Initial rules list is empty",
                0, digester.getRules().rules().size());

        // We're going to set up
        digester.addRule("a/b/c/d", new TestRule("a/b/c/d"));
        digester.addRule("*/d", new TestRule("*/d"));
        digester.addRule("*/c/d", new TestRule("*/c/d"));

        // Test exact match
        assertEquals("Exact match takes precedence 1",
                1, digester.getRules().match(null, "a/b/c/d").size());
        assertEquals("Exact match takes precedence 2",
                "a/b/c/d",
                ((TestRule) digester.getRules().match(null, "a/b/c/d").iterator().next()).getIdentifier());

        // Test wildcard tail matching
        assertEquals("Wildcard tail matching rule 1",
                1, digester.getRules().match(null, "a/b/d").size());
        assertEquals("Wildcard tail matching rule 2",
                "*/d",
                ((TestRule) digester.getRules().match(null, "a/b/d").iterator().next()).getIdentifier());

        // Test the longest matching pattern rule
        assertEquals("Longest tail rule 1",
                1, digester.getRules().match(null, "x/c/d").size());
        assertEquals("Longest tail rule 2",
                "*/c/d",
                ((TestRule) digester.getRules().match(null, "x/c/d").iterator().next()).getIdentifier());

    }


    /**
     * Test the basic stack mechanisms.
     */
    public void testStackMethods() {

        Object value = null;

        // New stack must be empty
        assertEquals("New stack is empty", 0, digester.getCount());
        value = digester.peek();
        assertNull("New stack peek() returns null", value);
        value = digester.pop();
        assertNull("New stack pop() returns null", value);

        // Test pushing and popping activities
        digester.push("First Item");
        assertEquals("Pushed one item size", 1, digester.getCount());
        value = digester.peek();
        assertNotNull("Peeked first item is not null", value);
        assertEquals("Peeked first item value", "First Item", (String) value);

        digester.push("Second Item");
        assertEquals("Pushed two items size", 2, digester.getCount());
        value = digester.peek();
        assertNotNull("Peeked second item is not null", value);
        assertEquals("Peeked second item value", "Second Item", (String) value);

        value = digester.pop();
        assertEquals("Popped stack size", 1, digester.getCount());
        assertNotNull("Popped second item is not null", value);
        assertEquals("Popped second item value", "Second Item", (String) value);
        value = digester.peek();
        assertNotNull("Remaining item is not null", value);
        assertEquals("Remaining item value", "First Item", (String) value);
        assertEquals("Remaining stack size", 1, digester.getCount());

        // Cleared stack is empty
        digester.push("Dummy Item");
        digester.clear();
        assertEquals("Cleared stack is empty", 0, digester.getCount());
        value = digester.peek();
        assertNull("Cleared stack peek() returns null", value);
        value = digester.pop();
        assertNull("Cleared stack pop() returns null", value);

    }

    public void testOnceAndOnceOnly() throws Exception {
        
        class TestConfigureDigester extends Digester {
            public int called=0;
            public TestConfigureDigester() {}
            
            protected void initialize() {
                called++;
            }
        }
        
        TestConfigureDigester digester = new TestConfigureDigester();
        
        String xml = "<?xml version='1.0'?><document/>";
        digester.parse(new StringReader(xml));
        
        assertEquals("Initialize should be called once and only once", 1, digester.called);
    }
    
    public void testBasicSubstitution() throws Exception {
        class TestSubRule extends Rule {
            public String body;
            public Attributes attributes;
            
            public void begin(String namespace, String name, Attributes attributes) {
                this.attributes = new AttributesImpl(attributes);
            }
            
            public void body(String namespace, String name, String text) {
                this.body = text;
            }
        }
        
        TestSubRule tsr = new TestSubRule();
        Digester digester = new Digester();
        digester.addRule("alpha/beta", tsr);
            
        // it's not easy to transform dirty harry into the mighty circus - but let's give it a try
        String xml = "<?xml version='1.0'?><alpha><beta forname='Dirty' surname='Harry'>Do you feel luck punk?</beta></alpha>";
        InputSource in = new InputSource(new StringReader(xml));
        
        digester.parse(in);
        
        assertEquals("Unsubstituted body text", "Do you feel luck punk?", tsr.body);
        assertEquals("Unsubstituted number of attributes", 2, tsr.attributes.getLength());
        assertEquals("Unsubstituted forname attribute value", "Dirty", tsr.attributes.getValue("forname"));
        assertEquals("Unsubstituted surname attribute value", "Harry", tsr.attributes.getValue("surname"));

        digester.setSubstitutor(
            new Substitutor() {
                public Attributes substitute(Attributes attributes) {
                    AttributesImpl results = new AttributesImpl();
                    results.addAttribute("", "python", "python", "CDATA", "Cleese");
                    return results;
                }   
                
                public String substitute(String bodyText) {
                    return "And now for something completely different...";
                }
            });
        
        // now transform into the full monty
        in = new InputSource(new StringReader(xml));
        digester.parse(in);
        
        assertEquals("Substituted body text", "And now for something completely different...", tsr.body);
        assertEquals("Substituted number of attributes", 1, tsr.attributes.getLength());
        assertEquals("Substituted python attribute value", "Cleese", tsr.attributes.getValue("", "python"));
    }
    
    /** Tests the push-peek-pop cycle for a named stack */
    public void testNamedStackPushPeekPop() throws Exception
    {
        BigDecimal archimedesAveragePi = new BigDecimal("3.1418");
        String testStackName = "org.apache.commons.digester.tests.testNamedStackPushPeekPop";
        Digester digester = new Digester();
        assertTrue("Stack starts empty:", digester.isEmpty(testStackName));
        digester.push(testStackName, archimedesAveragePi);
        assertEquals("Peeked value:", archimedesAveragePi, digester.peek(testStackName));
        assertEquals("Popped value:", archimedesAveragePi, digester.pop(testStackName));
        assertTrue("Stack ends empty:", digester.isEmpty(testStackName));
        
        digester.push(testStackName, "1");
        digester.push(testStackName, "2");
        digester.push(testStackName, "3");
        
        assertEquals("Peek#1", "1", digester.peek(testStackName, 2));
        assertEquals("Peek#2", "2", digester.peek(testStackName, 1));
        assertEquals("Peek#3", "3", digester.peek(testStackName, 0));
        assertEquals("Peek#3a", "3", digester.peek(testStackName));
        
        try {
            // peek beyond stack
            digester.peek(testStackName, 3);
            fail("Peek#4 failed to throw an exception.");
        } catch(EmptyStackException ex) {
            // ok, expected
        }
        
        try {
            // peek a nonexistent named stack
            digester.peek("no.such.stack", 0);
            fail("Peeking a non-existent stack failed to throw an exception.");
        } catch(EmptyStackException ex) {
            // ok, expected
        }
    }
    
    /** Tests that values are stored independently */
    public void testNamedIndependence()
    {
        String testStackOneName = "org.apache.commons.digester.tests.testNamedIndependenceOne";
        String testStackTwoName = "org.apache.commons.digester.tests.testNamedIndependenceTwo";
        Digester digester = new Digester();
        digester.push(testStackOneName, "Tweedledum");
        digester.push(testStackTwoName, "Tweedledee");
        assertEquals("Popped value one:", "Tweedledum", digester.pop(testStackOneName));
        assertEquals("Popped value two:", "Tweedledee", digester.pop(testStackTwoName));
    }
    
    /** Tests popping named stack not yet pushed */
    public void testPopNamedStackNotPushed() 
    {
        String testStackName = "org.apache.commons.digester.tests.testPopNamedStackNotPushed";
        Digester digester = new Digester();
        try {
        
            digester.pop(testStackName);
            fail("Expected an EmptyStackException");
            
        } catch (EmptyStackException e) {
            // expected
        }
        
        try {
        
            digester.peek(testStackName);
            fail("Expected an EmptyStackException");
            
        } catch (EmptyStackException e) {
            // expected
        }
    }
    
    /** Tests for isEmpty */
    public void testNamedStackIsEmpty()
    {
        String testStackName = "org.apache.commons.digester.tests.testNamedStackIsEmpty";
        Digester digester = new Digester();
        assertTrue(
            "A named stack that has no object pushed onto it yet should be empty", 
            digester.isEmpty(testStackName));
            
        digester.push(testStackName, "Some test value");
        assertFalse(
            "A named stack that has an object pushed onto it should be not empty",
            digester.isEmpty(testStackName));
            
        digester.peek(testStackName);
        assertFalse(
            "Peek should not effect whether the stack is empty",
            digester.isEmpty(testStackName));
        
        digester.pop(testStackName);
        assertTrue(
            "A named stack that has it's last object popped is empty", 
            digester.isEmpty(testStackName));
    }

    /**
     * Test the Digester.getRoot method.
     */
    public void testGetRoot() throws Exception {
        Digester digester = new Digester();
        digester.addRule("root", new ObjectCreateRule(TestBean.class));
            
        String xml = "<root/>";
        InputSource in = new InputSource(new StringReader(xml));
        
        digester.parse(in);
        
        Object root = digester.getRoot();
        assertNotNull("root object not retrieved", root);
        assertTrue("root object not a TestRule instance", (root instanceof TestBean));
    }
    
    /** Utility class for method testStackAction */
    private static class TrackingStackAction implements StackAction {
    	public ArrayList events = new ArrayList();
    	public Object onPush(Digester d, String stackName, Object o) {
    		String msg = "push:" + stackName + ":" + o.toString();
    		events.add(msg);
    		
    		String str = o.toString();
    		if (str.startsWith("replpush")) {
    			return new String(str);
    		} else {
    			return o;
    		}
    	}
    	public Object onPop(Digester d, String stackName, Object o) {
    		String msg = "pop:" + stackName + ":" + o.toString();
    		events.add(msg);
    		String str = o.toString();
    		if (str.startsWith("replpop")) {
    			return new String(str);
    		} else {
    			return o;
    		}
    	}
    }

    /**
     * Test custom StackAction subclasses.
     */
    public void testStackAction() {
    	TrackingStackAction action = new TrackingStackAction();
    	
    	Object obj1 = new String("obj1");
    	Object obj2 = new String("obj2");
    	Object obj3 = new String("replpop.obj3");
    	Object obj4 = new String("replpush.obj4");

    	Object obj8 = new String("obj8");
    	Object obj9 = new String("obj9");

    	Digester d = new Digester();
    	d.setStackAction(action);

    	assertEquals(0, action.events.size());
    	d.push(obj1);
    	d.push(obj2);
    	d.push(obj3);
    	d.push(obj4);

    	assertNotNull(d.peek(0));
    	// for obj4, a copy should have been pushed
    	assertFalse(obj4 == d.peek(0));
    	assertEquals(obj4, d.peek(0));
    	// for obj3, replacement only occurs on pop
    	assertSame(obj3, d.peek(1));
    	assertSame(obj2, d.peek(2));
    	assertSame(obj1, d.peek(3));

    	Object obj4a = d.pop();
    	Object obj3a = d.pop();
    	Object obj2a = d.pop();
    	Object obj1a = d.pop();
    	
    	assertFalse(obj4 == obj4a);
    	assertEquals(obj4, obj4a);
    	assertFalse(obj3 == obj4a);
    	assertEquals(obj3, obj3a);
    	assertSame(obj2, obj2a);
    	assertSame(obj1, obj1a);

    	d.push("stack1", obj8);
    	d.push("stack1", obj9);
    	Object obj9a = d.pop("stack1");
    	Object obj8a = d.pop("stack1");

    	assertSame(obj8, obj8a);
    	assertSame(obj9, obj9a);

    	assertEquals(12, action.events.size());
    	assertEquals("push:null:obj1", action.events.get(0));
    	assertEquals("push:null:obj2", action.events.get(1));
    	assertEquals("push:null:replpop.obj3", action.events.get(2));
    	assertEquals("push:null:replpush.obj4", action.events.get(3));
    	assertEquals("pop:null:replpush.obj4", action.events.get(4));
    	assertEquals("pop:null:replpop.obj3", action.events.get(5));
    	assertEquals("pop:null:obj2", action.events.get(6));
    	assertEquals("pop:null:obj1", action.events.get(7));

    	assertEquals("push:stack1:obj8", action.events.get(8));
    	assertEquals("push:stack1:obj9", action.events.get(9));
    	assertEquals("pop:stack1:obj9", action.events.get(10));
    	assertEquals("pop:stack1:obj8", action.events.get(11));
    }
}
