1 package org.apache.commons.digester3;
2
3 /*
4 * Licensed to the Apache Software Foundation (ASF) under one
5 * or more contributor license agreements. See the NOTICE file
6 * distributed with this work for additional information
7 * regarding copyright ownership. The ASF licenses this file
8 * to you under the Apache License, Version 2.0 (the
9 * "License"); you may not use this file except in compliance
10 * with the License. You may obtain a copy of the License at
11 *
12 * http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing,
15 * software distributed under the License is distributed on an
16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 * KIND, either express or implied. See the License for the
18 * specific language governing permissions and limitations
19 * under the License.
20 */
21
22 import static java.lang.String.format;
23 import static org.apache.commons.beanutils.MethodUtils.invokeExactMethod;
24 import static org.apache.commons.beanutils.MethodUtils.invokeMethod;
25
26 import org.xml.sax.Attributes;
27
28 /**
29 * Abstract implementation for {@link org.apache.commons.digester3.SetNextRule},
30 * {@link org.apache.commons.digester3.SetRootRule} and {@link org.apache.commons.digester3.SetTopRule} rules.
31 *
32 * @since 3.0
33 */
34 public abstract class AbstractMethodRule
35 extends Rule
36 {
37
38 /**
39 * The method name to call on the parent object.
40 */
41 protected String methodName = null;
42
43 /**
44 * The Java class name of the parameter type expected by the method.
45 */
46 protected String paramTypeName = null;
47
48 /**
49 * The Java class name of the parameter type expected by the method.
50 */
51 protected Class<?> paramType;
52
53 /**
54 * Should we use exact matching. Default is no.
55 */
56 protected boolean useExactMatch = false;
57
58 /**
59 * Should this rule be invoked when {@link #begin(String, String, Attributes)} (true)
60 * or {@link #end(String, String)} (false) methods are invoked, false by default.
61 */
62 protected boolean fireOnBegin = false;
63
64 /**
65 * Construct a "set next" rule with the specified method name. The method's argument type is assumed to be the class
66 * of the child object.
67 *
68 * @param methodName Method name of the parent method to call
69 */
70 public AbstractMethodRule( String methodName )
71 {
72 this( methodName, (String) null );
73 }
74
75 /**
76 * Construct a "set next" rule with the specified method name.
77 *
78 * @param methodName Method name of the parent method to call
79 * @param paramType Java class of the parent method's argument (if you wish to use a primitive type, specify the
80 * corresonding Java wrapper class instead, such as <code>java.lang.Boolean</code> for a
81 * <code>boolean</code> parameter)
82 */
83 public AbstractMethodRule( String methodName, Class<?> paramType )
84 {
85 this( methodName, paramType.getName() );
86 this.paramType = paramType;
87 }
88
89 /**
90 * Construct a "set next" rule with the specified method name.
91 *
92 * @param methodName Method name of the parent method to call
93 * @param paramTypeName Java class of the parent method's argument (if you wish to use a primitive type, specify the
94 * corresonding Java wrapper class instead, such as <code>java.lang.Boolean</code> for a
95 * <code>boolean</code> parameter)
96 */
97 public AbstractMethodRule( String methodName, String paramTypeName )
98 {
99 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 }