/* $Id: RuleTestCase.java 730332 2008-12-31 07:09:35Z 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.IOException;
import java.io.InputStream;
import java.util.ArrayList;

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

import org.xml.sax.SAXException;

/**
 * <p>Test Case for the Digester class.  These tests perform parsing of
 * XML documents to exercise the built-in rules.</p>
 *
 * @author Craig R. McClanahan
 * @author Janek Bogucki
 * @version $Revision: 730332 $ $Date: 2008-12-31 02:09:35 -0500 (Wed, 31 Dec 2008) $
 */

public class RuleTestCase extends TestCase {


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


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


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


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

        super(name);

    }


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


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

        digester = new Digester();

    }


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

        return (new TestSuite(RuleTestCase.class));

    }


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

        digester = null;

    }



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


    /**
     * Test object creation (and associated property setting) with nothing on
     * the stack, which should cause an appropriate Employee object to be
     * returned.
     */
    public void testObjectCreate1() throws SAXException, IOException {

        // Configure the digester as required
        digester.addObjectCreate("employee",
                "org.apache.commons.digester.Employee");
        digester.addSetProperties("employee");

        // Parse our test input.
        Object root = null;
        root = digester.parse(getInputStream("Test1.xml"));

        assertNotNull("Digester returned an object", root);
        assertTrue("Digester returned an Employee",
                root instanceof Employee);
        Employee employee = (Employee) root;
        assertEquals("First name is correct",
                "First Name",
                employee.getFirstName());
        assertEquals("Last name is correct",
                "Last Name",
                employee.getLastName());


    }


    /**
     * Test object creation (and associated property setting) with nothing on
     * the stack, which should cause an appropriate Employee object to be
     * returned.  The processing rules will process the nested Address elements
     * as well, but will not attempt to add them to the Employee.
     */
    public void testObjectCreate2() throws SAXException, IOException {

        // Configure the digester as required
        digester.addObjectCreate("employee", Employee.class);
        digester.addSetProperties("employee");
        digester.addObjectCreate("employee/address",
                "org.apache.commons.digester.Address");
        digester.addSetProperties("employee/address");

        // Parse our test input.
        Object root = null;
        root = digester.parse(getInputStream("Test1.xml"));

        assertNotNull("Digester returned an object", root);
        assertTrue("Digester returned an Employee",
                root instanceof Employee);
        Employee employee = (Employee) root;
        assertEquals("First name is correct",
                "First Name",
                employee.getFirstName());
        assertEquals("Last name is correct",
                "Last Name",
                employee.getLastName());


    }


    /**
     * Test object creation (and associated property setting) with nothing on
     * the stack, which should cause an appropriate Employee object to be
     * returned.  The processing rules will process the nested Address elements
     * as well, and will add them to the owning Employee.
     */
    public void testObjectCreate3() throws SAXException, IOException {

        // Configure the digester as required
        digester.addObjectCreate("employee", Employee.class);
        digester.addSetProperties("employee");
        digester.addObjectCreate("employee/address",
                "org.apache.commons.digester.Address");
        digester.addSetProperties("employee/address");
        digester.addSetNext("employee/address",
                "addAddress");

        // Parse our test input once
        Object root = null;
        root = digester.parse(getInputStream("Test1.xml"));

        validateObjectCreate3(root);

        // Parse the same input again
        try {
            root = digester.parse(getInputStream("Test1.xml"));
        } catch (Throwable t) {
            fail("Digester threw IOException: " + t);
        }
        validateObjectCreate3(root);

    }


    /**
     * Same as testObjectCreate1(), except use individual call method rules
     * to set the properties of the Employee.
     */
    public void testObjectCreate4() throws SAXException, IOException {

        // Configure the digester as required
        digester.addObjectCreate("employee", Employee.class);
        digester.addCallMethod("employee",
                "setFirstName", 1);
        digester.addCallParam("employee", 0, "firstName");
        digester.addCallMethod("employee",
                "setLastName", 1);
        digester.addCallParam("employee", 0, "lastName");


        // Parse our test input.
        Object root = null;
        root = digester.parse(getInputStream("Test1.xml"));

        assertNotNull("Digester returned an object", root);
        assertTrue("Digester returned an Employee",
                root instanceof Employee);
        Employee employee = (Employee) root;
        assertEquals("First name is correct",
                "First Name",
                employee.getFirstName());
        assertEquals("Last name is correct",
                "Last Name",
                employee.getLastName());

    }


    /**
     * Same as testObjectCreate1(), except use individual call method rules
     * to set the properties of the Employee. Bean data are defined using 
     * elements instead of attributes. The purpose is to test CallMethod with
     * a paramCount=0 (ie the body of the element is the argument of the 
     * method).
     */
    public void testObjectCreate5() throws SAXException, IOException {

        // Configure the digester as required
        digester.addObjectCreate("employee", Employee.class);
        digester.addCallMethod("employee/firstName", "setFirstName", 0);
        digester.addCallMethod("employee/lastName", "setLastName", 0);


        // Parse our test input.
        Object root = null;
        root = digester.parse(getInputStream("Test5.xml"));

        assertNotNull("Digester returned an object", root);
        assertTrue("Digester returned an Employee",
                root instanceof Employee);
        Employee employee = (Employee) root;
        assertEquals("First name is correct",
                "First Name",
                employee.getFirstName());
        assertEquals("Last name is correct",
                "Last Name",
                employee.getLastName());

    }


    /**
     * It should be possible to parse the same input twice, and get trees
     * of objects that are isomorphic but not be identical object instances.
     */
    public void testRepeatedParse() throws SAXException, IOException {

        // Configure the digester as required
        digester.addObjectCreate("employee", Employee.class);
        digester.addSetProperties("employee");
        digester.addObjectCreate("employee/address",
                "org.apache.commons.digester.Address");
        digester.addSetProperties("employee/address");
        digester.addSetNext("employee/address",
                "addAddress");

        // Parse our test input the first time
        Object root1 = null;
        root1 = digester.parse(getInputStream("Test1.xml"));

        validateObjectCreate3(root1);

        // Parse our test input the second time
        Object root2 = null;
        root2 = digester.parse(getInputStream("Test1.xml"));

        validateObjectCreate3(root2);

        // Make sure that it was a different root
        assertTrue("Different tree instances were returned",
                root1 != root2);

    }


    /**
     * Test object creation (and associated property setting) with nothing on
     * the stack, which should cause an appropriate Employee object to be
     * returned.  The processing rules will process the nested Address elements
     * as well, but will not attempt to add them to the Employee.
     */
    public void testRuleSet1() throws SAXException, IOException {

        // Configure the digester as required
        RuleSet rs = new TestRuleSet();
        digester.addRuleSet(rs);

        // Parse our test input.
        Object root = null;
        root = digester.parse(getInputStream("Test1.xml"));

        assertNotNull("Digester returned an object", root);
        assertTrue("Digester returned an Employee",
                root instanceof Employee);
        Employee employee = (Employee) root;
        assertEquals("First name is correct",
                "First Name",
                employee.getFirstName());
        assertEquals("Last name is correct",
                "Last Name",
                employee.getLastName());
        assertNotNull("Can retrieve home address",
                employee.getAddress("home"));
        assertNotNull("Can retrieve office address",
                employee.getAddress("office"));

    }


    /**
     * Same as <code>testRuleSet1</code> except using a single namespace.
     */
    public void testRuleSet2() throws SAXException, IOException {

        // Configure the digester as required
        digester.setNamespaceAware(true);
        RuleSet rs = new TestRuleSet(null,
                "http://commons.apache.org/digester/Foo");
        digester.addRuleSet(rs);

        // Parse our test input.
        Object root = null;
        root = digester.parse(getInputStream("Test2.xml"));

        assertNotNull("Digester returned an object", root);
        assertTrue("Digester returned an Employee",
                root instanceof Employee);
        Employee employee = (Employee) root;
        assertEquals("First name is correct",
                "First Name",
                employee.getFirstName());
        assertEquals("Last name is correct",
                "Last Name",
                employee.getLastName());
        assertNotNull("Can retrieve home address",
                employee.getAddress("home"));
        assertNotNull("Can retrieve office address",
                employee.getAddress("office"));

    }


    /**
     * Same as <code>testRuleSet2</code> except using a namespace
     * for employee that we should recognize, and a namespace for
     * address that we should skip.
     */
    public void testRuleSet3() throws SAXException, IOException {

        // Configure the digester as required
        digester.setNamespaceAware(true);
        RuleSet rs = new TestRuleSet(null,
                "http://commons.apache.org/digester/Foo");
        digester.addRuleSet(rs);

        // Parse our test input.
        Object root = null;
        root = digester.parse(getInputStream("Test3.xml"));

        assertNotNull("Digester returned an object", root);
        assertTrue("Digester returned an Employee",
                root instanceof Employee);
        Employee employee = (Employee) root;
        assertEquals("First name is correct",
                "First Name",
                employee.getFirstName());
        assertEquals("Last name is correct",
                "Last Name",
                employee.getLastName());
        assertNull("Can not retrieve home address",
                employee.getAddress("home"));
        assertNull("Can not retrieve office address",
                employee.getAddress("office"));

    }


    /**
     * Test the two argument version of the SetTopRule rule. This test is
     * based on testObjectCreate3 and should result in the same tree of
     * objects.  Instead of using the SetNextRule rule which results in
     * a method invocation on the (top-1) (parent) object with the top
     * object (child) as an argument, this test uses the SetTopRule rule
     * which results in a method invocation on the top object (child)
     * with the top-1 (parent) object as an argument.  The three argument
     * form is tested in <code>testSetTopRule2</code>.
     */
    public void testSetTopRule1() throws SAXException, IOException {

        // Configure the digester as required
        digester.addObjectCreate("employee",
                "org.apache.commons.digester.Employee");
        digester.addSetProperties("employee");
        digester.addObjectCreate("employee/address",
                "org.apache.commons.digester.Address");
        digester.addSetProperties("employee/address");
        digester.addSetTop("employee/address", "setEmployee");

        // Parse our test input.
        Object root = null;
        root = digester.parse(getInputStream("Test1.xml"));
        validateObjectCreate3(root);

    }


    /**
     * Same as <code>testSetTopRule1</code> except using the three argument
     * form of the SetTopRule rule.
     */
    public void testSetTopRule2() throws SAXException, IOException {

        // Configure the digester as required
        digester.addObjectCreate("employee",
                "org.apache.commons.digester.Employee");
        digester.addSetProperties("employee");
        digester.addObjectCreate("employee/address",
                "org.apache.commons.digester.Address");
        digester.addSetProperties("employee/address");
        digester.addSetTop("employee/address", "setEmployee",
                "org.apache.commons.digester.Employee");

        // Parse our test input.
        Object root = null;
        root = digester.parse(getInputStream("Test1.xml"));

        validateObjectCreate3(root);

    }

    /**
     * Test rule addition - this boils down to making sure that 
     * digester is set properly on rule addition.
     */
    public void testAddRule() {
        Digester digester = new Digester();
        TestRule rule =  new TestRule("Test");
        digester.addRule("/root", rule);
        
        assertEquals("Digester is not properly on rule addition.", 
                        digester, rule.getDigester());

    }
    

    public void testSetNext() throws SAXException, IOException {
        Digester digester = new Digester();
        digester.setRules(new ExtendedBaseRules());
        digester.setValidating(false);
        
        
        digester.addObjectCreate("!*/b", BetaBean.class);
        digester.addObjectCreate("!*/a", AlphaBean.class);
        digester.addObjectCreate("root", ArrayList.class);
        digester.addSetProperties("!*");
        digester.addSetNext("!*/b/?", "setChild");
        digester.addSetNext("!*/a/?", "setChild");
        digester.addSetNext("!root/?", "add");
        ArrayList root = 
            (ArrayList) digester.parse(getInputStream("Test4.xml"));
        
        assertEquals("Wrong array size", 2, root.size());
        AlphaBean one = (AlphaBean) root.get(0);
        assertTrue(one.getChild() instanceof BetaBean);
        BetaBean two = (BetaBean) one.getChild();
        assertEquals("Wrong name (1)", two.getName() , "TWO");
        assertTrue(two.getChild() instanceof AlphaBean);
        AlphaBean three = (AlphaBean) two.getChild(); 
        assertEquals("Wrong name (2)", three.getName() , "THREE");       
        BetaBean four = (BetaBean) root.get(1);
        assertEquals("Wrong name (3)", four.getName() , "FOUR");
        assertTrue(four.getChild() instanceof BetaBean);
        BetaBean five = (BetaBean) four.getChild(); 
        assertEquals("Wrong name (4)", five.getName() , "FIVE");               
        
    }
    
    
    public void testSetTop() throws SAXException, IOException {
        Digester digester = new Digester();
        digester.setRules(new ExtendedBaseRules());
        digester.setValidating(false);
        
        
        digester.addObjectCreate("!*/b", BetaBean.class);
        digester.addObjectCreate("!*/a", AlphaBean.class);
        digester.addObjectCreate("root", ArrayList.class);
        digester.addSetProperties("!*");
        digester.addSetTop("!*/b/?", "setParent");
        digester.addSetTop("!*/a/?", "setParent");
        digester.addSetRoot("!*/a", "add");
        digester.addSetRoot("!*/b", "add");
        ArrayList root = 
            (ArrayList) digester.parse(getInputStream("Test4.xml"));
        
        assertEquals("Wrong array size", 5, root.size());
        
        // note that the array is in popped order (rather than pushed)
        
        Object obj = root.get(1);
        assertTrue("TWO should be a BetaBean", obj instanceof BetaBean);
        BetaBean two = (BetaBean) obj;
        assertNotNull("Two's parent should not be null", two.getParent());
        assertEquals("Wrong name (1)", "TWO", two.getName());
        assertEquals("Wrong name (2)", "ONE", two.getParent().getName() );
        
        obj = root.get(0);
        assertTrue("THREE should be an AlphaBean", obj instanceof AlphaBean);
        AlphaBean three = (AlphaBean) obj;
        assertNotNull("Three's parent should not be null", three.getParent());
        assertEquals("Wrong name (3)", "THREE", three.getName());
        assertEquals("Wrong name (4)", "TWO", three.getParent().getName());
        
        obj = root.get(3);
        assertTrue("FIVE should be a BetaBean", obj instanceof BetaBean);
        BetaBean five = (BetaBean) obj;
        assertNotNull("Five's parent should not be null", five.getParent());
        assertEquals("Wrong name (5)", "FIVE", five.getName());
        assertEquals("Wrong name (6)", "FOUR", five.getParent().getName());

    }


    /**
     */
    public void testSetCustomProperties() throws SAXException, IOException {
        
        Digester digester = new Digester();
        
        digester.setValidating(false);
        
        digester.addObjectCreate("toplevel", ArrayList.class);
        digester.addObjectCreate("toplevel/one", Address.class);
        digester.addSetNext("toplevel/one", "add");
        digester.addObjectCreate("toplevel/two", Address.class);
        digester.addSetNext("toplevel/two", "add");
        digester.addObjectCreate("toplevel/three", Address.class);
        digester.addSetNext("toplevel/three", "add");
        digester.addObjectCreate("toplevel/four", Address.class);
        digester.addSetNext("toplevel/four", "add");
        digester.addSetProperties("toplevel/one");
        digester.addSetProperties(
                    "toplevel/two", 
                    new String[] {"alt-street", "alt-city", "alt-state"}, 
                    new String[] {"street", "city", "state"});
        digester.addSetProperties(
                    "toplevel/three", 
                    new String[] {"aCity", "state"}, 
                    new String[] {"city"});
        digester.addSetProperties("toplevel/four", "alt-city", "city");
        

        ArrayList root = 
            (ArrayList) digester.parse(getInputStream("Test7.xml"));
        
        assertEquals("Wrong array size", 4, root.size());
        
        // note that the array is in popped order (rather than pushed)
         
        Object 
        obj = root.get(0);
        assertTrue("(1) Should be an Address ", obj instanceof Address);
        Address addressOne = (Address) obj;
        assertEquals("(1) Street attribute", "New Street", 
                    addressOne.getStreet());
        assertEquals("(1) City attribute", "Las Vegas", addressOne.getCity());
        assertEquals("(1) State attribute", "Nevada", addressOne.getState());
        
        obj = root.get(1);
        assertTrue("(2) Should be an Address ", obj instanceof Address);
        Address addressTwo = (Address) obj;
        assertEquals("(2) Street attribute", "Old Street", 
                    addressTwo.getStreet());
        assertEquals("(2) City attribute", "Portland", addressTwo.getCity());
        assertEquals("(2) State attribute", "Oregon", addressTwo.getState());
        
        obj = root.get(2);
        assertTrue("(3) Should be an Address ", obj instanceof Address);
        Address addressThree = (Address) obj;
        assertEquals("(3) Street attribute", "4th Street", 
                    addressThree.getStreet());
        assertEquals("(3) City attribute", "Dayton", addressThree.getCity());
        assertEquals("(3) State attribute", "US" , addressThree.getState());
       
        obj = root.get(3);
        assertTrue("(4) Should be an Address ", obj instanceof Address);
        Address addressFour = (Address) obj;
        assertEquals("(4) Street attribute", "6th Street", 
                    addressFour.getStreet());
        assertEquals("(4) City attribute", "Cleveland", addressFour.getCity());
        assertEquals("(4) State attribute", "Ohio", addressFour.getState());
        
    }
    
    // ------------------------------------------------ Utility Support Methods


    /**
     * Return an appropriate InputStream for the specified test file (which
     * must be inside our current package.
     *
     * @param name Name of the test file we want
     *
     * @exception IOException if an input/output error occurs
     */
    protected InputStream getInputStream(String name) throws IOException {

        return (this.getClass().getResourceAsStream
                ("/org/apache/commons/digester/" + name));

    }


    /**
     * Validate the assertions for ObjectCreateRule3.
     *
     * @param root Root object returned by <code>digester.parse()</code>
     */
    protected void validateObjectCreate3(Object root) {

        // Validate the retrieved Employee
        assertNotNull("Digester returned an object", root);
        assertTrue("Digester returned an Employee",
                root instanceof Employee);
        Employee employee = (Employee) root;
        assertEquals("First name is correct",
                "First Name",
                employee.getFirstName());
        assertEquals("Last name is correct",
                "Last Name",
                employee.getLastName());

        // Validate the corresponding "home" Address
        Address home = employee.getAddress("home");
        assertNotNull("Retrieved home address", home);
        assertEquals("Home street", "Home Street",
                home.getStreet());
        assertEquals("Home city", "Home City",
                home.getCity());
        assertEquals("Home state", "HS",
                home.getState());
        assertEquals("Home zip", "HmZip",
                home.getZipCode());

        // Validate the corresponding "office" Address
        Address office = employee.getAddress("office");
        assertNotNull("Retrieved office address", office);
        assertEquals("Office street", "Office Street",
                office.getStreet());
        assertEquals("Office city", "Office City",
                office.getCity());
        assertEquals("Office state", "OS",
                office.getState());
        assertEquals("Office zip", "OfZip",
                office.getZipCode());

    }
    

}
