001/* $Id: DigesterTestCase.java 1212599 2011-12-09 19:46:42Z simonetripodi $
002 *
003 * Licensed to the Apache Software Foundation (ASF) under one or more
004 * contributor license agreements.  See the NOTICE file distributed with
005 * this work for additional information regarding copyright ownership.
006 * The ASF licenses this file to You under the Apache License, Version 2.0
007 * (the "License"); you may not use this file except in compliance with
008 * the License.  You may obtain a copy of the License at
009 *
010 *      http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019package org.apache.commons.digester3;
020
021import static org.junit.Assert.assertEquals;
022import static org.junit.Assert.assertFalse;
023import static org.junit.Assert.assertNotNull;
024import static org.junit.Assert.assertNotSame;
025import static org.junit.Assert.assertNull;
026import static org.junit.Assert.assertSame;
027import static org.junit.Assert.assertTrue;
028import static org.junit.Assert.fail;
029
030import java.io.File;
031import java.io.InputStream;
032import java.io.Reader;
033import java.io.StringReader;
034import java.math.BigDecimal;
035import java.net.URL;
036import java.util.ArrayList;
037import java.util.EmptyStackException;
038import java.util.Map;
039
040import org.apache.commons.digester3.Digester;
041import org.apache.commons.digester3.ObjectCreateRule;
042import org.apache.commons.digester3.Rule;
043import org.apache.commons.digester3.RulesBase;
044import org.apache.commons.digester3.StackAction;
045import org.apache.commons.digester3.Substitutor;
046import org.junit.After;
047import org.junit.Before;
048import org.junit.Test;
049import org.xml.sax.Attributes;
050import org.xml.sax.InputSource;
051import org.xml.sax.helpers.AttributesImpl;
052
053/**
054 * <p>
055 * Test Case for the Digester class. These tests exercise the individual methods of a Digester, but do not attempt to
056 * process complete documents.
057 * </p>
058 *
059 * @author Craig R. McClanahan
060 */
061public class DigesterTestCase
062{
063
064    // ----------------------------------------------------- Instance Variables
065
066    /**
067     * The digester instance we will be processing.
068     */
069    protected Digester digester = null;
070
071    /**
072     * The set of public identifiers, and corresponding resource names, for the versions of the DTDs that we know about.
073     * There <strong>MUST</strong> be an even number of Strings in this array.
074     */
075    protected static final String registrations[] = { "-//Netscape Communications//DTD RSS 0.9//EN",
076        "/org/apache/commons/digester3/rss/rss-0.9.dtd", "-//Netscape Communications//DTD RSS 0.91//EN",
077        "/org/apache/commons/digester3/rss/rss-0.91.dtd", };
078
079    // -------------------------------------------------- Overall Test Methods
080
081    /**
082     * Set up instance variables required by this test case.
083     */
084    @Before
085    public void setUp()
086    {
087
088        digester = new Digester();
089        digester.setRules( new RulesBase() );
090
091    }
092
093    /**
094     * Tear down instance variables required by this test case.
095     */
096    @After
097    public void tearDown()
098    {
099
100        digester = null;
101
102    }
103
104    // ------------------------------------------------ Individual Test Methods
105
106    /**
107     * Test <code>null</code> parsing. (should lead to <code>IllegalArgumentException</code>s)
108     */
109    @Test
110    public void testNullFileParse()
111        throws Exception
112    {
113
114        try
115        {
116            digester.parse( (File) null );
117            fail( "Expected IllegalArgumentException with null argument" );
118        }
119        catch ( IllegalArgumentException e )
120        {
121            // expected
122        }
123
124    }
125
126    @Test
127    public void testNullInputSourceParse()
128        throws Exception
129    {
130
131        try
132        {
133            digester.parse( (InputSource) null );
134            fail( "Expected IllegalArgumentException with null argument" );
135        }
136        catch ( IllegalArgumentException e )
137        {
138            // expected
139        }
140
141    }
142
143    @Test
144    public void testNullInputStreamParse()
145        throws Exception
146    {
147
148        try
149        {
150            digester.parse( (InputStream) null );
151            fail( "Expected IllegalArgumentException with null argument" );
152        }
153        catch ( IllegalArgumentException e )
154        {
155            // expected
156        }
157
158    }
159
160    @Test
161    public void testNullReaderParse()
162        throws Exception
163    {
164
165        try
166        {
167            digester.parse( (Reader) null );
168            fail( "Expected IllegalArgumentException with null argument" );
169        }
170        catch ( IllegalArgumentException e )
171        {
172            // expected
173        }
174
175    }
176
177    @Test
178    public void testNullStringParse()
179        throws Exception
180    {
181
182        try
183        {
184            digester.parse( (String) null );
185            fail( "Expected IllegalArgumentException with null argument" );
186        }
187        catch ( IllegalArgumentException e )
188        {
189            // expected
190        }
191
192    }
193
194    @Test
195    public void testNullURLParse()
196        throws Exception
197    {
198
199        try
200        {
201            digester.parse( (URL) null );
202            fail( "Expected IllegalArgumentException with null argument" );
203        }
204        catch ( IllegalArgumentException e )
205        {
206            // expected
207        }
208
209    }
210
211    /**
212     * Test the basic property getters and setters.
213     */
214    @Test
215    public void testProperties()
216    {
217
218        assertNull( "Initial error handler is null", digester.getErrorHandler() );
219        digester.setErrorHandler( digester );
220        assertTrue( "Set error handler is digester", digester.getErrorHandler() == digester );
221        digester.setErrorHandler( null );
222        assertNull( "Reset error handler is null", digester.getErrorHandler() );
223
224        assertTrue( "Initial namespace aware is false", !digester.getNamespaceAware() );
225        digester.setNamespaceAware( true );
226        assertTrue( "Set namespace aware is true", digester.getNamespaceAware() );
227        digester.setNamespaceAware( false );
228        assertTrue( "Reset namespace aware is false", !digester.getNamespaceAware() );
229
230        assertTrue( "Initial validating is false", !digester.getValidating() );
231        digester.setValidating( true );
232        assertTrue( "Set validating is true", digester.getValidating() );
233        digester.setValidating( false );
234        assertTrue( "Reset validating is false", !digester.getValidating() );
235
236    }
237
238    /**
239     * Test registration of URLs for specified public identifiers.
240     */
241    @Test
242    public void testRegistrations()
243    {
244
245        Map<String, URL> map = digester.getRegistrations();
246        assertEquals( "Initially zero registrations", 0, map.size() );
247        int n = 0;
248        for ( int i = 0; i < registrations.length; i += 2 )
249        {
250            URL url = this.getClass().getResource( registrations[i + 1] );
251            if ( url != null )
252            {
253                digester.register( registrations[i], url );
254                n++;
255            }
256        }
257        map = digester.getRegistrations();
258        assertEquals( "Registered two URLs", n, map.size() );
259
260        int count[] = new int[n];
261        for ( int i = 0; i < n; i++ )
262            count[i] = 0;
263        for ( String key : map.keySet() )
264        {
265            for ( int i = 0; i < n; i++ )
266            {
267                if ( key.equals( registrations[i * 2] ) )
268                {
269                    count[i]++;
270                    break;
271                }
272            }
273        }
274        for ( int i = 0; i < n; i++ )
275            assertEquals( "Count for key " + registrations[i * 2], 1, count[i] );
276
277    }
278
279    /**
280     * Basic test for rule creation and matching.
281     */
282    @Test
283    public void testRules()
284    {
285
286        assertEquals( "Initial rules list is empty", 0, digester.getRules().match( null, "a", null, null ).size() );
287        digester.addSetProperties( "a" );
288        assertEquals( "Add a matching rule", 1, digester.getRules().match( null, "a", null, null ).size() );
289        digester.addSetProperties( "b" );
290        assertEquals( "Add a non-matching rule", 1, digester.getRules().match( null, "a", null, null ).size() );
291        digester.addSetProperties( "a/b" );
292        assertEquals( "Add a non-matching nested rule", 1, digester.getRules().match( null, "a", null, null ).size() );
293        digester.addSetProperties( "a/b" );
294        assertEquals( "Add a second matching rule", 2, digester.getRules().match( null, "a/b", null, null ).size() );
295
296    }
297
298    /**
299     * <p>
300     * Test matching rules in {@link RulesBase}.
301     * </p>
302     * <p>
303     * Tests:
304     * </p>
305     * <ul>
306     * <li>exact match</li>
307     * <li>tail match</li>
308     * <li>longest pattern rule</li>
309     * </ul>
310     */
311    @Test
312    public void testRulesBase()
313    {
314
315        assertEquals( "Initial rules list is empty", 0, digester.getRules().rules().size() );
316
317        // We're going to set up
318        digester.addRule( "a/b/c/d", new TestRule( "a/b/c/d" ) );
319        digester.addRule( "*/d", new TestRule( "*/d" ) );
320        digester.addRule( "*/c/d", new TestRule( "*/c/d" ) );
321
322        // Test exact match
323        assertEquals( "Exact match takes precedence 1", 1, digester.getRules().match( null, "a/b/c/d", null, null ).size() );
324        assertEquals( "Exact match takes precedence 2", "a/b/c/d",
325                      ( (TestRule) digester.getRules().match( null, "a/b/c/d", null, null ).iterator().next() ).getIdentifier() );
326
327        // Test wildcard tail matching
328        assertEquals( "Wildcard tail matching rule 1", 1, digester.getRules().match( null, "a/b/d", null, null ).size() );
329        assertEquals( "Wildcard tail matching rule 2", "*/d",
330                      ( (TestRule) digester.getRules().match( null, "a/b/d", null, null ).iterator().next() ).getIdentifier() );
331
332        // Test the longest matching pattern rule
333        assertEquals( "Longest tail rule 1", 1, digester.getRules().match( null, "x/c/d", null, null ).size() );
334        assertEquals( "Longest tail rule 2", "*/c/d",
335                      ( (TestRule) digester.getRules().match( null, "x/c/d", null, null ).iterator().next() ).getIdentifier() );
336
337    }
338
339    /**
340     * Test the basic stack mechanisms.
341     */
342    @Test
343    public void testStackMethods()
344    {
345
346        Object value = null;
347
348        // New stack must be empty
349        assertEquals( "New stack is empty", 0, digester.getCount() );
350        value = digester.peek();
351        assertNull( "New stack peek() returns null", value );
352        value = digester.pop();
353        assertNull( "New stack pop() returns null", value );
354
355        // Test pushing and popping activities
356        digester.push( "First Item" );
357        assertEquals( "Pushed one item size", 1, digester.getCount() );
358        value = digester.peek();
359        assertNotNull( "Peeked first item is not null", value );
360        assertEquals( "Peeked first item value", "First Item", value );
361
362        digester.push( "Second Item" );
363        assertEquals( "Pushed two items size", 2, digester.getCount() );
364        value = digester.peek();
365        assertNotNull( "Peeked second item is not null", value );
366        assertEquals( "Peeked second item value", "Second Item", value );
367
368        value = digester.pop();
369        assertEquals( "Popped stack size", 1, digester.getCount() );
370        assertNotNull( "Popped second item is not null", value );
371        assertEquals( "Popped second item value", "Second Item", value );
372        value = digester.peek();
373        assertNotNull( "Remaining item is not null", value );
374        assertEquals( "Remaining item value", "First Item", value );
375        assertEquals( "Remaining stack size", 1, digester.getCount() );
376
377        // Cleared stack is empty
378        digester.push( "Dummy Item" );
379        digester.clear();
380        assertEquals( "Cleared stack is empty", 0, digester.getCount() );
381        value = digester.peek();
382        assertNull( "Cleared stack peek() returns null", value );
383        value = digester.pop();
384        assertNull( "Cleared stack pop() returns null", value );
385
386    }
387
388    @Test
389    public void testOnceAndOnceOnly()
390        throws Exception
391    {
392
393        class TestConfigureDigester
394            extends Digester
395        {
396            public int called = 0;
397
398            public TestConfigureDigester()
399            {
400            }
401
402            @Override
403            protected void initialize()
404            {
405                called++;
406            }
407        }
408
409        TestConfigureDigester digester = new TestConfigureDigester();
410
411        String xml = "<?xml version='1.0'?><document/>";
412        digester.parse( new StringReader( xml ) );
413
414        assertEquals( "Initialize should be called once and only once", 1, digester.called );
415    }
416
417    @Test
418    public void testBasicSubstitution()
419        throws Exception
420    {
421        class TestSubRule
422            extends Rule
423        {
424            public String body;
425
426            public Attributes attributes;
427
428            @Override
429            public void begin( String namespace, String name, Attributes attributes )
430            {
431                this.attributes = new AttributesImpl( attributes );
432            }
433
434            @Override
435            public void body( String namespace, String name, String text )
436            {
437                this.body = text;
438            }
439        }
440
441        TestSubRule tsr = new TestSubRule();
442        Digester digester = new Digester();
443        digester.addRule( "alpha/beta", tsr );
444
445        // it's not easy to transform dirty harry into the mighty circus - but let's give it a try
446        String xml =
447            "<?xml version='1.0'?><alpha><beta forname='Dirty' surname='Harry'>Do you feel luck punk?</beta></alpha>";
448        InputSource in = new InputSource( new StringReader( xml ) );
449
450        digester.parse( in );
451
452        assertEquals( "Unsubstituted body text", "Do you feel luck punk?", tsr.body );
453        assertEquals( "Unsubstituted number of attributes", 2, tsr.attributes.getLength() );
454        assertEquals( "Unsubstituted forname attribute value", "Dirty", tsr.attributes.getValue( "forname" ) );
455        assertEquals( "Unsubstituted surname attribute value", "Harry", tsr.attributes.getValue( "surname" ) );
456
457        digester.setSubstitutor( new Substitutor()
458        {
459            @Override
460            public Attributes substitute( Attributes attributes )
461            {
462                AttributesImpl results = new AttributesImpl();
463                results.addAttribute( "", "python", "python", "CDATA", "Cleese" );
464                return results;
465            }
466
467            @Override
468            public String substitute( String bodyText )
469            {
470                return "And now for something completely different...";
471            }
472        } );
473
474        // now transform into the full monty
475        in = new InputSource( new StringReader( xml ) );
476        digester.parse( in );
477
478        assertEquals( "Substituted body text", "And now for something completely different...", tsr.body );
479        assertEquals( "Substituted number of attributes", 1, tsr.attributes.getLength() );
480        assertEquals( "Substituted python attribute value", "Cleese", tsr.attributes.getValue( "", "python" ) );
481    }
482
483    /** Tests the push-peek-pop cycle for a named stack */
484    @Test
485    public void testNamedStackPushPeekPop()
486        throws Exception
487    {
488        BigDecimal archimedesAveragePi = new BigDecimal( "3.1418" );
489        String testStackName = "org.apache.commons.digester3.tests.testNamedStackPushPeekPop";
490        Digester digester = new Digester();
491        assertTrue( "Stack starts empty:", digester.isEmpty( testStackName ) );
492        digester.push( testStackName, archimedesAveragePi );
493        assertEquals( "Peeked value:", archimedesAveragePi, digester.peek( testStackName ) );
494        assertEquals( "Popped value:", archimedesAveragePi, digester.pop( testStackName ) );
495        assertTrue( "Stack ends empty:", digester.isEmpty( testStackName ) );
496
497        digester.push( testStackName, "1" );
498        digester.push( testStackName, "2" );
499        digester.push( testStackName, "3" );
500
501        assertEquals( "Peek#1", "1", digester.peek( testStackName, 2 ) );
502        assertEquals( "Peek#2", "2", digester.peek( testStackName, 1 ) );
503        assertEquals( "Peek#3", "3", digester.peek( testStackName, 0 ) );
504        assertEquals( "Peek#3a", "3", digester.peek( testStackName ) );
505
506        try
507        {
508            // peek beyond stack
509            digester.peek( testStackName, 3 );
510            fail( "Peek#4 failed to throw an exception." );
511        }
512        catch ( EmptyStackException ex )
513        {
514            // ok, expected
515        }
516
517        try
518        {
519            // peek a nonexistent named stack
520            digester.peek( "no.such.stack", 0 );
521            fail( "Peeking a non-existent stack failed to throw an exception." );
522        }
523        catch ( EmptyStackException ex )
524        {
525            // ok, expected
526        }
527    }
528
529    /** Tests that values are stored independently */
530    @Test
531    public void testNamedIndependence()
532    {
533        String testStackOneName = "org.apache.commons.digester3.tests.testNamedIndependenceOne";
534        String testStackTwoName = "org.apache.commons.digester3.tests.testNamedIndependenceTwo";
535        Digester digester = new Digester();
536        digester.push( testStackOneName, "Tweedledum" );
537        digester.push( testStackTwoName, "Tweedledee" );
538        assertEquals( "Popped value one:", "Tweedledum", digester.pop( testStackOneName ) );
539        assertEquals( "Popped value two:", "Tweedledee", digester.pop( testStackTwoName ) );
540    }
541
542    /** Tests popping named stack not yet pushed */
543    @Test
544    public void testPopNamedStackNotPushed()
545    {
546        String testStackName = "org.apache.commons.digester3.tests.testPopNamedStackNotPushed";
547        Digester digester = new Digester();
548        try
549        {
550
551            digester.pop( testStackName );
552            fail( "Expected an EmptyStackException" );
553
554        }
555        catch ( EmptyStackException e )
556        {
557            // expected
558        }
559
560        try
561        {
562
563            digester.peek( testStackName );
564            fail( "Expected an EmptyStackException" );
565
566        }
567        catch ( EmptyStackException e )
568        {
569            // expected
570        }
571    }
572
573    /** Tests for isEmpty */
574    @Test
575    public void testNamedStackIsEmpty()
576    {
577        String testStackName = "org.apache.commons.digester3.tests.testNamedStackIsEmpty";
578        Digester digester = new Digester();
579        assertTrue( "A named stack that has no object pushed onto it yet should be empty",
580                    digester.isEmpty( testStackName ) );
581
582        digester.push( testStackName, "Some test value" );
583        assertFalse( "A named stack that has an object pushed onto it should be not empty",
584                     digester.isEmpty( testStackName ) );
585
586        digester.peek( testStackName );
587        assertFalse( "Peek should not effect whether the stack is empty", digester.isEmpty( testStackName ) );
588
589        digester.pop( testStackName );
590        assertTrue( "A named stack that has it's last object popped is empty", digester.isEmpty( testStackName ) );
591    }
592
593    /**
594     * Test the Digester.getRoot method.
595     */
596    @Test
597    public void testGetRoot()
598        throws Exception
599    {
600        Digester digester = new Digester();
601        digester.addRule( "root", new ObjectCreateRule( TestBean.class ) );
602
603        String xml = "<root/>";
604        InputSource in = new InputSource( new StringReader( xml ) );
605
606        digester.parse( in );
607
608        Object root = digester.getRoot();
609        assertNotNull( "root object not retrieved", root );
610        assertTrue( "root object not a TestRule instance", ( root instanceof TestBean ) );
611    }
612
613    /** Utility class for method testStackAction */
614    private static class TrackingStackAction
615        implements StackAction
616    {
617        public ArrayList<String> events = new ArrayList<String>();
618
619        public Object onPush( Digester d, String stackName, Object o )
620        {
621            String msg = "push:" + stackName + ":" + o.toString();
622            events.add( msg );
623
624            String str = o.toString();
625            if ( str.startsWith( "replpush" ) )
626            {
627                return new String( str );
628            }
629            return o;
630        }
631
632        public Object onPop( Digester d, String stackName, Object o )
633        {
634            String msg = "pop:" + stackName + ":" + o.toString();
635            events.add( msg );
636            String str = o.toString();
637            if ( str.startsWith( "replpop" ) )
638            {
639                return new String( str );
640            }
641            return o;
642        }
643    }
644
645    /**
646     * Test custom StackAction subclasses.
647     */
648    @Test
649    public void testStackAction()
650    {
651        TrackingStackAction action = new TrackingStackAction();
652
653        Object obj1 = new String( "obj1" );
654        Object obj2 = new String( "obj2" );
655        Object obj3 = new String( "replpop.obj3" );
656        Object obj4 = new String( "replpush.obj4" );
657
658        Object obj8 = new String( "obj8" );
659        Object obj9 = new String( "obj9" );
660
661        Digester d = new Digester();
662        d.setStackAction( action );
663
664        assertEquals( 0, action.events.size() );
665        d.push( obj1 );
666        d.push( obj2 );
667        d.push( obj3 );
668        d.push( obj4 );
669
670        assertNotNull( d.peek( 0 ) );
671        // for obj4, a copy should have been pushed
672        assertNotSame( obj4, d.peek( 0 ) );
673        assertEquals( obj4, d.peek( 0 ) );
674        // for obj3, replacement only occurs on pop
675        assertSame( obj3, d.peek( 1 ) );
676        assertSame( obj2, d.peek( 2 ) );
677        assertSame( obj1, d.peek( 3 ) );
678
679        Object obj4a = d.pop();
680        Object obj3a = d.pop();
681        Object obj2a = d.pop();
682        Object obj1a = d.pop();
683
684        assertFalse( obj4 == obj4a );
685        assertEquals( obj4, obj4a );
686        assertFalse( obj3 == obj4a );
687        assertEquals( obj3, obj3a );
688        assertSame( obj2, obj2a );
689        assertSame( obj1, obj1a );
690
691        d.push( "stack1", obj8 );
692        d.push( "stack1", obj9 );
693        Object obj9a = d.pop( "stack1" );
694        Object obj8a = d.pop( "stack1" );
695
696        assertSame( obj8, obj8a );
697        assertSame( obj9, obj9a );
698
699        assertEquals( 12, action.events.size() );
700        assertEquals( "push:null:obj1", action.events.get( 0 ) );
701        assertEquals( "push:null:obj2", action.events.get( 1 ) );
702        assertEquals( "push:null:replpop.obj3", action.events.get( 2 ) );
703        assertEquals( "push:null:replpush.obj4", action.events.get( 3 ) );
704        assertEquals( "pop:null:replpush.obj4", action.events.get( 4 ) );
705        assertEquals( "pop:null:replpop.obj3", action.events.get( 5 ) );
706        assertEquals( "pop:null:obj2", action.events.get( 6 ) );
707        assertEquals( "pop:null:obj1", action.events.get( 7 ) );
708
709        assertEquals( "push:stack1:obj8", action.events.get( 8 ) );
710        assertEquals( "push:stack1:obj9", action.events.get( 9 ) );
711        assertEquals( "pop:stack1:obj9", action.events.get( 10 ) );
712        assertEquals( "pop:stack1:obj8", action.events.get( 11 ) );
713    }
714}