001/* $Id: SetNextRule.java 992060 2010-09-02 19:09:47Z simonetripodi $
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
020package org.apache.commons.digester;
021
022
023import 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
045public 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    @Deprecated
063    public SetNextRule(Digester digester, String methodName) {
064
065        this(methodName);
066
067    }
068
069
070    /**
071     * Construct a "set next" rule with the specified method name.
072     *
073     * @param digester The associated Digester
074     * @param methodName Method name of the parent method to call
075     * @param paramType Java class of the parent method's argument
076     *  (if you wish to use a primitive type, specify the corresonding
077     *  Java wrapper class instead, such as <code>java.lang.Boolean</code>
078     *  for a <code>boolean</code> parameter)
079     *
080     * @deprecated The digester instance is now set in the {@link Digester#addRule} method. 
081     * Use {@link #SetNextRule(String methodName,String paramType)} instead.
082     */
083    @Deprecated
084    public SetNextRule(Digester digester, String methodName,
085                       String paramType) {
086
087        this(methodName, paramType);
088
089    }
090
091    /**
092     * Construct a "set next" rule with the specified method name.  The
093     * method's argument type is assumed to be the class of the
094     * child object.
095     *
096     * @param methodName Method name of the parent method to call
097     */
098    public SetNextRule(String methodName) {
099
100        this(methodName, null);
101
102    }
103
104
105    /**
106     * Construct a "set next" rule with the specified method name.
107     *
108     * @param methodName Method name of the parent method to call
109     * @param paramType Java class of the parent method's argument
110     *  (if you wish to use a primitive type, specify the corresonding
111     *  Java wrapper class instead, such as <code>java.lang.Boolean</code>
112     *  for a <code>boolean</code> parameter)
113     */
114    public SetNextRule(String methodName,
115                       String paramType) {
116
117        this.methodName = methodName;
118        this.paramType = paramType;
119
120    }
121
122
123    // ----------------------------------------------------- Instance Variables
124
125
126    /**
127     * The method name to call on the parent object.
128     */
129    protected String methodName = null;
130
131
132    /**
133     * The Java class name of the parameter type expected by the method.
134     */
135    protected String paramType = null;
136
137    /**
138     * Should we use exact matching. Default is no.
139     */
140    protected boolean useExactMatch = false;
141
142    // --------------------------------------------------------- Public Methods
143
144
145    /**
146     * <p>Is exact matching being used.</p>
147     *
148     * <p>This rule uses <code>org.apache.commons.beanutils.MethodUtils</code> 
149     * to introspect the relevent objects so that the right method can be called.
150     * Originally, <code>MethodUtils.invokeExactMethod</code> was used.
151     * This matches methods very strictly 
152     * and so may not find a matching method when one exists.
153     * This is still the behaviour when exact matching is enabled.</p>
154     *
155     * <p>When exact matching is disabled, <code>MethodUtils.invokeMethod</code> is used.
156     * This method finds more methods but is less precise when there are several methods 
157     * with correct signatures.
158     * So, if you want to choose an exact signature you might need to enable this property.</p>
159     *
160     * <p>The default setting is to disable exact matches.</p>
161     *
162     * @return true iff exact matching is enabled
163     * @since Digester Release 1.1.1
164     */
165    public boolean isExactMatch() {
166    
167        return useExactMatch;
168    }
169    
170    /**
171     * <p>Set whether exact matching is enabled.</p>
172     *
173     * <p>See {@link #isExactMatch()}.</p>
174     *
175     * @param useExactMatch should this rule use exact method matching
176     * @since Digester Release 1.1.1
177     */    
178    public void setExactMatch(boolean useExactMatch) {
179
180        this.useExactMatch = useExactMatch;
181    }
182
183    /**
184     * Process the end of this element.
185     */
186    @Override
187    public void end() throws Exception {
188
189        // Identify the objects to be used
190        Object child = digester.peek(0);
191        Object parent = digester.peek(1);
192        if (digester.log.isDebugEnabled()) {
193            if (parent == null) {
194                digester.log.debug("[SetNextRule]{" + digester.match +
195                        "} Call [NULL PARENT]." +
196                        methodName + "(" + child + ")");
197            } else {
198                digester.log.debug("[SetNextRule]{" + digester.match +
199                        "} Call " + parent.getClass().getName() + "." +
200                        methodName + "(" + child + ")");
201            }
202        }
203
204        // Call the specified method
205        Class<?> paramTypes[] = new Class<?>[1];
206        if (paramType != null) {
207            paramTypes[0] =
208                    digester.getClassLoader().loadClass(paramType);
209        } else {
210            paramTypes[0] = child.getClass();
211        }
212        
213        if (useExactMatch) {
214        
215            MethodUtils.invokeExactMethod(parent, methodName,
216                new Object[]{ child }, paramTypes);
217                
218        } else {
219        
220            MethodUtils.invokeMethod(parent, methodName,
221                new Object[]{ child }, paramTypes);
222        
223        }
224    }
225
226
227    /**
228     * Render a printable version of this Rule.
229     */
230    @Override
231    public String toString() {
232
233        StringBuffer sb = new StringBuffer("SetNextRule[");
234        sb.append("methodName=");
235        sb.append(methodName);
236        sb.append(", paramType=");
237        sb.append(paramType);
238        sb.append("]");
239        return (sb.toString());
240
241    }
242
243
244}