[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>