View Javadoc

1   /* $Id: CallMethodRuleTestCase.java 1212522 2011-12-09 17:05:15Z sebb $
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one or more
4    * contributor license agreements.  See the NOTICE file distributed with
5    * this work for additional information regarding copyright ownership.
6    * The ASF licenses this file to You under the Apache License, Version 2.0
7    * (the "License"); you may not use this file except in compliance with
8    * the License.  You may obtain a copy of the License at
9    *
10   *      http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  package org.apache.commons.digester3;
20  
21  import static org.apache.commons.digester3.binder.DigesterLoader.newLoader;
22  import static org.junit.Assert.assertEquals;
23  import static org.junit.Assert.assertNotNull;
24  import static org.junit.Assert.assertTrue;
25  import static org.junit.Assert.fail;
26  
27  import java.io.IOException;
28  import java.io.InputStream;
29  import java.io.StringReader;
30  import java.util.ArrayList;
31  import java.util.HashMap;
32  import java.util.Set;
33  
34  import org.apache.commons.digester3.binder.AbstractRulesModule;
35  import org.junit.Test;
36  import org.xml.sax.SAXException;
37  
38  //import org.apache.commons.logging.impl.SimpleLog;
39  
40  /**
41   * <p>
42   * Tests for the <code>CallMethodRule</code> and associated <code>CallParamRule</code>.
43   * 
44   * @author Christopher Lenz
45   */
46  public class CallMethodRuleTestCase
47  {
48  
49      /**
50       * Test method calls with the CallMethodRule rule. It should be possible to call a method with no arguments using
51       * several rule syntaxes.
52       */
53      @Test
54      public void testBasic()
55          throws SAXException, IOException
56      {
57          Digester digester = newLoader( new AbstractRulesModule()
58          {
59  
60              @Override
61              protected void configure()
62              {
63                  forPattern( "employee" ).createObject().ofType( Employee.class );
64                  // try all syntax permutations
65                  forPattern( "employee" ).callMethod( "toString" ).withParamCount( 0 ).withParamTypes( (Class[]) null )
66                      .then()
67                      .callMethod( "toString" ).withParamCount( 0 ).withParamTypes( (String[]) null )
68                      .then()
69                      .callMethod( "toString" ).withParamCount( 0 ).withParamTypes( new Class[] {} )
70                      .then()
71                      .callMethod( "toString" ).withParamCount( 0 ).withParamTypes( new String[] {} )
72                      .then()
73                      .callMethod( "toString" );
74              }
75  
76          }).newDigester();
77  
78          // Parse our test input.
79          // An exception will be thrown if the method can't be found
80          Employee root1 = digester.parse( getInputStream( "Test5.xml" ) );
81          assertNotNull( root1 );
82      }
83  
84      /**
85       * Test method calls with the CallMethodRule reading from the element body, with no CallParamMethod rules added.
86       */
87      @Test
88      public void testCallMethodOnly()
89          throws Exception
90      {
91          Digester digester = newLoader( new AbstractRulesModule()
92          {
93  
94              @Override
95              protected void configure()
96              {
97                  forPattern( "employee" ).createObject().ofType( Employee.class );
98                  forPattern( "employee/firstName" ).callMethod( "setFirstName" ).usingElementBodyAsArgument();
99                  forPattern( "employee/lastName" ).callMethod( "setLastName" ).usingElementBodyAsArgument();
100             }
101 
102         }).newDigester();
103 
104         // Parse our test input
105         Employee employee = digester.parse( getInputStream( "Test9.xml" ) );
106         assertNotNull( "parsed an employee", employee );
107 
108         // Validate that the property setters were called
109         assertEquals( "Set first name", "First Name", employee.getFirstName() );
110         assertEquals( "Set last name", "Last Name", employee.getLastName() );
111     }
112 
113     /**
114      * Test CallMethodRule variants which specify the classes of the parameters to target methods. String, int, boolean,
115      * float should all be acceptable as parameter types.
116      */
117     @Test
118     public void testSettingProperties()
119         throws SAXException, IOException
120     {
121         Digester digester = newLoader( new AbstractRulesModule()
122         {
123 
124             @Override
125             protected void configure()
126             {
127                 forPattern( "employee" ).createObject().ofType( Employee.class )
128                     .then()
129                     .callMethod( "setLastName" ).withParamTypes( "java.lang.String" );
130                 forPattern( "employee/lastName" ).callParam().ofIndex( 0 );
131             }
132 
133         }).newDigester();
134 
135         // Parse our test input
136 
137         // an exception will be thrown if the method can't be found
138         Employee employee = digester.parse( getInputStream( "Test5.xml" ) );
139         assertEquals( "Failed to call Employee.setLastName", "Last Name", employee.getLastName() );
140 
141         digester = newLoader( new AbstractRulesModule()
142         {
143 
144             @Override
145             protected void configure()
146             {
147                 forPattern( "employee" ).createObject().ofType( Employee.class )
148                     .then()
149                     .callMethod( "setAge" ).withParamTypes( int.class );
150                 forPattern( "employee/age" ).callParam();
151             }
152 
153         }).newDigester();
154 
155         // Parse our test input
156         // an exception will be thrown if the method can't be found
157         employee = digester.parse( getInputStream( "Test5.xml" ) );
158         assertEquals( "Failed to call Employee.setAge", 21, employee.getAge() );
159 
160         digester = newLoader( new AbstractRulesModule()
161         {
162 
163             @Override
164             protected void configure()
165             {
166                 forPattern( "employee" ).createObject().ofType( Employee.class )
167                     .then()
168                     .callMethod( "setActive" ).withParamTypes( boolean.class );
169                 forPattern( "employee/active" ).callParam();
170             }
171 
172         }).newDigester();
173 
174         // Parse our test input
175         // an exception will be thrown if the method can't be found
176         employee = digester.parse( getInputStream( "Test5.xml" ) );
177         assertEquals( "Failed to call Employee.setActive", true, employee.isActive() );
178 
179         digester = newLoader( new AbstractRulesModule()
180         {
181 
182             @Override
183             protected void configure()
184             {
185                 forPattern( "employee" ).createObject().ofType( Employee.class )
186                     .then()
187                     .callMethod( "setSalary" ).withParamTypes( float.class );
188                 forPattern( "employee/salary" ).callParam();
189             }
190 
191         }).newDigester();
192 
193         // Parse our test input
194         // an exception will be thrown if the method can't be found
195         employee = digester.parse( getInputStream( "Test5.xml" ) );
196         assertEquals( "Failed to call Employee.setSalary", 1000000.0f, employee.getSalary(), 0.1f );
197     }
198 
199     /**
200      * This tests the call methods params enhancement that provides for more complex stack-based calls.
201      */
202     @Test
203     public void testParamsFromStack()
204         throws SAXException, IOException
205     {
206         Digester digester = newLoader( new AbstractRulesModule()
207         {
208 
209             @Override
210             protected void configure()
211             {
212                 forPattern( "map" ).createObject().ofType( HashMap.class )
213                     .then()
214                     .callMethod( "put" ).withParamCount( 2 );
215                 forPattern( "map/key" ).createObject().ofType( AlphaBean.class )
216                     .then()
217                     .setProperties()
218                     .then()
219                     .callParam().fromStack( true );
220                 forPattern( "map/value" ).createObject().ofType( BetaBean.class )
221                     .then()
222                     .setProperties()
223                     .then()
224                     .callParam().ofIndex( 1 ).fromStack( true );
225             }
226 
227         }).newDigester();
228 
229         StringBuilder xml =
230             new StringBuilder().append( "<?xml version='1.0'?>" ).append( "<map>" ).append( "  <key name='The key'/>" ).append( "  <value name='The value'/>" ).append( "</map>" );
231 
232         HashMap<AlphaBean, BetaBean> map = digester.parse( new StringReader( xml.toString() ) );
233 
234         assertNotNull( map );
235         assertEquals( 1, map.size() );
236         assertEquals( "The key", map.keySet().iterator().next().getName() );
237         assertEquals( "The value", map.values().iterator().next().getName() );
238     }
239 
240     /**
241      * Test that the target object for a CallMethodRule is the object that was on top of the object stack when the
242      * CallMethodRule fired, even when other rules fire between the CallMethodRule and its associated CallParamRules.
243      * <p>
244      * The current implementation of CallMethodRule ensures this works by firing only at the end of the tag that
245      * CallMethodRule triggered on.
246      */
247     @Test
248     public void testOrderNestedPartA()
249         throws Exception
250     {
251         Digester digester = newLoader( new AbstractRulesModule()
252         {
253 
254             @Override
255             protected void configure()
256             {
257                 // Here, we use the "grandchild element name" as a parameter to
258                 // the created element, to ensure that all the params aren't
259                 // avaiable to the CallMethodRule until some other rules have fired,
260                 // in particular an ObjectCreateRule. The CallMethodRule should still
261                 // function correctly in this scenario.
262                 forPattern( "toplevel/element" ).createObject().ofType( NamedBean.class )
263                     .then()
264                     .callMethod( "setName" ).withParamCount( 1 );
265                 forPattern( "toplevel/element/element/element" ).callParam().ofIndex( 0 ).fromAttribute( "name" );
266                 forPattern( "toplevel/element/element" ).createObject().ofType( NamedBean.class );
267             }
268 
269         }).newDigester();
270 
271        // Parse our test input
272        // an exception will be thrown if the method can't be found
273         NamedBean root1 = digester.parse( getInputStream( "Test8.xml" ) );
274 
275 
276         // if the CallMethodRule were to incorrectly invoke the method call
277         // on the second-created NamedBean instance, then the root one would
278         // have a null name. If it works correctly, the target element will
279         // be the first-created (root) one, despite the fact that a second
280         // object instance was created between the firing of the
281         // CallMethodRule and its associated CallParamRule.
282         assertEquals( "Wrong method call order", "C", root1.getName() );
283     }
284 
285     /**
286      * Test nested CallMethod rules.
287      * <p>
288      * The current implementation of CallMethodRule, in which the method is invoked in its end() method, causes
289      * behaviour which some users find non-intuitive. In this test it can be seen to "reverse" the order of data
290      * processed. However this is the way CallMethodRule has always behaved, and it is expected that apps out there rely
291      * on this call order so this test is present to ensure that no-one changes this behaviour.
292      */
293     @Test
294     public void testOrderNestedPartB()
295         throws Exception
296     {
297         Digester digester = newLoader( new AbstractRulesModule()
298         {
299 
300             @Override
301             protected void configure()
302             {
303                 forPattern( "*/element" ).callMethod( "append" ).withParamCount( 1 )
304                     .then()
305                     .callParam().ofIndex( 0 ).fromAttribute( "name" );
306             }
307 
308         }).newDigester();
309 
310         // Configure the digester as required
311         StringBuilder word = new StringBuilder();
312         digester.push( word );
313 
314         // Parse our test input
315         Object root1 = null;
316         try
317         {
318             // an exception will be thrown if the method can't be found
319             root1 = digester.parse( getInputStream( "Test8.xml" ) );
320             assertNotNull( root1 );
321         }
322         catch ( Throwable t )
323         {
324             // this means that the method can't be found and so the test fails
325             fail( "Digester threw Exception:  " + t );
326         }
327 
328         assertEquals( "Wrong method call order", "CBA", word.toString() );
329     }
330 
331     @Test
332     public void testPrimitiveReading()
333         throws Exception
334     {
335         StringReader reader =
336             new StringReader( "<?xml version='1.0' ?><root><bean good='true'/><bean good='false'/><bean/>"
337                 + "<beanie bad='Fee Fie Foe Fum' good='true'/><beanie bad='Fee Fie Foe Fum' good='false'/>"
338                 + "<beanie bad='Fee Fie Foe Fum'/></root>" );
339 
340         Digester digester = new Digester();
341 
342         // SimpleLog log = new SimpleLog("[testPrimitiveReading:Digester]");
343         // log.setLevel(SimpleLog.LOG_LEVEL_TRACE);
344         // digester.setLogger(log);
345 
346         digester.addObjectCreate( "root/bean", PrimitiveBean.class );
347         digester.addSetNext( "root/bean", "add" );
348         Class<?>[] params = { Boolean.TYPE };
349         digester.addCallMethod( "root/bean", "setBoolean", 1, params );
350         digester.addCallParam( "root/bean", 0, "good" );
351 
352         digester.addObjectCreate( "root/beanie", PrimitiveBean.class );
353         digester.addSetNext( "root/beanie", "add" );
354         Class<?>[] beanieParams = { String.class, Boolean.TYPE };
355         digester.addCallMethod( "root/beanie", "testSetBoolean", 2, beanieParams );
356         digester.addCallParam( "root/beanie", 0, "bad" );
357         digester.addCallParam( "root/beanie", 1, "good" );
358 
359         ArrayList<PrimitiveBean> list = new ArrayList<PrimitiveBean>();
360         digester.push( list );
361         digester.parse( reader );
362 
363         assertEquals( "Wrong number of beans in list", 6, list.size() );
364         PrimitiveBean bean = list.get( 0 );
365         assertTrue( "Bean 0 property not called", bean.getSetBooleanCalled() );
366         assertEquals( "Bean 0 property incorrect", true, bean.getBoolean() );
367         bean = list.get( 1 );
368         assertTrue( "Bean 1 property not called", bean.getSetBooleanCalled() );
369         assertEquals( "Bean 1 property incorrect", false, bean.getBoolean() );
370         bean = list.get( 2 );
371         // no attibute, no call is what's expected
372         assertTrue( "Bean 2 property called", !bean.getSetBooleanCalled() );
373         bean = list.get( 3 );
374         assertTrue( "Bean 3 property not called", bean.getSetBooleanCalled() );
375         assertEquals( "Bean 3 property incorrect", true, bean.getBoolean() );
376         bean = list.get( 4 );
377         assertTrue( "Bean 4 property not called", bean.getSetBooleanCalled() );
378         assertEquals( "Bean 4 property incorrect", false, bean.getBoolean() );
379         bean = list.get( 5 );
380         assertTrue( "Bean 5 property not called", bean.getSetBooleanCalled() );
381         assertEquals( "Bean 5 property incorrect", false, bean.getBoolean() );
382     }
383 
384     @Test
385     public void testFromStack()
386         throws Exception
387     {
388 
389         StringReader reader =
390             new StringReader( "<?xml version='1.0' ?><root><one/><two/><three/><four/><five/></root>" );
391 
392         Digester digester = new Digester();
393 
394         Class<?>[] params = { String.class };
395 
396         digester.addObjectCreate( "root/one", NamedBean.class );
397         digester.addSetNext( "root/one", "add" );
398         digester.addCallMethod( "root/one", "setName", 1, params );
399         digester.addCallParam( "root/one", 0, 2 );
400 
401         digester.addObjectCreate( "root/two", NamedBean.class );
402         digester.addSetNext( "root/two", "add" );
403         digester.addCallMethod( "root/two", "setName", 1, params );
404         digester.addCallParam( "root/two", 0, 3 );
405 
406         digester.addObjectCreate( "root/three", NamedBean.class );
407         digester.addSetNext( "root/three", "add" );
408         digester.addCallMethod( "root/three", "setName", 1, params );
409         digester.addCallParam( "root/three", 0, 4 );
410 
411         digester.addObjectCreate( "root/four", NamedBean.class );
412         digester.addSetNext( "root/four", "add" );
413         digester.addCallMethod( "root/four", "setName", 1, params );
414         digester.addCallParam( "root/four", 0, 5 );
415 
416         digester.addObjectCreate( "root/five", NamedBean.class );
417         digester.addSetNext( "root/five", "add" );
418         Class<?>[] newParams = { String.class, String.class };
419         digester.addCallMethod( "root/five", "test", 2, newParams );
420         digester.addCallParam( "root/five", 0, 10 );
421         digester.addCallParam( "root/five", 1, 3 );
422 
423         // prepare stack
424         digester.push( "That lamb was sure to go." );
425         digester.push( "And everywhere that Mary went," );
426         digester.push( "It's fleece was white as snow." );
427         digester.push( "Mary had a little lamb," );
428 
429         ArrayList<NamedBean> list = new ArrayList<NamedBean>();
430         digester.push( list );
431         digester.parse( reader );
432 
433         assertEquals( "Wrong number of beans in list", 5, list.size() );
434         NamedBean bean = list.get( 0 );
435         assertEquals( "Parameter not set from stack (1)", "Mary had a little lamb,", bean.getName() );
436         bean = list.get( 1 );
437         assertEquals( "Parameter not set from stack (2)", "It's fleece was white as snow.", bean.getName() );
438         bean = list.get( 2 );
439         assertEquals( "Parameter not set from stack (3)", "And everywhere that Mary went,", bean.getName() );
440         bean = list.get( 3 );
441         assertEquals( "Parameter not set from stack (4)", "That lamb was sure to go.", bean.getName() );
442         bean = list.get( 4 );
443         assertEquals( "Out of stack not set to null", null, bean.getName() );
444     }
445 
446     @Test
447     public void testTwoCalls()
448         throws Exception
449     {
450 
451         StringReader reader =
452             new StringReader( "<?xml version='1.0' ?><root>" + "<param class='int' coolness='true'>25</param>"
453                 + "<param class='long'>50</param>" + "<param class='float' coolness='false'>90</param></root>" );
454 
455         Digester digester = new Digester();
456         // SimpleLog log = new SimpleLog("{testTwoCalls:Digester]");
457         // log.setLevel(SimpleLog.LOG_LEVEL_TRACE);
458         // digester.setLogger(log);
459 
460         digester.addObjectCreate( "root/param", ParamBean.class );
461         digester.addSetNext( "root/param", "add" );
462         digester.addCallMethod( "root/param", "setThisAndThat", 2 );
463         digester.addCallParam( "root/param", 0, "class" );
464         digester.addCallParam( "root/param", 1 );
465         digester.addCallMethod( "root/param", "setCool", 1, new Class[] { boolean.class } );
466         digester.addCallParam( "root/param", 0, "coolness" );
467 
468         ArrayList<ParamBean> list = new ArrayList<ParamBean>();
469         digester.push( list );
470         digester.parse( reader );
471 
472         assertEquals( "Wrong number of objects created", 3, list.size() );
473         ParamBean bean = list.get( 0 );
474         assertEquals( "Coolness wrong (1)", true, bean.isCool() );
475         assertEquals( "This wrong (1)", "int", bean.getThis() );
476         assertEquals( "That wrong (1)", "25", bean.getThat() );
477         bean = list.get( 1 );
478         assertEquals( "Coolness wrong (2)", false, bean.isCool() );
479         assertEquals( "This wrong (2)", "long", bean.getThis() );
480         assertEquals( "That wrong (2)", "50", bean.getThat() );
481         bean = list.get( 2 );
482         assertEquals( "Coolness wrong (3)", false, bean.isCool() );
483         assertEquals( "This wrong (3)", "float", bean.getThis() );
484         assertEquals( "That wrong (3)", "90", bean.getThat() );
485     }
486 
487     @Test
488     public void testNestedBody()
489         throws Exception
490     {
491 
492         StringReader reader =
493             new StringReader( "<?xml version='1.0' ?><root>" + "<spam>Simple</spam>"
494                 + "<spam>Complex<spam>Deep<spam>Deeper<spam>Deepest</spam></spam></spam></spam>" + "</root>" );
495 
496         Digester digester = new Digester();
497 
498         // SimpleLog log = new SimpleLog("[testPrimitiveReading:Digester]");
499         // log.setLevel(SimpleLog.LOG_LEVEL_TRACE);
500         // digester.setLogger(log);
501 
502         digester.addObjectCreate( "root/spam", NamedBean.class );
503         digester.addSetRoot( "root/spam", "add" );
504         digester.addCallMethod( "root/spam", "setName", 1 );
505         digester.addCallParam( "root/spam", 0 );
506 
507         digester.addObjectCreate( "root/spam/spam", NamedBean.class );
508         digester.addSetRoot( "root/spam/spam", "add" );
509         digester.addCallMethod( "root/spam/spam", "setName", 1 );
510         digester.addCallParam( "root/spam/spam", 0 );
511 
512         digester.addObjectCreate( "root/spam/spam/spam", NamedBean.class );
513         digester.addSetRoot( "root/spam/spam/spam", "add" );
514         digester.addCallMethod( "root/spam/spam/spam", "setName", 1 );
515         digester.addCallParam( "root/spam/spam/spam", 0 );
516 
517         digester.addObjectCreate( "root/spam/spam/spam/spam", NamedBean.class );
518         digester.addSetRoot( "root/spam/spam/spam/spam", "add" );
519         digester.addCallMethod( "root/spam/spam/spam/spam", "setName", 1 );
520         digester.addCallParam( "root/spam/spam/spam/spam", 0 );
521 
522         ArrayList<NamedBean> list = new ArrayList<NamedBean>();
523         digester.push( list );
524         digester.parse( reader );
525 
526         NamedBean bean = list.get( 0 );
527         assertEquals( "Wrong name (1)", "Simple", bean.getName() );
528         // these are added in deepest first order by the addRootRule
529         bean = list.get( 4 );
530         assertEquals( "Wrong name (2)", "Complex", bean.getName() );
531         bean = list.get( 3 );
532         assertEquals( "Wrong name (3)", "Deep", bean.getName() );
533         bean = list.get( 2 );
534         assertEquals( "Wrong name (4)", "Deeper", bean.getName() );
535         bean = list.get( 1 );
536         assertEquals( "Wrong name (5)", "Deepest", bean.getName() );
537     }
538 
539     @Test
540     public void testProcessingHook()
541         throws Exception
542     {
543 
544         class TestCallMethodRule
545             extends CallMethodRule
546         {
547             Object result;
548 
549             TestCallMethodRule( String methodName, int paramCount )
550             {
551                 super( methodName, paramCount );
552             }
553 
554             @Override
555             protected void processMethodCallResult( Object result )
556             {
557                 this.result = result;
558             }
559         }
560 
561         StringReader reader =
562             new StringReader( "<?xml version='1.0' ?><root>"
563                 + "<param class='float' coolness='false'>90</param></root>" );
564 
565         Digester digester = new Digester();
566         // SimpleLog log = new SimpleLog("{testTwoCalls:Digester]");
567         // log.setLevel(SimpleLog.LOG_LEVEL_TRACE);
568         // digester.setLogger(log);
569 
570         digester.addObjectCreate( "root/param", ParamBean.class );
571         digester.addSetNext( "root/param", "add" );
572         TestCallMethodRule rule = new TestCallMethodRule( "setThisAndThat", 2 );
573         digester.addRule( "root/param", rule );
574         digester.addCallParam( "root/param", 0, "class" );
575         digester.addCallParam( "root/param", 1, "coolness" );
576 
577         ArrayList<ParamBean> list = new ArrayList<ParamBean>();
578         digester.push( list );
579         digester.parse( reader );
580 
581         assertEquals( "Wrong number of objects created", 1, list.size() );
582         assertEquals( "Result not passed into hook", "The Other", rule.result );
583     }
584 
585     /** Test for the PathCallParamRule */
586     @Test
587     public void testPathCallParam()
588         throws Exception
589     {
590         String xml =
591             "<?xml version='1.0'?><main>" + "<alpha><beta>Ignore this</beta></alpha>"
592                 + "<beta><epsilon><gamma>Ignore that</gamma></epsilon></beta>" + "</main>";
593 
594         SimpleTestBean bean = new SimpleTestBean();
595         bean.setAlphaBeta( "[UNSET]", "[UNSET]" );
596 
597         StringReader in = new StringReader( xml );
598         Digester digester = new Digester();
599         digester.setRules( new ExtendedBaseRules() );
600         digester.addCallParamPath( "*/alpha/?", 0 );
601         digester.addCallParamPath( "*/epsilon/?", 1 );
602         digester.addCallMethod( "main", "setAlphaBeta", 2 );
603 
604         digester.push( bean );
605 
606         digester.parse( in );
607 
608         assertEquals( "Test alpha property setting", "main/alpha/beta", bean.getAlpha() );
609         assertEquals( "Test beta property setting", "main/beta/epsilon/gamma", bean.getBeta() );
610     }
611 
612     /**
613      * Test invoking an object which does not exist on the stack.
614      */
615     @Test
616     public void testCallInvalidTarget()
617         throws Exception
618     {
619 
620         Digester digester = new Digester();
621         digester.addObjectCreate( "employee", HashMap.class );
622 
623         // there should be only one object on the stack (index zero),
624         // so selecting a target object with index 1 on the object stack
625         // should result in an exception.
626         CallMethodRule r = new CallMethodRule( 1, "put", 0 );
627         digester.addRule( "employee", r );
628 
629         try
630         {
631             digester.parse( getInputStream( "Test5.xml" ) );
632             fail( "Exception should be thrown for invalid target offset" );
633         }
634         catch ( SAXException e )
635         {
636             // ok, exception expected
637         }
638     }
639 
640     /**
641      * Test invoking an object which is at top-1 on the stack, like SetNextRule does...
642      */
643     @Test
644     public void testCallNext()
645         throws Exception
646     {
647 
648         Digester digester = new Digester();
649         digester.addObjectCreate( "employee", HashMap.class );
650 
651         digester.addObjectCreate( "employee/address", Address.class );
652         digester.addSetNestedProperties( "employee/address" );
653         CallMethodRule r = new CallMethodRule( 1, "put", 2 );
654         digester.addRule( "employee/address", r );
655         digester.addCallParam( "employee/address/type", 0 );
656         digester.addCallParam( "employee/address", 1, 0 );
657 
658         HashMap<String, Address> map = digester.parse( getInputStream( "Test5.xml" ) );
659 
660         assertNotNull( map );
661         Set<String> keys = map.keySet();
662         assertEquals( 2, keys.size() );
663         Address home = map.get( "home" );
664         assertNotNull( home );
665         assertEquals( "HmZip", home.getZipCode() );
666         Address office = map.get( "office" );
667         assertNotNull( office );
668         assertEquals( "OfZip", office.getZipCode() );
669     }
670 
671     /**
672      * Test invoking an object which is at the root of the stack, like SetRoot does...
673      */
674     @Test
675     public void testCallRoot()
676         throws Exception
677     {
678 
679         Digester digester = new Digester();
680         digester.addObjectCreate( "employee", HashMap.class );
681 
682         digester.addObjectCreate( "employee/address", Address.class );
683         digester.addSetNestedProperties( "employee/address" );
684         CallMethodRule r = new CallMethodRule( -1, "put", 2 );
685         digester.addRule( "employee/address", r );
686         digester.addCallParam( "employee/address/type", 0 );
687         digester.addCallParam( "employee/address", 1, 0 );
688 
689         HashMap<String, Address> map = digester.parse( getInputStream( "Test5.xml" ) );
690 
691         assertNotNull( map );
692         Set<String> keys = map.keySet();
693         assertEquals( 2, keys.size() );
694         Address home = map.get( "home" );
695         assertNotNull( home );
696         assertEquals( "HmZip", home.getZipCode() );
697         Address office = map.get( "office" );
698         assertNotNull( office );
699         assertEquals( "OfZip", office.getZipCode() );
700     }
701 
702     // ------------------------------------------------ Utility Support Methods
703 
704     /**
705      * Return an appropriate InputStream for the specified test file (which must be inside our current package.
706      * 
707      * @param name Name of the test file we want
708      * @exception IOException if an input/output error occurs
709      */
710     protected InputStream getInputStream( String name )
711         throws IOException
712     {
713 
714         return ( this.getClass().getResourceAsStream( "/org/apache/commons/digester3/" + name ) );
715 
716     }
717 
718 }