KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > tomcat > util > digester > CallMethodRule


1 /* $Id: CallMethodRule.java 467222 2006-10-24 03:17:11Z markt $
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
20 package org.apache.tomcat.util.digester;
21
22
23 import org.apache.tomcat.util.IntrospectionUtils;
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
42  * <a HREF="http://jakarta.apache.org/commons/beanutils/apidocs/org/apache/commons/beanutils/MethodUtils.html">
43  * org.apache.commons.beanutils.MethodUtils#invokeMethod
44  * </a> by default.
45  * This increases the kinds of methods successfully and allows primitives
46  * to be matched by passing in wrapper classes.
47  * There are rare cases when org.apache.commons.beanutils.MethodUtils#invokeExactMethod
48  * (the old default) is required.
49  * This method is much stricter in its reflection.
50  * Setting the <code>UseExactMatch</code> to true reverts to the use of this
51  * method.</p>
52  *
53  * <p>Note that the target method is invoked when the <i>end</i> of
54  * the tag the CallMethodRule fired on is encountered, <i>not</i> when the
55  * last parameter becomes available. This implies that rules which fire on
56  * tags nested within the one associated with the CallMethodRule will
57  * fire before the CallMethodRule invokes the target method. This behaviour is
58  * not configurable. </p>
59  *
60  * <p>Note also that if a CallMethodRule is expecting exactly one parameter
61  * and that parameter is not available (eg CallParamRule is used with an
62  * attribute name but the attribute does not exist) then the method will
63  * not be invoked. If a CallMethodRule is expecting more than one parameter,
64  * then it is always invoked, regardless of whether the parameters were
65  * available or not (missing parameters are passed as null values).</p>
66  */

