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}