001package 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 022import static java.lang.System.arraycopy; 023import static java.lang.String.format; 024import static org.apache.commons.beanutils.ConstructorUtils.getAccessibleConstructor; 025import static org.apache.commons.beanutils.ConvertUtils.convert; 026 027import java.lang.reflect.Constructor; 028import java.lang.reflect.Method; 029import java.util.ArrayList; 030import java.util.Arrays; 031 032import net.sf.cglib.proxy.Callback; 033import net.sf.cglib.proxy.Enhancer; 034import net.sf.cglib.proxy.Factory; 035import net.sf.cglib.proxy.MethodInterceptor; 036import net.sf.cglib.proxy.MethodProxy; 037 038import org.xml.sax.Attributes; 039import 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 */ 045public 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 != 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}