001    package org.apache.commons.digester3;
002    
003    /*
004     * Licensed to the Apache Software Foundation (ASF) under one
005     * or more contributor license agreements.  See the NOTICE file
006     * distributed with this work for additional information
007     * regarding copyright ownership.  The ASF licenses this file
008     * to you under the Apache License, Version 2.0 (the
009     * "License"); you may not use this file except in compliance
010     * with the License.  You may obtain a copy of the License at
011     *
012     *   http://www.apache.org/licenses/LICENSE-2.0
013     *
014     * Unless required by applicable law or agreed to in writing,
015     * software distributed under the License is distributed on an
016     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017     * KIND, either express or implied.  See the License for the
018     * specific language governing permissions and limitations
019     * under the License.
020     */
021    
022    import static java.lang.String.format;
023    import static org.apache.commons.beanutils.MethodUtils.invokeExactMethod;
024    import static org.apache.commons.beanutils.MethodUtils.invokeMethod;
025    
026    import org.xml.sax.Attributes;
027    
028    /**
029     * Abstract implementation for {@link org.apache.commons.digester3.SetNextRule},
030     * {@link org.apache.commons.digester3.SetRootRule} and {@link org.apache.commons.digester3.SetTopRule} rules.
031     *
032     * @since 3.0
033     */
034    public abstract class AbstractMethodRule
035        extends Rule
036    {
037    
038        /**
039         * The method name to call on the parent object.
040         */
041        protected String methodName = null;
042    
043        /**
044         * The Java class name of the parameter type expected by the method.
045         */
046        protected String paramTypeName = null;
047    
048        /**
049         * The Java class name of the parameter type expected by the method.
050         */
051        protected Class<?> paramType;
052    
053        /**
054         * Should we use exact matching. Default is no.
055         */
056        protected boolean useExactMatch = false;
057    
058        /**
059         * Should this rule be invoked when {@link #begin(String, String, Attributes)} (true)
060         * or {@link #end(String, String)} (false) methods are invoked, false by default.
061         */
062        protected boolean fireOnBegin = false;
063    
064        /**
065         * Construct a "set next" rule with the specified method name. The method's argument type is assumed to be the class
066         * of the child object.
067         * 
068         * @param methodName Method name of the parent method to call
069         */
070        public AbstractMethodRule( String methodName )
071        {
072            this( methodName, (String) null );
073        }
074    
075        /**
076         * Construct a "set next" rule with the specified method name.
077         * 
078         * @param methodName Method name of the parent method to call
079         * @param paramType Java class of the parent method's argument (if you wish to use a primitive type, specify the
080         *            corresonding Java wrapper class instead, such as <code>java.lang.Boolean</code> for a
081         *            <code>boolean</code> parameter)
082         */
083        public AbstractMethodRule( String methodName, Class<?> paramType )
084        {
085            this( methodName, paramType.getName() );
086            this.paramType = paramType;
087        }
088    
089        /**
090         * Construct a "set next" rule with the specified method name.
091         * 
092         * @param methodName Method name of the parent method to call
093         * @param paramTypeName Java class of the parent method's argument (if you wish to use a primitive type, specify the
094         *            corresonding Java wrapper class instead, such as <code>java.lang.Boolean</code> for a
095         *            <code>boolean</code> parameter)
096         */
097        public AbstractMethodRule( String methodName, String paramTypeName )
098        {
099            this.methodName = methodName;
100            this.paramTypeName = paramTypeName;
101        }
102    
103        /**
104         * <p>
105         * Is exact matching being used.
106         * </p>
107         * <p>
108         * This rule uses <code>org.apache.commons.beanutils.MethodUtils</code> to introspect the relevent objects so that
109         * the right method can be called. Originally, <code>MethodUtils.invokeExactMethod</code> was used. This matches
110         * methods very strictly and so may not find a matching method when one exists. This is still the behaviour when
111         * exact matching is enabled.
112         * </p>
113         * <p>
114         * When exact matching is disabled, <code>MethodUtils.invokeMethod</code> is used. This method finds more methods
115         * but is less precise when there are several methods with correct signatures. So, if you want to choose an exact
116         * signature you might need to enable this property.
117         * </p>
118         * <p>
119         * The default setting is to disable exact matches.
120         * </p>
121         * 
122         * @return true if exact matching is enabled
123         * @since Digester Release 1.1.1
124         */
125        public boolean isExactMatch()
126        {
127            return useExactMatch;
128        }
129    
130        /**
131         * Sets this rule be invoked when {@link #begin(String, String, Attributes)} (true)
132         * or {@link #end(String, String)} (false) methods are invoked, false by default.
133         *
134         * @param fireOnBegin flag to mark this rule be invoked when {@link #begin(String, String, Attributes)} (true)
135         *                    or {@link #end(String, String)} (false) methods are invoked, false by default.
136         */
137        public void setFireOnBegin( boolean fireOnBegin )
138        {
139            this.fireOnBegin = fireOnBegin;
140        }
141    
142        /**
143         * Returns the flag this rule be invoked when {@link #begin(String, String, Attributes)} (true)
144         * or {@link #end(String, String)} (false) methods are invoked, false by default.
145         *
146         * @return the flag this rule be invoked when {@link #begin(String, String, Attributes)} (true)
147         * or {@link #end(String, String)} (false) methods are invoked, false by default.
148         */
149        public boolean isFireOnBegin()
150        {
151            return fireOnBegin;
152        }
153    
154        /**
155         * <p>
156         * Set whether exact matching is enabled.
157         * </p>
158         * <p>
159         * See {@link #isExactMatch()}.
160         * </p>
161         * 
162         * @param useExactMatch should this rule use exact method matching
163         * @since Digester Release 1.1.1
164         */
165        public void setExactMatch( boolean useExactMatch )
166        {
167            this.useExactMatch = useExactMatch;
168        }
169    
170        /**
171         * {@inheritDoc}
172         */
173        @Override
174        public void begin( String namespace, String name, Attributes attributes )
175            throws Exception
176        {
177            if ( fireOnBegin )
178            {
179                invoke();
180            }
181        }
182    
183        /**
184         * {@inheritDoc}
185         */
186        @Override
187        public void end( String namespace, String name )
188            throws Exception
189        {
190            if ( !fireOnBegin )
191            {
192                invoke();
193            }
194        }
195    
196        /**
197         * Just performs the method execution.
198         *
199         * @throws Exception if any error occurs.
200         */
201        private void invoke()
202            throws Exception
203        {
204            // Identify the objects to be used
205            Object child = getChild();
206            Object parent = getParent();
207            if ( getDigester().getLogger().isDebugEnabled() )
208            {
209                if ( parent == null )
210                {
211                    getDigester().getLogger().debug( format( "[%s]{%s} Call [NULL PARENT].%s(%s)",
212                                                             getClass().getSimpleName(),
213                                                             getDigester().getMatch(),
214                                                             methodName,
215                                                             child ) );
216                }
217                else
218                {
219                    getDigester().getLogger().debug( format( "[%s]{%s} Call %s.%s(%s)",
220                                                             getClass().getSimpleName(),
221                                                             getDigester().getMatch(),
222                                                             parent.getClass().getName(),
223                                                             methodName,
224                                                             child ) );
225                }
226            }
227    
228            // Call the specified method
229            Class<?> paramTypes[] = new Class<?>[1];
230            if ( paramType != null )
231            {
232                paramTypes[0] = getDigester().getClassLoader().loadClass( paramTypeName );
233            }
234            else
235            {
236                paramTypes[0] = child.getClass();
237            }
238    
239            if ( useExactMatch )
240            {
241                invokeExactMethod( parent, methodName, new Object[] { child }, paramTypes );
242            }
243            else
244            {
245                invokeMethod( parent, methodName, new Object[] { child }, paramTypes );
246            }
247        }
248    
249        /**
250         * Returns the argument object of method has to be invoked.
251         *
252         * @return the argument object of method has to be invoked.
253         */
254        protected abstract Object getChild();
255    
256        /**
257         * Returns the target object of method has to be invoked.
258         *
259         * @return the target object of method has to be invoked.
260         */
261        protected abstract Object getParent();
262    
263        /**
264         * {@inheritDoc}
265         */
266        @Override
267        public final String toString()
268        {
269            return format( "%s[methodName=%s, paramType=%s, paramTypeName=%s, useExactMatch=%s, fireOnBegin=%s]",
270                           getClass().getSimpleName(), methodName, paramType, paramTypeName, useExactMatch, fireOnBegin );
271        }
272    
273    }