001package 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
022import static java.lang.String.format;
023import static org.apache.commons.beanutils.MethodUtils.invokeExactMethod;
024import static org.apache.commons.beanutils.MethodUtils.invokeMethod;
025
026import 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 */
034public 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}