View Javadoc

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 }