001    /* $Id: SetNextRule.java 471661 2006-11-06 08:09:25Z skitching $
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    
019    
020    package org.apache.commons.digester;
021    
022    
023    import org.apache.commons.beanutils.MethodUtils;
024    
025    
026    /**
027     * <p>Rule implementation that calls a method on the (top-1) (parent)
028     * object, passing the top object (child) as an argument.  It is
029     * commonly used to establish parent-child relationships.</p>
030     *
031     * <p>This rule now supports more flexible method matching by default.
032     * It is possible that this may break (some) code 
033     * written against release 1.1.1 or earlier.
034     * See {@link #isExactMatch()} for more details.</p> 
035     *
036     * <p>Note that while CallMethodRule uses commons-beanutils' data-conversion
037     * functionality (ConvertUtils class) to convert parameter values into
038     * the appropriate type for the parameter to the called method, this
039     * rule does not. Needing to use ConvertUtils functionality when building
040     * parent-child relationships is expected to be very rare; however if you 
041     * do need this then instead of using this rule, create a CallMethodRule
042     * specifying targetOffset of 1 in the constructor.</p>
043     */
044    
045    public class SetNextRule extends Rule {
046    
047    
048        // ----------------------------------------------------------- Constructors
049    
050    
051        /**
052         * Construct a "set next" rule with the specified method name.  The
053         * method's argument type is assumed to be the class of the
054         * child object.
055         *
056         * @param digester The associated Digester
057         * @param methodName Method name of the parent method to call
058         *
059         * @deprecated The digester instance is now set in the {@link Digester#addRule} method. 
060         * Use {@link #SetNextRule(String methodName)} instead.
061         */
062        public SetNextRule(Digester digester, String methodName) {
063    
064            this(methodName);
065    
066        }
067    
068    
069        /**
070         * Construct a "set next" rule with the specified method name.
071         *
072         * @param digester The associated Digester
073         * @param methodName Method name of the parent method to call
074         * @param paramType Java class of the parent method's argument
075         *  (if you wish to use a primitive type, specify the corresonding
076         *  Java wrapper class instead, such as <code>java.lang.Boolean</code>
077         *  for a <code>boolean</code> parameter)
078         *
079         * @deprecated The digester instance is now set in the {@link Digester#addRule} method. 
080         * Use {@link #SetNextRule(String methodName,String paramType)} instead.
081         */
082        public SetNextRule(Digester digester, String methodName,
083                           String paramType) {
084    
085            this(methodName, paramType);
086    
087        }
088    
089        /**
090         * Construct a "set next" rule with the specified method name.  The
091         * method's argument type is assumed to be the class of the
092         * child object.
093         *
094         * @param methodName Method name of the parent method to call
095         */
096        public SetNextRule(String methodName) {
097    
098            this(methodName, null);
099    
100        }
101    
102    
103        /**
104         * Construct a "set next" rule with the specified method name.
105         *
106         * @param methodName Method name of the parent method to call
107         * @param paramType Java class of the parent method's argument
108         *  (if you wish to use a primitive type, specify the corresonding
109         *  Java wrapper class instead, such as <code>java.lang.Boolean</code>
110         *  for a <code>boolean</code> parameter)
111         */
112        public SetNextRule(String methodName,
113                           String paramType) {
114    
115            this.methodName = methodName;
116            this.paramType = paramType;
117    
118        }
119    
120    
121        // ----------------------------------------------------- Instance Variables
122    
123    
124        /**
125         * The method name to call on the parent object.
126         */
127        protected String methodName = null;
128    
129    
130        /**
131         * The Java class name of the parameter type expected by the method.
132         */
133        protected String paramType = null;
134    
135        /**
136         * Should we use exact matching. Default is no.
137         */
138        protected boolean useExactMatch = false;
139    
140        // --------------------------------------------------------- Public Methods
141    
142    
143        /**
144         * <p>Is exact matching being used.</p>
145         *
146         * <p>This rule uses <code>org.apache.commons.beanutils.MethodUtils</code> 
147         * to introspect the relevent objects so that the right method can be called.
148         * Originally, <code>MethodUtils.invokeExactMethod</code> was used.
149         * This matches methods very strictly 
150         * and so may not find a matching method when one exists.
151         * This is still the behaviour when exact matching is enabled.</p>
152         *
153         * <p>When exact matching is disabled, <code>MethodUtils.invokeMethod</code> is used.
154         * This method finds more methods but is less precise when there are several methods 
155         * with correct signatures.
156         * So, if you want to choose an exact signature you might need to enable this property.</p>
157         *
158         * <p>The default setting is to disable exact matches.</p>
159         *
160         * @return true iff exact matching is enabled
161         * @since Digester Release 1.1.1
162         */
163        public boolean isExactMatch() {
164        
165            return useExactMatch;
166        }
167        
168        /**
169         * <p>Set whether exact matching is enabled.</p>
170         *
171         * <p>See {@link #isExactMatch()}.</p>
172         *
173         * @param useExactMatch should this rule use exact method matching
174         * @since Digester Release 1.1.1
175         */    
176        public void setExactMatch(boolean useExactMatch) {
177    
178            this.useExactMatch = useExactMatch;
179        }
180    
181        /**
182         * Process the end of this element.
183         */
184        public void end() throws Exception {
185    
186            // Identify the objects to be used
187            Object child = digester.peek(0);
188            Object parent = digester.peek(1);
189            if (digester.log.isDebugEnabled()) {
190                if (parent == null) {
191                    digester.log.debug("[SetNextRule]{" + digester.match +
192                            "} Call [NULL PARENT]." +
193                            methodName + "(" + child + ")");
194                } else {
195                    digester.log.debug("[SetNextRule]{" + digester.match +
196                            "} Call " + parent.getClass().getName() + "." +
197                            methodName + "(" + child + ")");
198                }
199            }
200    
201            // Call the specified method
202            Class paramTypes[] = new Class[1];
203            if (paramType != null) {
204                paramTypes[0] =
205                        digester.getClassLoader().loadClass(paramType);
206            } else {
207                paramTypes[0] = child.getClass();
208            }
209            
210            if (useExactMatch) {
211            
212                MethodUtils.invokeExactMethod(parent, methodName,
213                    new Object[]{ child }, paramTypes);
214                    
215            } else {
216            
217                MethodUtils.invokeMethod(parent, methodName,
218                    new Object[]{ child }, paramTypes);
219            
220            }
221        }
222    
223    
224        /**
225         * Render a printable version of this Rule.
226         */
227        public String toString() {
228    
229            StringBuffer sb = new StringBuffer("SetNextRule[");
230            sb.append("methodName=");
231            sb.append(methodName);
232            sb.append(", paramType=");
233            sb.append(paramType);
234            sb.append("]");
235            return (sb.toString());
236    
237        }
238    
239    
240    }