Hi,

As discussed on the user list, there appears to be a need to make calls
on objects other than the top-of-stack in a manner more flexible than
the limited SetNextRule and SetRootRule.

Attached is a patch which adds a "targetOffset" attribute to the
CallMethodRule to indicate which object on the digester stack gets
invoked. The default value for the offset is 0, which is the old
behaviour for CallMethodRule (invoke method on top object).

If this is ok, I can commit this myself [first use of new apache commit
rights - yay!]

Notes:
(a) the test case uses SetNestedPropertiesRule. When factory methods for
this new rule were added to Digester, the main usage (no constructor
params) was omitted, so attached is a minor patch to fix this.

(b) several new constructors were added to CallMethodRule each taking an
int targetOffset as a first param. Selecting the target seems the
*first* thing to do, hence makes sense to me to make it the first
param..

(c) In CallMethodRule, if target object for call is found to be null, a
deliberate exception with a useful message is now thrown rather than
throwing a NullPointerException. I used a SAXException as I couldn't see
anything more appropriate. Alternate suggestions welcome.

(d) I haven't added factory methods on Digester for the new
CallMethodRule constructors. This usage isn't wildly common, so I'm not
sure the factory methods are really needed. We can always add them later
anyway.

Regards,

Simon


Index: Digester.java
===================================================================
RCS file: /home/cvspublic/jakarta-commons/digester/src/java/org/apache/commons/digester/Digester.java,v
retrieving revision 1.91
diff -r1.91 Digester.java
2222a2223,2232
>      */
>     public void addSetNestedProperties(String pattern) {
>     
>         addRule(pattern, new SetNestedPropertiesRule());
>     }
> 
>     /**
>      * Adds an [EMAIL PROTECTED] SetNestedPropertiesRule}.
>      *
>      * @param pattern register the rule with this pattern
Index: CallMethodRule.java
===================================================================
RCS file: /home/cvspublic/jakarta-commons/digester/src/java/org/apache/commons/digester/CallMethodRule.java,v
retrieving revision 1.28
diff -r1.28 CallMethodRule.java
101d100
< 
181a181,182
>         this(0, methodName, paramCount);
>     }
182a184,200
>     /**
>      * Construct a "call method" rule with the specified method name.  The
>      * parameter types (if any) default to java.lang.String.
>      *
>      * @param targetOffset location of the target object. Positive numbers are
>      * relative to the top of the digester object stack. Negative numbers 
>      * are relative to the bottom of the stack. Zero implies the top
>      * object on the stack.
>      * @param methodName Method name of the parent method to call
>      * @param paramCount The number of parameters to collect, or
>      *  zero for a single argument from the body of this element.
>      */
>     public CallMethodRule(int targetOffset,
>                           String methodName,
>                           int paramCount) {
> 
>         this.targetOffset = targetOffset;
204c222,239
<         this(methodName, 0, (Class[]) null);
---
>         this(0, methodName, 0, (Class[]) null);
>     
>     }
>     
> 
>     /**
>      * Construct a "call method" rule with the specified method name.  
>      * The method should accept no parameters.
>      *
>      * @param targetOffset location of the target object. Positive numbers are
>      * relative to the top of the digester object stack. Negative numbers 
>      * are relative to the bottom of the stack. Zero implies the top
>      * object on the stack.
>      * @param methodName Method name of the parent method to call
>      */
>     public CallMethodRule(int targetOffset, String methodName) {
>     
>         this(targetOffset, methodName, 0, (Class[]) null);
227a263,264
>         this(0, methodName, paramCount, paramTypes);
>     }
228a266,290
>     /**
>      * Construct a "call method" rule with the specified method name and
>      * parameter types. If <code>paramCount</code> is set to zero the rule
>      * will use the body of this element as the single argument of the
>      * method, unless <code>paramTypes</code> is null or empty, in this
>      * case the rule will call the specified method with no arguments.
>      *
>      * @param targetOffset location of the target object. Positive numbers are
>      * relative to the top of the digester object stack. Negative numbers 
>      * are relative to the bottom of the stack. Zero implies the top
>      * object on the stack.
>      * @param methodName Method name of the parent method to call
>      * @param paramCount The number of parameters to collect, or
>      *  zero for a single argument from the body of ths element
>      * @param paramTypes The Java class names of the arguments
>      *  (if you wish to use a primitive type, specify the corresonding
>      *  Java wrapper class instead, such as <code>java.lang.Boolean</code>
>      *  for a <code>boolean</code> parameter)
>      */
>     public CallMethodRule(  int targetOffset,
>                             String methodName,
>                             int paramCount, 
>                             String paramTypes[]) {
> 
>         this.targetOffset = targetOffset;
267a330,331
>         this(0, methodName, paramCount, paramTypes);
>     }
268a333,358
>     /**
>      * Construct a "call method" rule with the specified method name and
>      * parameter types. If <code>paramCount</code> is set to zero the rule
>      * will use the body of this element as the single argument of the
>      * method, unless <code>paramTypes</code> is null or empty, in this
>      * case the rule will call the specified method with no arguments.
>      *
>      * @param targetOffset location of the target object. Positive numbers are
>      * relative to the top of the digester object stack. Negative numbers 
>      * are relative to the bottom of the stack. Zero implies the top
>      * object on the stack.
>      * @param methodName Method name of the parent method to call
>      * @param paramCount The number of parameters to collect, or
>      *  zero for a single argument from the body of ths element
>      * @param paramTypes The Java classes that represent the
>      *  parameter types of the method arguments
>      *  (if you wish to use a primitive type, specify the corresonding
>      *  Java wrapper class instead, such as <code>java.lang.Boolean.TYPE</code>
>      *  for a <code>boolean</code> parameter)
>      */
>     public CallMethodRule(  int targetOffset,
>                             String methodName,
>                             int paramCount, 
>                             Class paramTypes[]) {
> 
>         this.targetOffset = targetOffset;
294a385,391
>     /** 
>      * location of the target object for the call, relative to the
>      * top of the digester object stack. The default value of zero
>      * means the target object is the one on top of the stack.
>      */
>     private int targetOffset = 0;
> 
463a561,581
>         // Determine the target object for the method call
>         Object target;
>         if (targetOffset >= 0) {
>             target = digester.peek(targetOffset);
>         } else {
>             target = digester.peek( digester.getCount() + targetOffset );
>         }
>         
>         if (target == null) {
>             StringBuffer sb = new StringBuffer();
>             sb.append("[CallMethodRule]{");
>             sb.append(digester.match);
>             sb.append("} Call target is null (");
>             sb.append("targetOffset=");
>             sb.append(targetOffset);
>             sb.append(",stackdepth=");
>             sb.append(digester.getCount());
>             sb.append(")");
>             throw new org.xml.sax.SAXException(sb.toString());
>         }
>         
465d582
<         Object top = digester.peek();
470,474c587
<             if (top == null) {
<                 sb.append("[NULL TOP]");
<             } else {
<                 sb.append(top.getClass().getName());
<             }
---
>             sb.append(target.getClass().getName());
501c614
<             result = MethodUtils.invokeExactMethod(top, methodName,
---
>             result = MethodUtils.invokeExactMethod(target, methodName,
506c619
<             result = MethodUtils.invokeMethod(top, methodName,
---
>             result = MethodUtils.invokeMethod(target, methodName,
Index: CallMethodRuleTestCase.java
===================================================================
RCS file: /home/cvspublic/jakarta-commons/digester/src/test/org/apache/commons/digester/CallMethodRuleTestCase.java,v
retrieving revision 1.12
diff -r1.12 CallMethodRuleTestCase.java
595a596,677
> 
>     /** 
>      * Test invoking an object which does not exist on the stack.
>      */
>     public void testCallInvalidTarget() throws Exception {
>     
>         Digester digester = new Digester();
>         digester.addObjectCreate("employee", HashMap.class);
> 
>         // there should be only one object on the stack (index zero),
>         // so selecting a target object with index 1 on the object stack
>         // should result in an exception.
>         CallMethodRule r = new CallMethodRule(1, "put", 0);
>         digester.addRule("employee", r);
>         
>         try {
>             digester.parse(getInputStream("Test5.xml"));
>             fail("Exception should be thrown for invalid target offset");
>         }
>         catch(SAXException e) {
>             // ok, exception expected
>         }
>     }
> 
>     /** 
>      * Test invoking an object which is at top-1 on the stack, like
>      * SetNextRule does...
>      */
>     public void testCallNext() throws Exception {
>     
>         Digester digester = new Digester();
>         digester.addObjectCreate("employee", HashMap.class);
> 
>         digester.addObjectCreate("employee/address", Address.class);
>         digester.addSetNestedProperties("employee/address");
>         CallMethodRule r = new CallMethodRule(1, "put", 2);
>         digester.addRule("employee/address", r);
>         digester.addCallParam("employee/address/type", 0);
>         digester.addCallParam("employee/address", 1, 0);
>         
>         HashMap map = (HashMap) digester.parse(getInputStream("Test5.xml"));
>         
>         assertNotNull(map);
>         java.util.Set keys = map.keySet();
>         assertEquals(2, keys.size());
>         Address home = (Address) map.get("home");
>         assertNotNull(home);
>         assertEquals("HmZip", home.getZipCode());
>         Address office = (Address) map.get("office");
>         assertNotNull(office);
>         assertEquals("OfZip", office.getZipCode());
>     }
> 
>     /** 
>      * Test invoking an object which is at the root of the stack, like
>      * SetRoot does...
>      */
>     public void testCallRoot() throws Exception {
>     
>         Digester digester = new Digester();
>         digester.addObjectCreate("employee", HashMap.class);
> 
>         digester.addObjectCreate("employee/address", Address.class);
>         digester.addSetNestedProperties("employee/address");
>         CallMethodRule r = new CallMethodRule(-1, "put", 2);
>         digester.addRule("employee/address", r);
>         digester.addCallParam("employee/address/type", 0);
>         digester.addCallParam("employee/address", 1, 0);
>         
>         HashMap map = (HashMap) digester.parse(getInputStream("Test5.xml"));
>         
>         assertNotNull(map);
>         java.util.Set keys = map.keySet();
>         assertEquals(2, keys.size());
>         Address home = (Address) map.get("home");
>         assertNotNull(home);
>         assertEquals("HmZip", home.getZipCode());
>         Address office = (Address) map.get("office");
>         assertNotNull(office);
>         assertEquals("OfZip", office.getZipCode());
>     }
> 

---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]

Reply via email to