KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > commons > digester > CallMethodRule


1 /* $Id: CallMethodRule.java 190341 2005-06-13 04:01:45Z skitching $
2  *
3  * Copyright 2001-2004 The Apache Software Foundation.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  * http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */

17
18
19 package org.apache.commons.digester;
20
21
22 import org.apache.commons.beanutils.ConvertUtils;
23 import org.apache.commons.beanutils.MethodUtils;
24 import org.xml.sax.Attributes JavaDoc;
25
26
27 /**
28  * <p>Rule implementation that calls a method on an object on the stack
29  * (normally the top/parent object), passing arguments collected from
30  * subsequent <code>CallParamRule</code> rules or from the body of this
31  * element. </p>
32  *
33  * <p>By using {@link #CallMethodRule(String methodName)}
34  * a method call can be made to a method which accepts no
35  * arguments.</p>
36  *
37  * <p>Incompatible method parameter types are converted
38  * using <code>org.apache.commons.beanutils.ConvertUtils</code>.
39  * </p>
40  *
41  * <p>This rule now uses {@link MethodUtils#invokeMethod} by default.
42  * This increases the kinds of methods successfully and allows primitives
43  * to be matched by passing in wrapper classes.
44  * There are rare cases when {@link MethodUtils#invokeExactMethod}
45  * (the old default) is required.
46  * This method is much stricter in it's reflection.
47  * Setting the <code>UseExactMatch</code> to true reverts to the use of this
48  * method.</p>
49  *
50  * <p>Note that the target method is invoked when the <i>end</i> of
51  * the tag the CallMethodRule fired on is encountered, <i>not</i> when the
52  * last parameter becomes available. This implies that rules which fire on
53  * tags nested within the one associated with the CallMethodRule will
54  * fire before the CallMethodRule invokes the target method. This behaviour is
55  * not configurable. </p>
56  *
57  * <p>Note also that if a CallMethodRule is expecting exactly one parameter
58  * and that parameter is not available (eg CallParamRule is used with an
59  * attribute name but the attribute does not exist) then the method will
60  * not be invoked. If a CallMethodRule is expecting more than one parameter,
61  * then it is always invoked, regardless of whether the parameters were
62  * available or not; missing parameters are converted to the appropriate target
63  * type by calling ConvertUtils.convert. Note that the default ConvertUtils
64  * converters for the String type returns a null when passed a null, meaning
65  * that CallMethodRule will passed null for all String parameters for which
66  * there is no parameter info available from the XML. However parameters of
67  * type Float and Integer will be passed a real object containing a zero value
68  * as that is the output of the default ConvertUtils converters for those
69  * types when passed a null. You can register custom converters to change
70  * this behaviour; see the beautils library documentation for more info.</p>
71  *
72  * <p>Note that when a constructor is used with paramCount=0, indicating that
73  * the body of the element is to be passed to the target method, an empty
74  * element will cause an <i>empty string</i> to be passed to the target method,
75  * not null. And if automatic type conversion is being applied (ie if the
76  * target function takes something other than a string as a parameter) then
77  * the conversion will fail if the converter class does not accept an empty
78  * string as valid input.</p>
79  *
80  * <p>CallMethodRule has a design flaw which can cause it to fail under
81  * certain rule configurations. All CallMethodRule instances share a single
82  * parameter stack, and all CallParamRule instances simply store their data
83  * into the parameter-info structure that is on the top of the stack. This
84  * means that two CallMethodRule instances cannot be associated with the
85  * same pattern without getting scrambled parameter data. This same issue
86  * also applies when a CallMethodRule matches some element X, a different
87  * CallMethodRule matches a child element Y and some of the CallParamRules
88  * associated with the first CallMethodRule match element Y or one of its
89  * child elements. This issue has been present since the very first release
90  * of Digester. Note, however, that this configuration of CallMethodRule
91  * instances is not commonly required.</p>
92  */

