001/* $Id: CallMethodRuleTestCase.java 1212522 2011-12-09 17:05:15Z sebb $
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.apache.commons.digester3.binder.DigesterLoader.newLoader;
022import static org.junit.Assert.assertEquals;
023import static org.junit.Assert.assertNotNull;
024import static org.junit.Assert.assertTrue;
025import static org.junit.Assert.fail;
026
027import java.io.IOException;
028import java.io.InputStream;
029import java.io.StringReader;
030import java.util.ArrayList;
031import java.util.HashMap;
032import java.util.Set;
033
034import org.apache.commons.digester3.binder.AbstractRulesModule;
035import org.junit.Test;
036import org.xml.sax.SAXException;
037
038//import org.apache.commons.logging.impl.SimpleLog;
039
040/**
041 * <p>
042 * Tests for the <code>CallMethodRule</code> and associated <code>CallParamRule</code>.
043 * 
044 * @author Christopher Lenz
045 */
046public class CallMethodRuleTestCase
047{
048
049    /**
050     * Test method calls with the CallMethodRule rule. It should be possible to call a method with no arguments using
051     * several rule syntaxes.
052     */
053    @Test
054    public void testBasic()
055        throws SAXException, IOException
056    {
057        Digester digester = newLoader( new AbstractRulesModule()
058        {
059
060            @Override
061            protected void configure()
062            {
063                forPattern( "employee" ).createObject().ofType( Employee.class );
064                // try all syntax permutations
065                forPattern( "employee" ).callMethod( "toString" ).withParamCount( 0 ).withParamTypes( (Class[]) null )
066                    .then()
067                    .callMethod( "toString" ).withParamCount( 0 ).withParamTypes( (String[]) null )
068                    .then()
069                    .callMethod( "toString" ).withParamCount( 0 ).withParamTypes( new Class[] {} )
070                    .then()
071                    .callMethod( "toString" ).withParamCount( 0 ).withParamTypes( new String[] {} )
072                    .then()
073                    .callMethod( "toString" );
074            }
075
076        }).newDigester();
077
078        // Parse our test input.
079        // An exception will be thrown if the method can't be found
080        Employee root1 = digester.parse( getInputStream( "Test5.xml" ) );
081        assertNotNull( root1 );
082    }
083
084    /**
085     * Test method calls with the CallMethodRule reading from the element body, with no CallParamMethod rules added.
086     */
087    @Test
088    public void testCallMethodOnly()
089        throws Exception
090    {
091        Digester digester = newLoader( new AbstractRulesModule()
092        {
093
094            @Override
095            protected void configure()
096            {
097                forPattern( "employee" ).createObject().ofType( Employee.class );
098                forPattern( "employee/firstName" ).callMethod( "setFirstName" ).usingElementBodyAsArgument();
099                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}