67
68 public class CallMethodRule extends Rule {
69
70     // ----------------------------------------------------------- Constructors
71

72
73     /**
74      * Construct a "call method" rule with the specified method name. The
75      * parameter types (if any) default to java.lang.String.
76      *
77      * @param digester The associated Digester
78      * @param methodName Method name of the parent method to call
79      * @param paramCount The number of parameters to collect, or
80      * zero for a single argument from the body of this element.
81      *
82      *
83      * @deprecated The digester instance is now set in the {@link Digester#addRule} method.
84      * Use {@link #CallMethodRule(String methodName,int paramCount)} instead.
85      */

86     public CallMethodRule(Digester digester, String JavaDoc methodName,
87                           int paramCount) {
88
89         this(methodName, paramCount);
90
91     }
92
93
94     /**
95      * Construct a "call method" rule with the specified method name.
96      *
97      * @param digester The associated Digester
98      * @param methodName Method name of the parent method to call
99      * @param paramCount The number of parameters to collect, or
100      * zero for a single argument from the body of ths element
101      * @param paramTypes The Java class names of the arguments
102      * (if you wish to use a primitive type, specify the corresonding
103      * Java wrapper class instead, such as <code>java.lang.Boolean</code>
104      * for a <code>boolean</code> parameter)
105      *
106      * @deprecated The digester instance is now set in the {@link Digester#addRule} method.
107      * Use {@link #CallMethodRule(String methodName,int paramCount, String [] paramTypes)} instead.
108      */

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

133     public CallMethodRule(Digester digester, String JavaDoc methodName,
134                           int paramCount, Class JavaDoc paramTypes[]) {
135
136         this(methodName, paramCount, paramTypes);
137     }
138
139
140     /**
141      * Construct a "call method" rule with the specified method name. The
142      * parameter types (if any) default to java.lang.String.
143      *
144      * @param methodName Method name of the parent method to call
145      * @param paramCount The number of parameters to collect, or
146      * zero for a single argument from the body of this element.
147      */

148     public CallMethodRule(String JavaDoc methodName,
149                           int paramCount) {
150         this(0, methodName, paramCount);
151     }
152
153     /**
154      * Construct a "call method" rule with the specified method name. The
155      * parameter types (if any) default to java.lang.String.
156      *
157      * @param targetOffset location of the target object. Positive numbers are
158      * relative to the top of the digester object stack. Negative numbers
159      * are relative to the bottom of the stack. Zero implies the top
160      * object on the stack.
161      * @param methodName Method name of the parent method to call
162      * @param paramCount The number of parameters to collect, or
163      * zero for a single argument from the body of this element.
164      */

165     public CallMethodRule(int targetOffset,
166                           String JavaDoc methodName,
167                           int paramCount) {
168
169         this.targetOffset = targetOffset;
170         this.methodName = methodName;
171         this.paramCount = paramCount;
172         if (paramCount == 0) {
173             this.paramTypes = new Class JavaDoc[] { String JavaDoc.class };
174         } else {
175             this.paramTypes = new Class JavaDoc[paramCount];
176             for (int i = 0; i < this.paramTypes.length; i++) {
177                 this.paramTypes[i] = String JavaDoc.class;
178             }
179         }
180
181     }
182
183     /**
184      * Construct a "call method" rule with the specified method name.
185      * The method should accept no parameters.
186      *
187      * @param methodName Method name of the parent method to call
188      */

189     public CallMethodRule(String JavaDoc methodName) {
190     
191         this(0, methodName, 0, (Class JavaDoc[]) null);
192     
193     }
194     
195
196     /**
197      * Construct a "call method" rule with the specified method name.
198      * The method should accept no parameters.
199      *
200      * @param targetOffset location of the target object. Positive numbers are
201      * relative to the top of the digester object stack. Negative numbers
202      * are relative to the bottom of the stack. Zero implies the top
203      * object on the stack.
204      * @param methodName Method name of the parent method to call
205      */

206     public CallMethodRule(int targetOffset, String JavaDoc methodName) {
207     
208         this(targetOffset, methodName, 0, (Class JavaDoc[]) null);
209     
210     }
211     
212
213     /**
214      * Construct a "call method" rule with the specified method name and
215      * parameter types. If <code>paramCount</code> is set to zero the rule
216      * will use the body of this element as the single argument of the
217      * method, unless <code>paramTypes</code> is null or empty, in this
218      * case the rule will call the specified method with no arguments.
219      *
220      * @param methodName Method name of the parent method to call
221      * @param paramCount The number of parameters to collect, or
222      * zero for a single argument from the body of ths element
223      * @param paramTypes The Java class names of the arguments
224      * (if you wish to use a primitive type, specify the corresonding
225      * Java wrapper class instead, such as <code>java.lang.Boolean</code>
226      * for a <code>boolean</code> parameter)
227      */

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

254     public CallMethodRule( int targetOffset,
255                             String JavaDoc methodName,
256                             int paramCount,
257                             String JavaDoc paramTypes[]) {
258
259         this.targetOffset = targetOffset;
260         this.methodName = methodName;
261         this.paramCount = paramCount;
262         if (paramTypes == null) {
263             this.paramTypes = new Class JavaDoc[paramCount];
264             for (int i = 0; i < this.paramTypes.length; i++) {
265                 this.paramTypes[i] = "abc".getClass();
266             }
267         } else {
268             // copy the parameter class names into an array
269
// the classes will be loaded when the digester is set
270
this.paramClassNames = new String JavaDoc[paramTypes.length];
271             for (int i = 0; i < this.paramClassNames.length; i++) {
272                 this.paramClassNames[i] = paramTypes[i];
273             }
274         }
275
276     }
277
278
279     /**
280      * Construct a "call method" rule with the specified method name and
281      * parameter types. If <code>paramCount</code> is set to zero the rule
282      * will use the body of this element as the single argument of the
283      * method, unless <code>paramTypes</code> is null or empty, in this
284      * case the rule will call the specified method with no arguments.
285      *
286      * @param methodName Method name of the parent method to call
287      * @param paramCount The number of parameters to collect, or
288      * zero for a single argument from the body of ths element
289      * @param paramTypes The Java classes that represent the
290      * parameter types of the method arguments
291      * (if you wish to use a primitive type, specify the corresonding
292      * Java wrapper class instead, such as <code>java.lang.Boolean.TYPE</code>
293      * for a <code>boolean</code> parameter)
294      */

295     public CallMethodRule(
296                             String JavaDoc methodName,
297                             int paramCount,
298                             Class JavaDoc paramTypes[]) {
299         this(0, methodName, paramCount, paramTypes);
300     }
301
302     /**
303      * Construct a "call method" rule with the specified method name and
304      * parameter types. If <code>paramCount</code> is set to zero the rule
305      * will use the body of this element as the single argument of the
306      * method, unless <code>paramTypes</code> is null or empty, in this
307      * case the rule will call the specified method with no arguments.
308      *
309      * @param targetOffset location of the target object. Positive numbers are
310      * relative to the top of the digester object stack. Negative numbers
311      * are relative to the bottom of the stack. Zero implies the top
312      * object on the stack.
313      * @param methodName Method name of the parent method to call
314      * @param paramCount The number of parameters to collect, or
315      * zero for a single argument from the body of ths element
316      * @param paramTypes The Java classes that represent the
317      * parameter types of the method arguments
318      * (if you wish to use a primitive type, specify the corresonding
319      * Java wrapper class instead, such as <code>java.lang.Boolean.TYPE</code>
320      * for a <code>boolean</code> parameter)
321      */

322     public CallMethodRule( int targetOffset,
323                             String JavaDoc methodName,
324                             int paramCount,
325                             Class JavaDoc paramTypes[]) {
326
327         this.targetOffset = targetOffset;
328         this.methodName = methodName;
329         this.paramCount = paramCount;
330         if (paramTypes == null) {
331             this.paramTypes = new Class JavaDoc[paramCount];
332             for (int i = 0; i < this.paramTypes.length; i++) {
333                 this.paramTypes[i] = "abc".getClass();
334             }
335         } else {
336             this.paramTypes = new Class JavaDoc[paramTypes.length];
337             for (int i = 0; i < this.paramTypes.length; i++) {
338                 this.paramTypes[i] = paramTypes[i];
339             }
340         }
341
342     }
343
344
345     // ----------------------------------------------------- Instance Variables
346

347
348     /**
349      * The body text collected from this element.
350      */

351     protected String JavaDoc bodyText = null;
352
353
354     /**
355      * location of the target object for the call, relative to the
356      * top of the digester object stack. The default value of zero
357      * means the target object is the one on top of the stack.
358      */

359     protected int targetOffset = 0;
360
361     /**
362      * The method name to call on the parent object.
363      */

364     protected String JavaDoc methodName = null;
365
366
367     /**
368      * The number of parameters to collect from <code>MethodParam</code> rules.
369      * If this value is zero, a single parameter will be collected from the
370      * body of this element.
371      */

372     protected int paramCount = 0;
373
374
375     /**
376      * The parameter types of the parameters to be collected.
377      */

378     protected Class JavaDoc paramTypes[] = null;
379
380     /**
381      * The names of the classes of the parameters to be collected.
382      * This attribute allows creation of the classes to be postponed until the digester is set.
383      */

384     protected String JavaDoc paramClassNames[] = null;
385     
386     /**
387      * Should <code>MethodUtils.invokeExactMethod</code> be used for reflection.
388      */

389     protected boolean useExactMatch = false;
390     
391     // --------------------------------------------------------- Public Methods
392

393     /**
394      * Should <code>MethodUtils.invokeExactMethod</code>
395      * be used for the reflection.
396      */

397     public boolean getUseExactMatch() {
398         return useExactMatch;
399     }
400     
401     /**
402      * Set whether <code>MethodUtils.invokeExactMethod</code>
403      * should be used for the reflection.
404      */

405     public void setUseExactMatch(boolean useExactMatch)
406     {
407         this.useExactMatch = useExactMatch;
408     }
409
410     /**
411      * Set the associated digester.
412      * If needed, this class loads the parameter classes from their names.
413      */

414     public void setDigester(Digester digester)
415     {
416         // call superclass
417
super.setDigester(digester);
418         // if necessary, load parameter classes
419
if (this.paramClassNames != null) {
420             this.paramTypes = new Class JavaDoc[paramClassNames.length];
421             for (int i = 0; i < this.paramClassNames.length; i++) {
422                 try {
423                     this.paramTypes[i] =
424                             digester.getClassLoader().loadClass(this.paramClassNames[i]);
425                 } catch (ClassNotFoundException JavaDoc e) {
426                     // use the digester log
427
digester.getLogger().error("(CallMethodRule) Cannot load class " + this.paramClassNames[i], e);
428                     this.paramTypes[i] = null; // Will cause NPE later
429
}
430             }
431         }
432     }
433
434     /**
435      * Process the start of this element.
436      *
437      * @param attributes The attribute list for this element
438      */

439     public void begin(Attributes JavaDoc attributes) throws Exception JavaDoc {
440
441         // Push an array to capture the parameter values if necessary
442
if (paramCount > 0) {
443             Object JavaDoc parameters[] = new Object JavaDoc[paramCount];
444             for (int i = 0; i < parameters.length; i++) {
445                 parameters[i] = null;
446             }
447             digester.pushParams(parameters);
448         }
449
450     }
451
452
453     /**
454      * Process the body text of this element.
455      *
456      * @param bodyText The body text of this element
457      */

458     public void body(String JavaDoc bodyText) throws Exception JavaDoc {
459
460         if (paramCount == 0) {
461             this.bodyText = bodyText.trim();
462         }
463
464     }
465
466
467     /**
468      * Process the end of this element.
469      */

470     public void end() throws Exception JavaDoc {
471
472         // Retrieve or construct the parameter values array
473
Object JavaDoc parameters[] = null;
474         if (paramCount > 0) {
475
476             parameters = (Object JavaDoc[]) digester.popParams();
477             
478             if (digester.log.isTraceEnabled()) {
479                 for (int i=0,size=parameters.length;i<size;i++) {
480                     digester.log.trace("[CallMethodRule](" + i + ")" + parameters[i]) ;
481                 }
482             }
483             
484             // In the case where the parameter for the method
485
// is taken from an attribute, and that attribute
486
// isn't actually defined in the source XML file,
487
// skip the method call
488
if (paramCount == 1 && parameters[0] == null) {
489                 return;
490             }
491
492         } else if (paramTypes != null && paramTypes.length != 0) {
493
494             // In the case where the parameter for the method
495
// is taken from the body text, but there is no
496
// body text included in the source XML file,
497
// skip the method call
498
if (bodyText == null) {
499                 return;
500             }
501
502             parameters = new Object JavaDoc[1];
503             parameters[0] = bodyText;
504             if (paramTypes.length == 0) {
505                 paramTypes = new Class JavaDoc[1];
506                 paramTypes[0] = "abc".getClass();
507             }
508
509         }
510
511         // Construct the parameter values array we will need
512
// We only do the conversion if the param value is a String and
513
// the specified paramType is not String.
514
Object JavaDoc paramValues[] = new Object JavaDoc[paramTypes.length];
515         for (int i = 0; i < paramTypes.length; i++) {
516             // convert nulls and convert stringy parameters
517
// for non-stringy param types
518
if(
519                 parameters[i] == null ||
520                  (parameters[i] instanceof String JavaDoc &&
521                    !String JavaDoc.class.isAssignableFrom(paramTypes[i]))) {
522                 
523                 paramValues[i] =
524                         IntrospectionUtils.convert((String JavaDoc) parameters[i], paramTypes[i]);
525             } else {
526                 paramValues[i] = parameters[i];
527             }
528         }
529
530         // Determine the target object for the method call
531
Object JavaDoc target;
532         if (targetOffset >= 0) {
533             target = digester.peek(targetOffset);
534         } else {
535             target = digester.peek( digester.getCount() + targetOffset );
536         }
537         
538         if (target == null) {
539             StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
540             sb.append("[CallMethodRule]{");
541             sb.append(digester.match);
542             sb.append("} Call target is null (");
543             sb.append("targetOffset=");
544             sb.append(targetOffset);
545             sb.append(",stackdepth=");
546             sb.append(digester.getCount());
547             sb.append(")");
548             throw new org.xml.sax.SAXException JavaDoc(sb.toString());
549         }
550         
551         // Invoke the required method on the top object
552
if (digester.log.isDebugEnabled()) {
553             StringBuffer JavaDoc sb = new StringBuffer JavaDoc("[CallMethodRule]{");
554             sb.append(digester.match);
555             sb.append("} Call ");
556             sb.append(target.getClass().getName());
557             sb.append(".");
558             sb.append(methodName);
559             sb.append("(");
560             for (int i = 0; i < paramValues.length; i++) {
561                 if (i > 0) {
562                     sb.append(",");
563                 }
564                 if (paramValues[i] == null) {
565                     sb.append("null");
566                 } else {
567                     sb.append(paramValues[i].toString());
568                 }
569                 sb.append("/");
570                 if (paramTypes[i] == null) {
571                     sb.append("null");
572                 } else {
573                     sb.append(paramTypes[i].getName());
574                 }
575             }
576             sb.append(")");
577             digester.log.debug(sb.toString());
578         }
579         Object JavaDoc result = IntrospectionUtils.callMethodN(target, methodName,
580                 paramValues, paramTypes);
581         processMethodCallResult(result);
582     }
583
584
585     /**
586      * Clean up after parsing is complete.
587      */

588     public void finish() throws Exception JavaDoc {
589
590         bodyText = null;
591
592     }
593
594     /**
595      * Subclasses may override this method to perform additional processing of the
596      * invoked method's result.
597      *
598      * @param result the Object returned by the method invoked, possibly null
599      */

600     protected void processMethodCallResult(Object JavaDoc result) {
601         // do nothing
602
}
603
604     /**
605      * Render a printable version of this Rule.
606      */

607     public String JavaDoc toString() {
608
609         StringBuffer JavaDoc sb = new StringBuffer JavaDoc("CallMethodRule[");
610         sb.append("methodName=");
611         sb.append(methodName);
612         sb.append(", paramCount=");
613         sb.append(paramCount);
614         sb.append(", paramTypes={");
615         if (paramTypes != null) {
616             for (int i = 0; i < paramTypes.length; i++) {
617                 if (i > 0) {
618                     sb.append(", ");
619                 }
620                 sb.append(paramTypes[i].getName());
621             }
622         }
623         sb.append("}");
624         sb.append("]");
625         return (sb.toString());
626
627     }
628
629
630 }
631
Popular Tags