    001    /* $Id: SetNextRule.java 729106 2008-12-23 20:48:09Z rahul $
    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    }