[DIGESTER-153] Add constructor support to ObjectCreateRule - even when the constructor args come from nested elements

git-svn-id: https://svn.apache.org/repos/asf/commons/proper/digester/trunk@1209953 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/src/main/java/org/apache/commons/digester3/ObjectCreateRule.java b/src/main/java/org/apache/commons/digester3/ObjectCreateRule.java
index dbf2309..ea5763f 100644
--- a/src/main/java/org/apache/commons/digester3/ObjectCreateRule.java
+++ b/src/main/java/org/apache/commons/digester3/ObjectCreateRule.java
@@ -21,16 +21,18 @@
 
 import static java.lang.String.format;
 import static java.util.Arrays.fill;
-import static net.sf.cglib.proxy.Enhancer.isEnhanced;
 import static org.apache.commons.beanutils.ConstructorUtils.getAccessibleConstructor;
+import static org.apache.commons.beanutils.ConvertUtils.convert;
 
 import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
 import java.util.Arrays;
 
-import net.sf.cglib.proxy.Callback;
 import net.sf.cglib.proxy.Enhancer;
 import net.sf.cglib.proxy.Factory;
-import net.sf.cglib.proxy.LazyLoader;
+import net.sf.cglib.proxy.MethodInterceptor;
+import net.sf.cglib.proxy.MethodProxy;
 
 import org.xml.sax.Attributes;
 import org.xml.sax.SAXException;
@@ -42,6 +44,68 @@
 public class ObjectCreateRule
     extends Rule
 {
+    private interface DeferredConstructionProxy
+    {
+        void finish();
+    }
+
+    private static class DeferredConstructionCallback implements MethodInterceptor
+    {
+        Constructor<?> constructor;
+        Object[] constructorArgs;
+        ArrayList<RecordedInvocation> invocations = new ArrayList<RecordedInvocation>();
+        Object delegate;
+
+        DeferredConstructionCallback(Constructor<?> constructor, Object[] constructorArgs)
+        {
+            super();
+            this.constructor = constructor;
+            this.constructorArgs = constructorArgs;
+        }
+
+        public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable
+        {
+            boolean hasDelegate;
+            synchronized ( this ) {
+                hasDelegate = delegate != null;
+                if ( method.getDeclaringClass().equals( DeferredConstructionProxy.class ) )
+                {
+                    if ( !hasDelegate )
+                    {
+                        establishDelegate();
+                        hasDelegate = true;
+                    }
+                    return null;
+                }
+            }
+            if ( hasDelegate ) {
+                return proxy.invoke( delegate, args );
+            }
+            invocations.add( new RecordedInvocation( method, args ) );
+            return proxy.invokeSuper( obj, args );
+        }
+
+        private void establishDelegate() throws Exception {
+            // this piece of code is adapted from CallMethodRule
+            for ( int i = 0; i < constructorArgs.length; i++ )
+            {
+                // convert nulls and convert stringy parameters for non-stringy param types
+                if ( constructorArgs[i] == null
+                        || ( constructorArgs[i] instanceof String && !String.class.isAssignableFrom( constructor.getParameterTypes()[i] ) ) )
+                {
+                    constructorArgs[i] = convert( (String) constructorArgs[i], constructor.getParameterTypes()[i] );
+                }
+            }
+            delegate = constructor.newInstance( constructorArgs );
+            for ( RecordedInvocation invocation : invocations )
+            {
+                invocation.getInvokedMethod().invoke( delegate, invocation.getArguments() );
+            }
+            constructor = null;
+            constructorArgs = null;
+            invocations = null;
+        }
+    }
 
     // ----------------------------------------------------------- Constructors
 
@@ -205,16 +269,16 @@
         fill( constructorArguments, null );
         getDigester().pushParams( constructorArguments );
 
-        ObjectCreateRuleLazyLoader lazyLoader = new ObjectCreateRuleLazyLoader( constructor,
-                                                                                constructorArgumentsTypes,
-                                                                                constructorArguments );
+        DeferredConstructionCallback callback = new DeferredConstructionCallback(constructor, constructorArguments);
+
         if ( proxyFactory == null ) {
             synchronized ( this ) {
                 // check again for null now that we're in the synchronized block:
                 if ( proxyFactory == null ) {
                     Enhancer enhancer = new Enhancer();
                     enhancer.setSuperclass( clazz );
-                    enhancer.setCallback( lazyLoader );
+                    enhancer.setInterfaces(new Class[] { DeferredConstructionProxy.class });
+                    enhancer.setCallback( callback );
                     enhancer.setClassLoader( getDigester().getClassLoader() );
                     Object result = enhancer.create();
                     proxyFactory = (Factory) result;
@@ -222,7 +286,7 @@
                 }
             }
         }
-        return proxyFactory.newInstance( lazyLoader );
+        return proxyFactory.newInstance( callback );
     }
 
     /**
@@ -234,10 +298,10 @@
     {
         Object top = getDigester().pop();
 
-        if ( isEnhanced( top.getClass() ) )
+        if (top instanceof DeferredConstructionProxy)
         {
-            // do lazy load?!?
             getDigester().popParams();
+            ((DeferredConstructionProxy) top).finish();
         }
 
         if ( getDigester().getLogger().isDebugEnabled() )
diff --git a/src/main/java/org/apache/commons/digester3/RecordedInvocation.java b/src/main/java/org/apache/commons/digester3/RecordedInvocation.java
new file mode 100644
index 0000000..7072618
--- /dev/null
+++ b/src/main/java/org/apache/commons/digester3/RecordedInvocation.java
@@ -0,0 +1,135 @@
+/*

+ * Licensed to the Apache Software Foundation (ASF) under one or more

+ * contributor license agreements.  See the NOTICE file distributed with

+ * this work for additional information regarding copyright ownership.

+ * The ASF licenses this file to You under the Apache License, Version 2.0

+ * (the "License"); you may not use this file except in compliance with

+ * the License.  You may obtain a copy of the License at

+ *

+ *      http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing, software

+ * distributed under the License is distributed on an "AS IS" BASIS,

+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+ * See the License for the specific language governing permissions and

+ * limitations under the License.

+ */

