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.System.arraycopy;
023 import static java.lang.String.format;
024 import static org.apache.commons.beanutils.ConstructorUtils.getAccessibleConstructor;
025 import static org.apache.commons.beanutils.ConvertUtils.convert;
026
027 import java.lang.reflect.Constructor;
028 import java.lang.reflect.Method;
029 import java.util.ArrayList;
030 import java.util.Arrays;
031
032 import net.sf.cglib.proxy.Callback;
033 import net.sf.cglib.proxy.Enhancer;
034 import net.sf.cglib.proxy.Factory;
035 import net.sf.cglib.proxy.MethodInterceptor;
036 import net.sf.cglib.proxy.MethodProxy;
037
038 import org.xml.sax.Attributes;
039 import org.xml.sax.SAXException;
040
041 /**
042 * Rule implementation that creates a new object and pushes it onto the object stack. When the element is complete, the
043 * object will be popped
044 */
045 public class ObjectCreateRule
046 extends Rule
047 {
048 private static class DeferredConstructionCallback implements MethodInterceptor
049 {
050 Constructor<?> constructor;
051 Object[] constructorArgs;
052 ArrayList<RecordedInvocation> invocations = new ArrayList<RecordedInvocation>();
053 Object delegate;
054
055 DeferredConstructionCallback( Constructor<?> constructor, Object[] constructorArgs )
056 {
057 this.constructor = constructor;
058 this.constructorArgs = constructorArgs;
059 }
060
061 public Object intercept( Object obj, Method method, Object[] args, MethodProxy proxy )
062 throws Throwable
063 {
064 boolean hasDelegate = delegate != null;
065 if ( !hasDelegate )
066 {
067 invocations.add( new RecordedInvocation( method, args ) );
068 }
069 if ( hasDelegate )
070 {
071 return proxy.invoke( delegate, args );
072 }
073 return proxy.invokeSuper( obj, args );
074 }
075
076 void establishDelegate()
077 throws Exception
078 {
079 convertTo( constructor.getParameterTypes(), constructorArgs );
080 delegate = constructor.newInstance( constructorArgs );
081 for ( RecordedInvocation invocation : invocations )
082 {
083 invocation.getInvokedMethod().invoke( delegate, invocation.getArguments() );
084 }
085 constructor = null;
086 constructorArgs = null;
087 invocations = null;
088 }
089 }
090
091 private static class ProxyManager
092 {
093 private final Class<?> clazz;
094 private final Constructor<?> constructor;
095 private final Object[] templateConstructorArguments;
096 private final Digester digester;
097 private final boolean hasDefaultConstructor;
098 private Factory factory;
099
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.getName(), 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 }