| /* $Id$ |
| * |
| * 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.digester3; |
| |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertFalse; |
| import static org.junit.Assert.assertNotNull; |
| import static org.junit.Assert.assertNotSame; |
| import static org.junit.Assert.assertNull; |
| import static org.junit.Assert.assertSame; |
| import static org.junit.Assert.assertTrue; |
| import static org.junit.Assert.fail; |
| |
| 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.Map; |
| |
| import org.apache.commons.digester3.Digester; |
| import org.apache.commons.digester3.ObjectCreateRule; |
| import org.apache.commons.digester3.Rule; |
| import org.apache.commons.digester3.RulesBase; |
| import org.apache.commons.digester3.StackAction; |
| import org.apache.commons.digester3.Substitutor; |
| import org.junit.After; |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.xml.sax.Attributes; |
| 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$ $Date$ |
| */ |
| |
| public class DigesterTestCase |
| { |
| |
| // ----------------------------------------------------- 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/digester3/rss/rss-0.9.dtd", "-//Netscape Communications//DTD RSS 0.91//EN", |
| "/org/apache/commons/digester3/rss/rss-0.91.dtd", }; |
| |
| // -------------------------------------------------- Overall Test Methods |
| |
| /** |
| * Set up instance variables required by this test case. |
| */ |
| @Before |
| public void setUp() |
| { |
| |
| digester = new Digester(); |
| digester.setRules( new RulesBase() ); |
| |
| } |
| |
| /** |
| * Tear down instance variables required by this test case. |
| */ |
| @After |
| public void tearDown() |
| { |
| |
| digester = null; |
| |
| } |
| |
| // ------------------------------------------------ Individual Test Methods |
| |
| /** |
| * Test <code>null</code> parsing. (should lead to <code>IllegalArgumentException</code>s) |
| */ |
| @Test |
| public void testNullFileParse() |
| throws Exception |
| { |
| |
| try |
| { |
| digester.parse( (File) null ); |
| fail( "Expected IllegalArgumentException with null argument" ); |
| } |
| catch ( IllegalArgumentException e ) |
| { |
| // expected |
| } |
| |
| } |
| |
| @Test |
| public void testNullInputSourceParse() |
| throws Exception |
| { |
| |
| try |
| { |
| digester.parse( (InputSource) null ); |
| fail( "Expected IllegalArgumentException with null argument" ); |
| } |
| catch ( IllegalArgumentException e ) |
| { |
| // expected |
| } |
| |
| } |
| |
| @Test |
| public void testNullInputStreamParse() |
| throws Exception |
| { |
| |
| try |
| { |
| digester.parse( (InputStream) null ); |
| fail( "Expected IllegalArgumentException with null argument" ); |
| } |
| catch ( IllegalArgumentException e ) |
| { |
| // expected |
| } |
| |
| } |
| |
| @Test |
| public void testNullReaderParse() |
| throws Exception |
| { |
| |
| try |
| { |
| digester.parse( (Reader) null ); |
| fail( "Expected IllegalArgumentException with null argument" ); |
| } |
| catch ( IllegalArgumentException e ) |
| { |
| // expected |
| } |
| |
| } |
| |
| @Test |
| public void testNullStringParse() |
| throws Exception |
| { |
| |
| try |
| { |
| digester.parse( (String) null ); |
| fail( "Expected IllegalArgumentException with null argument" ); |
| } |
| catch ( IllegalArgumentException e ) |
| { |
| // expected |
| } |
| |
| } |
| |
| @Test |
| 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. |
| */ |
| @Test |
| public void testProperties() |
| { |
| |
| assertNull( "Initial error handler is null", digester.getErrorHandler() ); |
| digester.setErrorHandler( 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. |
| */ |
| @Test |
| public void testRegistrations() |
| { |
| |
| Map<String, URL> 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; |
| for ( String key : map.keySet() ) |
| { |
| 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. |
| */ |
| @Test |
| public void testRules() |
| { |
| |
| assertEquals( "Initial rules list is empty", 0, digester.getRules().match( null, "a", null, null ).size() ); |
| digester.addSetProperties( "a" ); |
| assertEquals( "Add a matching rule", 1, digester.getRules().match( null, "a", null, null ).size() ); |
| digester.addSetProperties( "b" ); |
| assertEquals( "Add a non-matching rule", 1, digester.getRules().match( null, "a", null, null ).size() ); |
| digester.addSetProperties( "a/b" ); |
| assertEquals( "Add a non-matching nested rule", 1, digester.getRules().match( null, "a", null, null ).size() ); |
| digester.addSetProperties( "a/b" ); |
| assertEquals( "Add a second matching rule", 2, digester.getRules().match( null, "a/b", null, null ).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> |
| */ |
| @Test |
| 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", null, null ).size() ); |
| assertEquals( "Exact match takes precedence 2", "a/b/c/d", |
| ( (TestRule) digester.getRules().match( null, "a/b/c/d", null, null ).iterator().next() ).getIdentifier() ); |
| |
| // Test wildcard tail matching |
| assertEquals( "Wildcard tail matching rule 1", 1, digester.getRules().match( null, "a/b/d", null, null ).size() ); |
| assertEquals( "Wildcard tail matching rule 2", "*/d", |
| ( (TestRule) digester.getRules().match( null, "a/b/d", null, null ).iterator().next() ).getIdentifier() ); |
| |
| // Test the longest matching pattern rule |
| assertEquals( "Longest tail rule 1", 1, digester.getRules().match( null, "x/c/d", null, null ).size() ); |
| assertEquals( "Longest tail rule 2", "*/c/d", |
| ( (TestRule) digester.getRules().match( null, "x/c/d", null, null ).iterator().next() ).getIdentifier() ); |
| |
| } |
| |
| /** |
| * Test the basic stack mechanisms. |
| */ |
| @Test |
| 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", 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", 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", value ); |
| value = digester.peek(); |
| assertNotNull( "Remaining item is not null", value ); |
| assertEquals( "Remaining item value", "First Item", 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 ); |
| |
| } |
| |
| @Test |
| public void testOnceAndOnceOnly() |
| throws Exception |
| { |
| |
| class TestConfigureDigester |
| extends Digester |
| { |
| public int called = 0; |
| |
| public TestConfigureDigester() |
| { |
| } |
| |
| @Override |
| 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 ); |
| } |
| |
| @Test |
| public void testBasicSubstitution() |
| throws Exception |
| { |
| class TestSubRule |
| extends Rule |
| { |
| public String body; |
| |
| public Attributes attributes; |
| |
| @Override |
| public void begin( String namespace, String name, Attributes attributes ) |
| { |
| this.attributes = new AttributesImpl( attributes ); |
| } |
| |
| @Override |
| 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() |
| { |
| @Override |
| public Attributes substitute( Attributes attributes ) |
| { |
| AttributesImpl results = new AttributesImpl(); |
| results.addAttribute( "", "python", "python", "CDATA", "Cleese" ); |
| return results; |
| } |
| |
| @Override |
| 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 */ |
| @Test |
| public void testNamedStackPushPeekPop() |
| throws Exception |
| { |
| BigDecimal archimedesAveragePi = new BigDecimal( "3.1418" ); |
| String testStackName = "org.apache.commons.digester3.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 */ |
| @Test |
| public void testNamedIndependence() |
| { |
| String testStackOneName = "org.apache.commons.digester3.tests.testNamedIndependenceOne"; |
| String testStackTwoName = "org.apache.commons.digester3.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 */ |
| @Test |
| public void testPopNamedStackNotPushed() |
| { |
| String testStackName = "org.apache.commons.digester3.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 */ |
| @Test |
| public void testNamedStackIsEmpty() |
| { |
| String testStackName = "org.apache.commons.digester3.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. |
| */ |
| @Test |
| 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<String> events = new ArrayList<String>(); |
| |
| 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 ); |
| } |
| 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 ); |
| } |
| return o; |
| } |
| } |
| |
| /** |
| * Test custom StackAction subclasses. |
| */ |
| @Test |
| 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 |
| assertNotSame( 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 ) ); |
| } |
| } |