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    
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        @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    }