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 }