commons-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ohe...@apache.org
Subject svn commit: r1408062 - in /commons/proper/configuration/trunk/src: main/java/org/apache/commons/configuration/beanutils/ test/java/org/apache/commons/configuration/beanutils/
Date Sun, 11 Nov 2012 18:09:25 GMT
Author: oheger
Date: Sun Nov 11 18:09:24 2012
New Revision: 1408062

URL: http://svn.apache.org/viewvc?rev=1408062&view=rev
Log:
[CONFIGURATION-514] Added a method to BeanHelper to determine the constructor to be invoked.

Modified:
    commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/beanutils/BeanHelper.java
    commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/beanutils/ConstructorArg.java
    commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/beanutils/TestBeanHelper.java

Modified: commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/beanutils/BeanHelper.java
URL: http://svn.apache.org/viewvc/commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/beanutils/BeanHelper.java?rev=1408062&r1=1408061&r2=1408062&view=diff
==============================================================================
--- commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/beanutils/BeanHelper.java
(original)
+++ commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/beanutils/BeanHelper.java
Sun Nov 11 18:09:24 2012
@@ -17,10 +17,12 @@
 package org.apache.commons.configuration.beanutils;
 
 import java.beans.PropertyDescriptor;
+import java.lang.reflect.Constructor;
 import java.lang.reflect.InvocationTargetException;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -63,6 +65,10 @@ public final class BeanHelper
     private static final Map<String, BeanFactory> BEAN_FACTORIES = Collections
             .synchronizedMap(new HashMap<String, BeanFactory>());
 
