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.System.arraycopy;
23  import static java.lang.String.format;
24  import static org.apache.commons.beanutils.ConstructorUtils.getAccessibleConstructor;
25  import static org.apache.commons.beanutils.ConvertUtils.convert;
26  
27  import java.lang.reflect.Constructor;
28  import java.lang.reflect.Method;
29  import java.util.ArrayList;
30  import java.util.Arrays;
31  
32  import net.sf.cglib.proxy.Callback;
33  import net.sf.cglib.proxy.Enhancer;
34  import net.sf.cglib.proxy.Factory;
35  import net.sf.cglib.proxy.MethodInterceptor;
36  import net.sf.cglib.proxy.MethodProxy;
37  
38  import org.xml.sax.Attributes;
39  import org.xml.sax.SAXException;
40  
41  /**
42   * Rule implementation that creates a new object and pushes it onto the object stack. When the element is complete, the
43   * object will be popped
44   */
45  public class ObjectCreateRule
46      extends Rule
47  {
48      private static class DeferredConstructionCallback implements MethodInterceptor
49      {
50          Constructor<?> constructor;
51          Object[] constructorArgs;
52          ArrayList<RecordedInvocation> invocations = new ArrayList<RecordedInvocation>();
53          Object delegate;
54  
55          DeferredConstructionCallback( Constructor<?> constructor, Object[] constructorArgs )
56          {
57              this.constructor = constructor;
58              this.constructorArgs = constructorArgs;
59          }
60  
61          public Object intercept( Object obj, Method method, Object[] args, MethodProxy proxy )
62              throws Throwable
63          {
64              boolean hasDelegate = delegate != null;
65              if ( !hasDelegate )
66              {
67                  invocations.add( new RecordedInvocation( method, args ) );
68              }
69              if ( hasDelegate )
70              {
71                  return proxy.invoke( delegate, args );
72              }
73              return proxy.invokeSuper( obj, args );
74          }
75  
76          void establishDelegate()
77              throws Exception
78          {
79              convertTo( constructor.getParameterTypes(), constructorArgs );
80              delegate = constructor.newInstance( constructorArgs );
81              for ( RecordedInvocation invocation : invocations )
82              {
83                  invocation.getInvokedMethod().invoke( delegate, invocation.getArguments() );
84              }
85              constructor = null;
86              constructorArgs = null;
87              invocations = null;
88          }
89      }
90  
91      private static class ProxyManager
92      {
93          private final Class<?> clazz;
94          private final Constructor<?> constructor;
95          private final Object[] templateConstructorArguments;
96          private final Digester digester;
97          private final boolean hasDefaultConstructor;
98          private Factory factory;
99  
100         ProxyManager( Class<?> clazz, Constructor<?> constructor, Object[] constructorArguments, Digester digester )
101         {
102             this.clazz = clazz;
103             hasDefaultConstructor = getAccessibleConstructor( clazz, new Class[0] ) != null;
104             this.constructor = constructor;
105             Class<?>[] argTypes = constructor.getParameterTypes();
106             templateConstructorArguments = new Object[argTypes.length];
107             if ( constructorArguments == null )
108             {
109                 for ( int i = 0; i < templateConstructorArguments.length; i++ )
110                 {
111                     if ( argTypes[i].equals( boolean.class ) )
112                     {
113                         templateConstructorArguments[i] = Boolean.FALSE;
114                         continue;
115                     }
116                     if ( argTypes[i].isPrimitive() )
117                     {
118                         templateConstructorArguments[i] = convert( "0", argTypes[i] );
119                         continue;
120                     }
121                     templateConstructorArguments[i] = null;
122                 }
123             }
124             else
125             {
126                 if ( constructorArguments.length != argTypes.length )
127                 {
128                     throw new IllegalArgumentException(
129                         format( "wrong number of constructor arguments specified: %s instead of %s",
130                         constructorArguments.length, argTypes.length ) );
131                 }
132                 arraycopy( constructorArguments, 0, templateConstructorArguments, 0, constructorArguments.length );
133             }
134             convertTo( argTypes, templateConstructorArguments );
135             this.digester = digester;
136         }
137 
138         Object createProxy()
139         {
140             Object[] constructorArguments = new Object[templateConstructorArguments.length];
141             arraycopy( templateConstructorArguments, 0, constructorArguments, 0, constructorArguments.length );
142             digester.pushParams( constructorArguments );
143 
144             DeferredConstructionCallback callback =
145                 new DeferredConstructionCallback( constructor, constructorArguments );
146 
147             Object result;
148 
149             if ( factory == null )
150             {
151                 Enhancer enhancer = new Enhancer();
152                 enhancer.setSuperclass( clazz );
153                 enhancer.setCallback( callback );
154                 enhancer.setClassLoader( digester.getClassLoader() );
155                 enhancer.setInterceptDuringConstruction( false );
156                 if ( hasDefaultConstructor )
157                 {
158                     result = enhancer.create();
159                 }
160                 else
161                 {
162                     result = enhancer.create( constructor.getParameterTypes(), constructorArguments );
163                 }
164                 factory = (Factory) result;
165                 return result;
166             }
167 
168             if ( hasDefaultConstructor )
169             {
170                 result = factory.newInstance( callback );
171             }
172             else
173             {
174                 result = factory.newInstance( constructor.getParameterTypes(),
175                     constructorArguments, new Callback[] { callback } );
176             }
177             return result;
178         }
179 
180         void finalize( Object proxy )
181             throws Exception
182         {
183             digester.popParams();
184             ( (DeferredConstructionCallback) ( (Factory) proxy ).getCallback( 0 ) ).establishDelegate();
185         }
186     }
187 
188     // ----------------------------------------------------------- Constructors
189 
190     /**
191      * Construct an object create rule with the specified class name.
192      *
193      * @param className Java class name of the object to be created
194      */
195     public ObjectCreateRule( String className )
196     {
197         this( className, (String) null );
198     }
199 
200     /**
201      * Construct an object create rule with the specified class.
202      *
203      * @param clazz Java class name of the object to be created
204      */
205     public ObjectCreateRule( Class<?> clazz )
206     {
207         this( clazz.getName(), (String) null );
208         this.clazz = clazz;
209     }
210 
211     /**
212      * Construct an object create rule with the specified class name and an optional attribute name containing an
213      * override.
214      *
215      * @param className Java class name of the object to be created
216      * @param attributeName Attribute name which, if present, contains an override of the class name to create
217      */
218     public ObjectCreateRule( String className, String attributeName )
219     {
220         this.className = className;
221         this.attributeName = attributeName;
222     }
223 
224     /**
225      * Construct an object create rule with the specified class and an optional attribute name containing an override.
226      *
227      * @param attributeName Attribute name which, if present, contains an
228      * @param clazz Java class name of the object to be created override of the class name to create
229      */
230     public ObjectCreateRule( String attributeName, Class<?> clazz )
231     {
232         this( clazz != null ? clazz.getName() : null, attributeName );
233         this.clazz = clazz;
234     }
235 
236     // ----------------------------------------------------- Instance Variables
237 
238     /**
239      * The attribute containing an override class name if it is present.
240      */
241     protected String attributeName = null;
242 
243     /**
244      * The Java class of the object to be created.
245      */
246     protected Class<?> clazz = null;
247 
248     /**
249      * The Java class name of the object to be created.
250      */
251     protected String className = null;
252 
253     /**
254      * The constructor argument types.
255      *
256      * @since 3.2
257      */
258     private Class<?>[] constructorArgumentTypes;
259 
260     /**
261      * The explictly specified default constructor arguments which may be overridden by CallParamRules.
262      *
263      * @since 3.2
264      */
265     private Object[] defaultConstructorArguments;
266 
267     /**
268      * Helper object for managing proxies.
269      *
270      * @since 3.2
271      */
272     private ProxyManager proxyManager;
273 
274     // --------------------------------------------------------- Public Methods
275 
276     /**
277      * Allows users to specify constructor argument types.
278      *
279      * @param constructorArgumentTypes the constructor argument types
280      * @since 3.2
281      */
282     public void setConstructorArgumentTypes( Class<?>... constructorArgumentTypes )
283     {
284         if ( constructorArgumentTypes == null )
285         {
286             throw new IllegalArgumentException( "Parameter 'constructorArgumentTypes' must not be null" );
287         }
288 
289         this.constructorArgumentTypes = constructorArgumentTypes;
290     }
291 
292     /**
293      * Allows users to specify default constructor arguments.  If a default/no-arg constructor is not available
294      * for the target class, these arguments will be used to create the proxy object.  For any argument
295      * not supplied by a {@link CallParamRule}, the corresponding item from this array will be used
296      * to construct the final object as well.
297      *
298      * @param constructorArguments the default constructor arguments.
299      * @since 3.2
300      */
301     public void setDefaultConstructorArguments( Object... constructorArguments )
302     {
303         if ( constructorArguments == null )
304         {
305             throw new IllegalArgumentException( "Parameter 'constructorArguments' must not be null" );
306         }
307 
308         this.defaultConstructorArguments = constructorArguments;
309     }
310 
311     /**
312      * {@inheritDoc}
313      */
314     @Override
315     public void begin( String namespace, String name, Attributes attributes )
316         throws Exception
317     {
318         Class<?> clazz = this.clazz;
319 
320         if ( clazz == null )
321         {
322             // Identify the name of the class to instantiate
323             String realClassName = className;
324             if ( attributeName != null )
325             {
326                 String value = attributes.getValue( attributeName );
327                 if ( value != null )
328                 {
329                     realClassName = value;
330                 }
331             }
332             if ( getDigester().getLogger().isDebugEnabled() )
333             {
334                 getDigester().getLogger().debug( format( "[ObjectCreateRule]{%s} New '%s'",
335                                                          getDigester().getMatch(),
336                                                          realClassName ) );
337             }
338 
339             // Instantiate the new object and push it on the context stack
340             clazz = getDigester().getClassLoader().loadClass( realClassName );
341         }
342         Object instance;
343         if ( constructorArgumentTypes == null || constructorArgumentTypes.length == 0 )
344         {
345             if ( getDigester().getLogger().isDebugEnabled() )
346             {
347                 getDigester()
348                     .getLogger()
349                     .debug( format( "[ObjectCreateRule]{%s} New '%s' using default empty constructor",
350                                     getDigester().getMatch(),
351                                     clazz.getName() ) );
352             }
353 
354             instance = clazz.newInstance();
355         }
356         else
357         {
358             if ( proxyManager == null )
359             {
360                 Constructor<?> constructor = getAccessibleConstructor( clazz, constructorArgumentTypes );
361 
362                 if ( constructor == null )
363                 {
364                     throw new SAXException(
365                                    format( "[ObjectCreateRule]{%s} Class '%s' does not have a construcor with types %s",
366                                            getDigester().getMatch(),
367                                            clazz.getName(),
368                                            Arrays.toString( constructorArgumentTypes ) ) );
369                 }
370                 proxyManager = new ProxyManager( clazz, constructor, defaultConstructorArguments, getDigester() );
371             }
372             instance = proxyManager.createProxy();
373         }
374         getDigester().push( instance );
375     }
376 
377     /**
378      * {@inheritDoc}
379      */
380     @Override
381     public void end( String namespace, String name )
382         throws Exception
383     {
384         Object top = getDigester().pop();
385 
386         if ( proxyManager != null )
387         {
388             proxyManager.finalize( top );
389         }
390 
391         if ( getDigester().getLogger().isDebugEnabled() )
392         {
393             getDigester().getLogger().debug( format( "[ObjectCreateRule]{%s} Pop '%s'",
394                                                      getDigester().getMatch(),
395                                                      top.getClass().getName() ) );
396         }
397     }
398 
399     /**
400      * {@inheritDoc}
401      */
402     @Override
403     public String toString()
404     {
405         return format( "ObjectCreateRule[className=%s, attributeName=%s]", className, attributeName );
406     }
407 
408     private static void convertTo( Class<?>[] types, Object[] array )
409     {
410         if ( array.length != types.length )
411         {
412             throw new IllegalArgumentException();
413         }
414         // this piece of code is adapted from CallMethodRule
415         for ( int i = 0; i < array.length; i++ )
416         {
417             // convert nulls and convert stringy parameters for non-stringy param types
418             if ( array[i] == null
419                     || ( array[i] instanceof String && !String.class.isAssignableFrom( types[i] ) ) )
420             {
421                 array[i] = convert( (String) array[i], types[i] );
422             }
423         }
424     }
425 
426 }