+

+package org.apache.commons.digester3;

+

+import java.lang.reflect.Method;

+

+/**

+ * Detached representation of a method invocation.

+ * From Commons [proxy] v2 branch.

+ * @author James Carman

+ */

+public class RecordedInvocation

+{

+//**********************************************************************************************************************

+// Fields

+//**********************************************************************************************************************

+

+    private final Method invokedMethod;

+    private final Object[] arguments;

+

+  //**********************************************************************************************************************

+ // Constructors

+ //**********************************************************************************************************************

+

+    /**

+     * Create a new RecordedInvocation instance.

+     * @param invokedMethod

+     * @param arguments

+     */

+    public RecordedInvocation( Method invokedMethod, Object[] arguments )

+    {

+        this.invokedMethod = invokedMethod;

+        this.arguments = arguments;

+    }

+

+  //**********************************************************************************************************************

+ // Canonical Methods

+ //**********************************************************************************************************************

+

+    /**

+     * Get the invokedMethod.

+     * @return Method

+     */

+    public Method getInvokedMethod() {

+        return invokedMethod;

+    }

+

+    /**

+     * Get the arguments.

+     * @return Object[]

+     */

+    public Object[] getArguments() {

+        return arguments;

+    }

+

+    /**

+     * {@inheritDoc}

+     */

+    public String toString()

+    {

+        StringBuffer buffer = new StringBuffer();

+        buffer.append(invokedMethod.getDeclaringClass().getName());

+        buffer.append(".");

+        buffer.append(invokedMethod.getName());

+        buffer.append("(");

+        int count = arguments.length;

+        for( int i = 0; i < count; i++ )

+        {

+            Object arg = arguments[i];

+            if( i > 0 )

+            {

+                buffer.append(", ");

+            }

+            convert(buffer, arg);

+        }

+        buffer.append(")");

+        return buffer.toString();

+    }

+

+    /**

+     * Add a string representation of <code>input</code> to <code>buffer</code>.

+     * @param buffer

+     * @param input

+     */

+    protected void convert( StringBuffer buffer, Object input )

+    {

+        if( input == null )

+        {

+            buffer.append("<null>");

+            return;

+        }

+

+        // Primitive types, and non-object arrays

+        // use toString().

+        if( !( input instanceof Object[] ) )

+        {

+            buffer.append(input.toString());

+            return;

+        }

+        else

+        {

+            buffer.append("(");

+            buffer.append(input.getClass().getSimpleName());

+            buffer.append("){");

+            Object[] array = ( Object[] ) input;

+            int count = array.length;

+            for( int i = 0; i < count; i++ )

+            {

+                if( i > 0 )

+                {

+                    buffer.append(", ");

+                }

+                // We use convert() again, because it could be a multi-dimensional array

+                // where each element must be converted.

+                convert(buffer, array[i]);

+            }

+            buffer.append("}");

+        }

+    }

+}

diff --git a/src/test/java/org/apache/commons/digester3/Digester153TestCase.java b/src/test/java/org/apache/commons/digester3/Digester153TestCase.java
index 688644b..7804b3f 100644
--- a/src/test/java/org/apache/commons/digester3/Digester153TestCase.java
+++ b/src/test/java/org/apache/commons/digester3/Digester153TestCase.java
@@ -62,6 +62,33 @@
         assertEquals( 9.99D, bean.getDoubleProperty(), 0 );
     }
 
+    @Test
+    public void constructorWithAttributeAndElement()
+        throws Exception
+    {
+        ObjectCreateRule createRule = new ObjectCreateRule( TestBean.class );
+        createRule.setConstructorArguments( boolean.class, double.class );
+
+        Digester digester = new Digester();
+        digester.addRule( "toplevel/bean", createRule );
+        digester.addCallParam( "toplevel/bean", 0, "boolean" );
+        digester.addCallParam( "toplevel/bean/double", 1 );
+        digester.addBeanPropertySetter("toplevel/bean/float", "floatProperty");
+
+        TestBean bean = digester.parse( getClass().getResourceAsStream( "ConstructorWithAttributeAndElement.xml" ) );
+
+        assertTrue( bean.getBooleanProperty() );
+        assertEquals( 9.99D, bean.getDoubleProperty(), 0 );
+        assertEquals( Float.valueOf( 5.5f ), Float.valueOf( bean.getFloatProperty() ) );
+
+        // do it again to exercise the cglib Factory:
+        bean = digester.parse( getClass().getResourceAsStream( "ConstructorWithAttributeAndElement.xml" ) );
+
+        assertTrue( bean.getBooleanProperty() );
+        assertEquals( 9.99D, bean.getDoubleProperty(), 0 );
+        assertEquals( Float.valueOf( 5.5f ), Float.valueOf( bean.getFloatProperty() ) );
+    }
+
     /*
     @Test
     public void basicConstructorViaBinder()
diff --git a/src/test/resources/org/apache/commons/digester3/ConstructorWithAttributeAndElement.xml b/src/test/resources/org/apache/commons/digester3/ConstructorWithAttributeAndElement.xml
new file mode 100644
index 0000000..4f00b93
--- /dev/null
+++ b/src/test/resources/org/apache/commons/digester3/ConstructorWithAttributeAndElement.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<toplevel>
+  <bean boolean="true">
+  	<double>9.99</double>
+  	<float>5.5</float>
+  </bean>
+</toplevel>