+    /** A format string for generating error messages for constructor matching. */
+    private static final String FMT_CTOR_ERROR =
+            "%s! Bean class = %s, constructor arguments = %s";
+
     /**
      * Stores the default bean factory, which will be used if no other factory
      * is provided.
@@ -373,6 +379,29 @@ public final class BeanHelper
     }
 
     /**
+     * Evaluates constructor arguments in the specified {@code BeanDeclaration}
+     * and tries to find a unique matching constructor. If this is not possible,
+     * an exception is thrown. Note: This method is intended to be used by
+     * concrete {@link BeanFactory} implementations and not by client code.
+     *
+     * @param beanClass the class of the bean to be created
+     * @param data the current {@code BeanDeclaration}
+     * @return the single matching constructor
+     * @throws ConfigurationRuntimeException if no single matching constructor
+     *         can be found
+     * @throws NullPointerException if the bean class or bean declaration are
+     *         <b>null</b>
+     */
+    public static <T> Constructor<T> findMatchingConstructor(
+            Class<T> beanClass, BeanDeclaration data)
+    {
+        List<Constructor<T>> matchingConstructors =
+                findMatchingConstructors(beanClass, data);
+        checkSingleMatchingConstructor(beanClass, data, matchingConstructors);
+        return matchingConstructors.get(0);
+    }
+
+    /**
      * Returns a {@code java.lang.Class} object for the specified name.
      * Because class loading can be tricky in some environments the code for
      * retrieving a class by its name was extracted into this helper method. So
@@ -464,4 +493,120 @@ public final class BeanHelper
             return getDefaultBeanFactory();
         }
     }
+
+    /**
+     * Returns a list with all constructors which are compatible with the
+     * constructor arguments specified by the given {@code BeanDeclaration}.
+     *
+     * @param beanClass the bean class to be instantiated
+     * @param data the current {@code BeanDeclaration}
+     * @return a list with all matching constructors
+     */
+    private static <T> List<Constructor<T>> findMatchingConstructors(
+            Class<T> beanClass, BeanDeclaration data)
+    {
+        List<Constructor<T>> result = new LinkedList<Constructor<T>>();
+        Collection<ConstructorArg> args = getConstructorArgs(data);
+        for (Constructor<?> ctor : beanClass.getConstructors())
+        {
+            if (matchesConstructor(ctor, args))
+            {
+                // cast should be okay according to the JavaDocs of
+                // getConstructors()
+                @SuppressWarnings("unchecked")
+                Constructor<T> match = (Constructor<T>) ctor;
+                result.add(match);
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Checks whether the given constructor is compatible with the given list of
+     * arguments.
+     *
+     * @param ctor the constructor to be checked
+     * @param args the collection of constructor arguments
+     * @return a flag whether this constructor is compatible with the given
+     *         arguments
+     */
+    private static boolean matchesConstructor(Constructor<?> ctor,
+            Collection<ConstructorArg> args)
+    {
+        Class<?>[] types = ctor.getParameterTypes();
+        if (types.length != args.size())
+        {
+            return false;
+        }
+
+        int idx = 0;
+        for (ConstructorArg arg : args)
+        {
+            if (!arg.matches(types[idx++]))
+            {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Helper method for extracting constructor arguments from a bean
+     * declaration. Deals with <b>null</b> values.
+     *
+     * @param data the bean declaration
+     * @return the collection with constructor arguments (never <b>null</b>)
+     */
+    private static Collection<ConstructorArg> getConstructorArgs(
+            BeanDeclaration data)
+    {
+        Collection<ConstructorArg> args = data.getConstructorArgs();
+        if (args == null)
+        {
+            args = Collections.emptySet();
+        }
+        return args;
+    }
+
+    /**
+     * Helper method for testing whether exactly one matching constructor was
+     * found. Throws a meaningful exception if there is not a single matching
+     * constructor.
+     *
+     * @param beanClass the bean class
+     * @param data the bean declaration
+     * @param matchingConstructors the list with matching constructors
+     * @throws ConfigurationRuntimeException if there is not exactly one match
+     */
+    private static <T> void checkSingleMatchingConstructor(Class<T> beanClass,
+            BeanDeclaration data, List<Constructor<T>> matchingConstructors)
+    {
+        if (matchingConstructors.isEmpty())
+        {
+            throw constructorMatchingException(beanClass, data,
+                    "No matching constructor found");
+        }
+        if (matchingConstructors.size() > 1)
+        {
+            throw constructorMatchingException(beanClass, data,
+                    "Multiple matching constructors found");
+        }
+    }
+
+    /**
+     * Creates an exception if no single matching constructor was found with a
+     * meaningful error message.
+     *
+     * @param beanClass the affected bean class
+     * @param data the bean declaration
+     * @param msg an error message
+     * @return the exception with the error message
+     */
+    private static ConfigurationRuntimeException constructorMatchingException(
+            Class<?> beanClass, BeanDeclaration data, String msg)
+    {
+        return new ConfigurationRuntimeException(String.format(FMT_CTOR_ERROR,
+                msg, beanClass.getName(), getConstructorArgs(data).toString()));
+    }
 }

Modified: commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/beanutils/ConstructorArg.java
URL: http://svn.apache.org/viewvc/commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/beanutils/ConstructorArg.java?rev=1408062&r1=1408061&r2=1408062&view=diff
==============================================================================
--- commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/beanutils/ConstructorArg.java
(original)
+++ commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/beanutils/ConstructorArg.java
Sun Nov 11 18:09:24 2012
@@ -205,4 +205,26 @@ public final class ConstructorArg
 
         return getTypeName() == null || getTypeName().equals(argCls.getName());
     }
+
+    /**
+     * Returns a string representation of this object. This string contains the
+     * value of this constructor argument and the explicit type if provided.
+     *
+     * @return a string for this object
+     */
+    @Override
+    public String toString()
+    {
+        StringBuilder buf = new StringBuilder();
+        buf.append(getClass().getSimpleName());
+        buf.append(" [ value = ");
+        buf.append(isNestedBeanDeclaration() ? getBeanDeclaration()
+                : getValue());
+        if (getTypeName() != null)
+        {
+            buf.append(" (").append(getTypeName()).append(')');
+        }
+        buf.append(" ]");
+        return buf.toString();
+    }
 }

Modified: commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/beanutils/TestBeanHelper.java
URL: http://svn.apache.org/viewvc/commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/beanutils/TestBeanHelper.java?rev=1408062&r1=1408061&r2=1408062&view=diff
==============================================================================
--- commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/beanutils/TestBeanHelper.java
(original)
+++ commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/beanutils/TestBeanHelper.java
Sun Nov 11 18:09:24 2012
@@ -21,7 +21,10 @@ import static org.junit.Assert.assertNot
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
+import java.lang.reflect.Constructor;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.Map;
@@ -42,6 +45,12 @@ import org.junit.Test;
  */
 public class TestBeanHelper
 {
+    /** Constant for the test value of the string property. */
+    private static final String TEST_STRING = "testString";
+
+    /** Constant for the test value of the numeric property. */
+    private static final int TEST_INT = 42;
+
     /** Constant for the name of the test bean factory. */
     private static final String TEST_FACTORY = "testFactory";
 
@@ -327,6 +336,98 @@ public class TestBeanHelper
     }
 
     /**
+     * Tests whether the standard constructor can be found.
+     */
+    @Test
+    public void testFindMatchingConstructorNoArgs()
+    {
+        TestBeanDeclaration decl = new TestBeanDeclaration();
+        Constructor<TestBean> ctor =
+                BeanHelper.findMatchingConstructor(TestBean.class, decl);
+        assertEquals("Not the standard constructor", 0,
+                ctor.getParameterTypes().length);
+    }
+
+    /**
+     * Tests whether a matching constructor is found if the number of arguments
+     * is unique.
+     */
+    @Test
+    public void testFindMatchingConstructorArgCount()
+    {
+        TestBeanDeclaration decl = new TestBeanDeclaration();
+        Collection<ConstructorArg> args = new ArrayList<ConstructorArg>();
+        args.add(ConstructorArg.forValue(TEST_STRING));
+        args.add(ConstructorArg.forValue(String.valueOf(TEST_INT)));
+        decl.setConstructorArgs(args);
+        Constructor<TestCtorBean> ctor =
+                BeanHelper.findMatchingConstructor(TestCtorBean.class, decl);
+        Class<?>[] paramTypes = ctor.getParameterTypes();
+        assertEquals("Wrong number of parameters", 2, paramTypes.length);
+        assertEquals("Wrong parameter type 1", String.class, paramTypes[0]);
+        assertEquals("Wrong parameter type 2", Integer.TYPE, paramTypes[1]);
+    }
+
+    /**
+     * Tests whether ambiguous constructor arguments are detected.
+     */
+    @Test(expected = ConfigurationRuntimeException.class)
+    public void testFindMatchingConstructorAmbiguous()
+    {
+        TestBeanDeclaration decl = new TestBeanDeclaration();
+        Collection<ConstructorArg> args = new ArrayList<ConstructorArg>();
+        args.add(ConstructorArg.forValue(TEST_STRING));
+        decl.setConstructorArgs(args);
+        BeanHelper.findMatchingConstructor(TestCtorBean.class, decl);
+    }
+
+    /**
+     * Tests whether explicit type declarations are used to resolve ambiguous
+     * parameter types.
+     */
+    @Test
+    public void testFindMatchingConstructorExplicitType()
+    {
+        TestBeanDeclaration decl = new TestBeanDeclaration();
+        Collection<ConstructorArg> args = new ArrayList<ConstructorArg>();
+        args.add(ConstructorArg.forBeanDeclaration(setUpBeanDeclaration(),
+                TestBean.class.getName()));
+        decl.setConstructorArgs(args);
+        Constructor<TestCtorBean> ctor =
+                BeanHelper.findMatchingConstructor(TestCtorBean.class, decl);
+        Class<?>[] paramTypes = ctor.getParameterTypes();
+        assertEquals("Wrong number of parameters", 1, paramTypes.length);
+        assertEquals("Wrong parameter type", TestBean.class, paramTypes[0]);
+    }
+
+    /**
+     * Tests the case that no matching constructor is found.
+     */
+    @Test
+    public void testFindMatchingConstructorNoMatch()
+    {
+        TestBeanDeclaration decl = new TestBeanDeclaration();
+        Collection<ConstructorArg> args = new ArrayList<ConstructorArg>();
+        args.add(ConstructorArg.forValue(TEST_STRING, getClass().getName()));
+        decl.setConstructorArgs(args);
+        try
+        {
+            BeanHelper.findMatchingConstructor(TestCtorBean.class, decl);
+            fail("No exception thrown!");
+        }
+        catch (ConfigurationRuntimeException crex)
+        {
+            String msg = crex.getMessage();
+            assertTrue("Bean class not found:" + msg,
+                    msg.indexOf(TestCtorBean.class.getName()) > 0);
+            assertTrue("Parameter value not found: " + msg,
+                    msg.indexOf(TEST_STRING) > 0);
+            assertTrue("Parameter type not found: " + msg,
+                    msg.indexOf("(" + getClass().getName() + ')') > 0);
+        }
+    }
+
+    /**
      * Returns an initialized bean declaration.
      *
      * @return the bean declaration
@@ -335,8 +436,8 @@ public class TestBeanHelper
     {
         TestBeanDeclaration data = new TestBeanDeclaration();
         Map<String, Object> properties = new HashMap<String, Object>();
-        properties.put("stringValue", "testString");
-        properties.put("intValue", "42");
+        properties.put("stringValue", TEST_STRING);
+        properties.put("intValue", String.valueOf(TEST_INT));
         data.setBeanProperties(properties);
         TestBeanDeclaration buddyData = new TestBeanDeclaration();
         Map<String, Object> properties2 = new HashMap<String, Object>();
@@ -362,9 +463,9 @@ public class TestBeanHelper
      */
     private void checkBean(TestBean bean)
     {
-        assertEquals("Wrong string property", "testString", bean
+        assertEquals("Wrong string property", TEST_STRING, bean
                 .getStringValue());
-        assertEquals("Wrong int property", 42, bean.getIntValue());
+        assertEquals("Wrong int property", TEST_INT, bean.getIntValue());
         TestBean buddy = bean.getBuddy();
         assertNotNull("Buddy was not set", buddy);
         assertEquals("Wrong string property in buddy", "Another test string",
@@ -415,6 +516,32 @@ public class TestBeanHelper
     }
 
     /**
+     * Another test bean class which defines some constructors.
+     */
+    public static class TestCtorBean extends TestBean
+    {
+        public TestCtorBean()
+        {
+        }
+
+        public TestCtorBean(TestBean buddy)
+        {
+            setBuddy(buddy);
+        }
+
+        public TestCtorBean(String s)
+        {
+            setStringValue(s);
+        }
+
+        public TestCtorBean(String s, int i)
+        {
+            this(s);
+            setIntValue(i);
+        }
+    }
+
+    /**
      * An implementation of the BeanFactory interface used for testing. This
      * implementation is really simple: If the TestBean class is provided, a new
      * instance will be created. Otherwise an exception is thrown.
@@ -454,7 +581,7 @@ public class TestBeanHelper
 
     /**
      * A test implementation of the BeanDeclaration interface. This
-     * implementation allows to set the values directly, which should be
+     * implementation allows setting the values directly, which should be
      * returned by the methods required by the BeanDeclaration interface.
      */
     static class TestBeanDeclaration implements BeanDeclaration
@@ -469,6 +596,8 @@ public class TestBeanHelper
 
         private Map<String, Object> nestedBeanDeclarations;
 
+        private Collection<ConstructorArg> constructorArgs;
+
         public String getBeanClassName()
         {
             return beanClassName;
@@ -521,8 +650,12 @@ public class TestBeanHelper
 
         public Collection<ConstructorArg> getConstructorArgs()
         {
-            //TODO implementation
-            return null;
+            return constructorArgs;
+        }
+
+        public void setConstructorArgs(Collection<ConstructorArg> args)
+        {
+            constructorArgs = args;
         }
     }
 }



Mime
View raw message