93
94 public class CallMethodRule extends Rule {
95
96     // ----------------------------------------------------------- Constructors
97

98     /**
99      * Construct a "call method" rule with the specified method name. The
100      * parameter types (if any) default to java.lang.String.
101      *
102      * @param digester The associated Digester
103      * @param methodName Method name of the parent method to call
104      * @param paramCount The number of parameters to collect, or
105      * zero for a single argument from the body of this element.
106      *
107      *
108      * @deprecated The digester instance is now set in the {@link Digester#addRule} method.
109      * Use {@link #CallMethodRule(String methodName,int paramCount)} instead.
110      */

111     public CallMethodRule(Digester digester, String JavaDoc methodName,
112                           int paramCount) {
113
114         this(methodName, paramCount);
115
116     }
117
118
119     /**
120      * Construct a "call method" rule with the specified method name.
121      *
122      * @param digester The associated Digester
123      * @param methodName Method name of the parent method to call
124      * @param paramCount The number of parameters to collect, or
125      * zero for a single argument from the body of ths element
126      * @param paramTypes The Java class names of the arguments
127      * (if you wish to use a primitive type, specify the corresonding
128      * Java wrapper class instead, such as <code>java.lang.Boolean</code>
129      * for a <code>boolean</code> parameter)
130      *
131      * @deprecated The digester instance is now set in the {@link Digester#addRule} method.
132      * Use {@link #CallMethodRule(String methodName,int paramCount, String [] paramTypes)} instead.
133      */

134     public CallMethodRule(Digester digester, String JavaDoc methodName,
135                           int paramCount, String JavaDoc paramTypes[]) {
136
137         this(methodName, paramCount, paramTypes);
138
139     }
140
141
142     /**
143      * Construct a "call method" rule with the specified method name.
144      *
145      * @param digester The associated Digester
146      * @param methodName Method name of the parent method to call
147      * @param paramCount The number of parameters to collect, or
148      * zero for a single argument from the body of ths element
149      * @param paramTypes The Java classes that represent the
150      * parameter types of the method arguments
151      * (if you wish to use a primitive type, specify the corresonding
152      * Java wrapper class instead, such as <code>java.lang.Boolean.TYPE</code>
153      * for a <code>boolean</code> parameter)
154      *
155      * @deprecated The digester instance is now set in the {@link Digester#addRule} method.
156      * Use {@link #CallMethodRule(String methodName,int paramCount, Class [] paramTypes)} instead.
157      */

158     public CallMethodRule(Digester digester, String JavaDoc methodName,
159                           int paramCount, Class JavaDoc paramTypes[]) {
160
161         this(methodName, paramCount, paramTypes);
162     }
163
164
165     /**
166      * Construct a "call method" rule with the specified method name. The
167      * parameter types (if any) default to java.lang.String.
168      *
169      * @param methodName Method name of the parent method to call
170      * @param paramCount The number of parameters to collect, or
171      * zero for a single argument from the body of this element.
172      */

173     public CallMethodRule(String JavaDoc methodName,
174                           int paramCount) {
175         this(0, methodName, paramCount);
176     }
177
178     /**
179      * Construct a "call method" rule with the specified method name. The
180      * parameter types (if any) default to java.lang.String.
181      *
182      * @param targetOffset location of the target object. Positive numbers are
183      * relative to the top of the digester object stack. Negative numbers
184      * are relative to the bottom of the stack. Zero implies the top
185      * object on the stack.
186      * @param methodName Method name of the parent method to call
187      * @param paramCount The number of parameters to collect, or
188      * zero for a single argument from the body of this element.
189      */

190     public CallMethodRule(int targetOffset,
191                           String JavaDoc methodName,
192                           int paramCount) {
193
194         this.targetOffset = targetOffset;
195         this.methodName = methodName;
196         this.paramCount = paramCount;
197         if (paramCount == 0) {
198             this.paramTypes = new Class JavaDoc[] { String JavaDoc.class };
199         } else {
200             this.paramTypes = new Class JavaDoc[paramCount];
201             for (int i = 0; i < this.paramTypes.length; i++) {
202                 this.paramTypes[i] = String JavaDoc.class;
203             }
204         }
205
206     }
207
208     /**
209      * Construct a "call method" rule with the specified method name.
210      * The method should accept no parameters.
211      *
212      * @param methodName Method name of the parent method to call
213      */

214     public CallMethodRule(String JavaDoc methodName) {
215     
216         this(0, methodName, 0, (Class JavaDoc[]) null);
217     
218     }
219     
220
221     /**
222      * Construct a "call method" rule with the specified method name.
223      * The method should accept no parameters.
224      *
225      * @param targetOffset location of the target object. Positive numbers are
226      * relative to the top of the digester object stack. Negative numbers
227      * are relative to the bottom of the stack. Zero implies the top
228      * object on the stack.
229      * @param methodName Method name of the parent method to call
230      */

231     public CallMethodRule(int targetOffset, String JavaDoc methodName) {
232     
233         this(targetOffset, methodName, 0, (Class JavaDoc[]) null);
234     
235     }
236     
237
238     /**
239      * Construct a "call method" rule with the specified method name and
240      * parameter types. If <code>paramCount</code> is set to zero the rule
241      * will use the body of this element as the single argument of the
242      * method, unless <code>paramTypes</code> is null or empty, in this
243      * case the rule will call the specified method with no arguments.
244      *
245      * @param methodName Method name of the parent method to call
246      * @param paramCount The number of parameters to collect, or
247      * zero for a single argument from the body of ths element
248      * @param paramTypes The Java class names of the arguments
249      * (if you wish to use a primitive type, specify the corresonding
250      * Java wrapper class instead, such as <code>java.lang.Boolean</code>
251      * for a <code>boolean</code> parameter)
252      */

253     public CallMethodRule(
254                             String JavaDoc methodName,
255                             int paramCount,
256                             String JavaDoc paramTypes[]) {
257         this(0, methodName, paramCount, paramTypes);
258     }
259
260     /**
261      * Construct a "call method" rule with the specified method name and
262      * parameter types. If <code>paramCount</code> is set to zero the rule
263      * will use the body of this element as the single argument of the
264      * method, unless <code>paramTypes</code> is null or empty, in this
265      * case the rule will call the specified method with no arguments.
266      *
267      * @param targetOffset location of the target object. Positive numbers are
268      * relative to the top of the digester object stack. Negative numbers
269      * are relative to the bottom of the stack. Zero implies the top
270      * object on the stack.
271      * @param methodName Method name of the parent method to call
272      * @param paramCount The number of parameters to collect, or
273      * zero for a single argument from the body of ths element
274      * @param paramTypes The Java class names of the arguments
275      * (if you wish to use a primitive type, specify the corresonding
276      * Java wrapper class instead, such as <code>java.lang.Boolean</code>
277      * for a <code>boolean</code> parameter)
278      */

279     public CallMethodRule( int targetOffset,
280                             String JavaDoc methodName,
281                             int paramCount,
282                             String JavaDoc paramTypes[]) {
283
284         this.targetOffset = targetOffset;
285         this.methodName = methodName;
286         this.paramCount = paramCount;
287         if (paramTypes == null) {
288             this.paramTypes = new Class JavaDoc[paramCount];
289             for (int i = 0; i < this.paramTypes.length; i++) {
290                 this.paramTypes[i] = String JavaDoc.class;
291             }
292         } else {
293             // copy the parameter class names into an array
294
// the classes will be loaded when the digester is set
295
this.paramClassNames = new String JavaDoc[paramTypes.length];
296             for (int i = 0; i < this.paramClassNames.length; i++) {
297                 this.paramClassNames[i] = paramTypes[i];
298             }
299         }
300
301     }
302
303
304     /**
305      * Construct a "call method" rule with the specified method name and
306      * parameter types. If <code>paramCount</code> is set to zero the rule
307      * will use the body of this element as the single argument of the
308      * method, unless <code>paramTypes</code> is null or empty, in this
309      * case the rule will call the specified method with no arguments.
310      *
311      * @param methodName Method name of the parent method to call
312      * @param paramCount The number of parameters to collect, or
313      * zero for a single argument from the body of ths element
314      * @param paramTypes The Java classes that represent the
315      * parameter types of the method arguments
316      * (if you wish to use a primitive type, specify the corresonding
317      * Java wrapper class instead, such as <code>java.lang.Boolean.TYPE</code>
318      * for a <code>boolean</code> parameter)
319      */

320     public CallMethodRule(
321                             String JavaDoc methodName,
322                             int paramCount,
323                             Class JavaDoc paramTypes[]) {
324         this(0, methodName, paramCount, paramTypes);
325     }
326
327     /**
328      * Construct a "call method" rule with the specified method name and
329      * parameter types. If <code>paramCount</code> is set to zero the rule
330      * will use the body of this element as the single argument of the
331      * method, unless <code>paramTypes</code> is null or empty, in this
332      * case the rule will call the specified method with no arguments.
333      *
334      * @param targetOffset location of the target object. Positive numbers are
335      * relative to the top of the digester object stack. Negative numbers
336      * are relative to the bottom of the stack. Zero implies the top
337      * object on the stack.
338      * @param methodName Method name of the parent method to call
339      * @param paramCount The number of parameters to collect, or
340      * zero for a single argument from the body of ths element
341      * @param paramTypes The Java classes that represent the
342      * parameter types of the method arguments
343      * (if you wish to use a primitive type, specify the corresonding
344      * Java wrapper class instead, such as <code>java.lang.Boolean.TYPE</code>
345      * for a <code>boolean</code> parameter)
346      */

347     public CallMethodRule( int targetOffset,
348                             String JavaDoc methodName,
349                             int paramCount,
350                             Class JavaDoc paramTypes[]) {
351
352         this.targetOffset = targetOffset;
353         this.methodName = methodName;
354         this.paramCount = paramCount;
355         if (paramTypes == null) {
356             this.paramTypes = new Class JavaDoc[paramCount];
357             for (int i = 0; i < this.paramTypes.length; i++) {
358                 this.paramTypes[i] = String JavaDoc.class;
359             }
360         } else {
361             this.paramTypes = new Class JavaDoc[paramTypes.length];
362             for (int i = 0; i < this.paramTypes.length; i++) {
363                 this.paramTypes[i] = paramTypes[i];
364             }
365         }
366
367     }
368
369
370     // ----------------------------------------------------- Instance Variables
371

372
373     /**
374      * The body text collected from this element.
375      */

376     protected String JavaDoc bodyText = null;
377
378
379     /**
380      * location of the target object for the call, relative to the
381      * top of the digester object stack. The default value of zero
382      * means the target object is the one on top of the stack.
383      */

384     private int targetOffset = 0;
385
386     /**
387      * The method name to call on the parent object.
388      */

389     protected String JavaDoc methodName = null;
390
391
392     /**
393      * The number of parameters to collect from <code>MethodParam</code> rules.
394      * If this value is zero, a single parameter will be collected from the
395      * body of this element.
396      */

397     protected int paramCount = 0;
398
399
400     /**
401      * The parameter types of the parameters to be collected.
402      */

403     protected Class JavaDoc paramTypes[] = null;
404
405     /**
406      * The names of the classes of the parameters to be collected.
407      * This attribute allows creation of the classes to be postponed until the digester is set.
408      */

409     private String JavaDoc paramClassNames[] = null;
410     
411     /**
412      * Should <code>MethodUtils.invokeExactMethod</code> be used for reflection.
413      */

414     protected boolean useExactMatch = false;
415     
416     // --------------------------------------------------------- Public Methods
417

418     /**
419      * Should <code>MethodUtils.invokeExactMethod</code>
420      * be used for the reflection.
421      */

422     public boolean getUseExactMatch() {
423         return useExactMatch;
424     }
425     
426     /**
427      * Set whether <code>MethodUtils.invokeExactMethod</code>
428      * should be used for the reflection.
429      */

430     public void setUseExactMatch(boolean useExactMatch)
431     {
432         this.useExactMatch = useExactMatch;
433     }
434
435     /**
436      * Set the associated digester.
437      * If needed, this class loads the parameter classes from their names.
438      */

439     public void setDigester(Digester digester)
440     {
441         // call superclass
442
super.setDigester(digester);
443         // if necessary, load parameter classes
444
if (this.paramClassNames != null) {
445             this.paramTypes = new Class JavaDoc[paramClassNames.length];
446             for (int i = 0; i < this.paramClassNames.length; i++) {
447                 try {
448                     this.paramTypes[i] =
449                             digester.getClassLoader().loadClass(this.paramClassNames[i]);
450                 } catch (ClassNotFoundException JavaDoc e) {
451                     // use the digester log
452
digester.getLogger().error("(CallMethodRule) Cannot load class " + this.paramClassNames[i], e);
453                     this.paramTypes[i] = null; // Will cause NPE later
454
}
455             }
456         }
457     }
458
459     /**
460      * Process the start of this element.
461      *
462      * @param attributes The attribute list for this element
463      */

464     public void begin(Attributes JavaDoc attributes) throws Exception JavaDoc {
465
466         // Push an array to capture the parameter values if necessary
467
if (paramCount > 0) {
468             Object JavaDoc parameters[] = new Object JavaDoc[paramCount];
469             for (int i = 0; i < parameters.length; i++) {
470                 parameters[i] = null;
471             }
472             digester.pushParams(parameters);
473         }
474
475     }
476
477
478     /**
479      * Process the body text of this element.
480      *
481      * @param bodyText The body text of this element
482      */

483     public void body(String JavaDoc bodyText) throws Exception JavaDoc {
484
485         if (paramCount == 0) {
486             this.bodyText = bodyText.trim();
487         }
488
489     }
490
491
492     /**
493      * Process the end of this element.
494      */

495     public void end() throws Exception JavaDoc {
496
497         // Retrieve or construct the parameter values array
498
Object JavaDoc parameters[] = null;
499         if (paramCount > 0) {
500
501             parameters = (Object JavaDoc[]) digester.popParams();
502             
503             if (digester.log.isTraceEnabled()) {
504                 for (int i=0,size=parameters.length;i<size;i++) {
505                     digester.log.trace("[CallMethodRule](" + i + ")" + parameters[i]) ;
506                 }
507             }
508             
509             // In the case where the target method takes a single parameter
510
// and that parameter does not exist (the CallParamRule never
511
// executed or the CallParamRule was intended to set the parameter
512
// from an attribute but the attribute wasn't present etc) then
513
// skip the method call.
514
//
515
// This is useful when a class has a "default" value that should
516
// only be overridden if data is present in the XML. I don't
517
// know why this should only apply to methods taking *one*
518
// parameter, but it always has been so we can't change it now.
519
if (paramCount == 1 && parameters[0] == null) {
520                 return;
521             }
522
523         } else if (paramTypes != null && paramTypes.length != 0) {
524             // Having paramCount == 0 and paramTypes.length == 1 indicates
525
// that we have the special case where the target method has one
526
// parameter being the body text of the current element.
527

528             // There is no body text included in the source XML file,
529
// so skip the method call
530
if (bodyText == null) {
531                 return;
532             }
533
534             parameters = new Object JavaDoc[1];
535             parameters[0] = bodyText;
536             if (paramTypes.length == 0) {
537                 paramTypes = new Class JavaDoc[1];
538                 paramTypes[0] = String JavaDoc.class;
539             }
540
541         } else {
542             // When paramCount is zero and paramTypes.length is zero it
543
// means that we truly are calling a method with no parameters.
544
// Nothing special needs to be done here.
545
;
546         }
547
548         // Construct the parameter values array we will need
549
// We only do the conversion if the param value is a String and
550
// the specified paramType is not String.
551
Object JavaDoc paramValues[] = new Object JavaDoc[paramTypes.length];
552         for (int i = 0; i < paramTypes.length; i++) {
553             // convert nulls and convert stringy parameters
554
// for non-stringy param types
555
if(
556                 parameters[i] == null ||
557                  (parameters[i] instanceof String JavaDoc &&
558                    !String JavaDoc.class.isAssignableFrom(paramTypes[i]))) {
559                 
560                 paramValues[i] =
561                         ConvertUtils.convert((String JavaDoc) parameters[i], paramTypes[i]);
562             } else {
563                 paramValues[i] = parameters[i];
564             }
565         }
566
567         // Determine the target object for the method call
568
Object JavaDoc target;
569         if (targetOffset >= 0) {
570             target = digester.peek(targetOffset);
571         } else {
572             target = digester.peek( digester.getCount() + targetOffset );
573         }
574         
575         if (target == null) {
576             StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
577             sb.append("[CallMethodRule]{");
578             sb.append(digester.match);
579             sb.append("} Call target is null (");
580             sb.append("targetOffset=");
581             sb.append(targetOffset);
582             sb.append(",stackdepth=");
583             sb.append(digester.getCount());
584             sb.append(")");
585             throw new org.xml.sax.SAXException JavaDoc(sb.toString());
586         }
587         
588         // Invoke the required method on the top object
589
if (digester.log.isDebugEnabled()) {
590             StringBuffer JavaDoc sb = new StringBuffer JavaDoc("[CallMethodRule]{");
591             sb.append(digester.match);
592             sb.append("} Call ");
593             sb.append(target.getClass().getName());
594             sb.append(".");
595             sb.append(methodName);
596             sb.append("(");
597             for (int i = 0; i < paramValues.length; i++) {
598                 if (i > 0) {
599                     sb.append(",");
600                 }
601                 if (paramValues[i] == null) {
602                     sb.append("null");
603                 } else {
604                     sb.append(paramValues[i].toString());
605                 }
606                 sb.append("/");
607                 if (paramTypes[i] == null) {
608                     sb.append("null");
609                 } else {
610                     sb.append(paramTypes[i].getName());
611                 }
612             }
613             sb.append(")");
614             digester.log.debug(sb.toString());
615         }
616         
617         Object JavaDoc result = null;
618         if (useExactMatch) {
619             // invoke using exact match
620
result = MethodUtils.invokeExactMethod(target, methodName,
621                 paramValues, paramTypes);
622                 
623         } else {
624             // invoke using fuzzier match
625
result = MethodUtils.invokeMethod(target, methodName,
626                 paramValues, paramTypes);
627         }
628         
629         processMethodCallResult(result);
630     }
631
632
633     /**
634      * Clean up after parsing is complete.
635      */

636     public void finish() throws Exception JavaDoc {
637
638         bodyText = null;
639
640     }
641
642     /**
643      * Subclasses may override this method to perform additional processing of the
644      * invoked method's result.
645      *
646      * @param result the Object returned by the method invoked, possibly null
647      */

648     protected void processMethodCallResult(Object JavaDoc result) {
649         // do nothing
650
}
651
652     /**
653      * Render a printable version of this Rule.
654      */

655     public String JavaDoc toString() {
656
657         StringBuffer JavaDoc sb = new StringBuffer JavaDoc("CallMethodRule[");
658         sb.append("methodName=");
659         sb.append(methodName);
660         sb.append(", paramCount=");
661         sb.append(paramCount);
662         sb.append(", paramTypes={");
663         if (paramTypes != null) {
664             for (int i = 0; i < paramTypes.length; i++) {
665                 if (i > 0) {
666                     sb.append(", ");
667                 }
668                 sb.append(paramTypes[i].getName());
669             }
670         }
671         sb.append("}");
672         sb.append("]");
673         return (sb.toString());
674
675     }
676
677
678 }
679
Popular Tags