Repository: orika-mapper/orika
Pull request #218 (2017-06-08 19:04:11)

Add an extension point to simple constructor resolver strategy
It's extended to handle constructor param names that follow a prefix-letter naming convention. For instance p-prefixed param names - (pName, pAge).


Patch:
diff --git a/core/src/main/java/ma/glasnost/orika/constructor/PrefixParamConstructorResolverStrategy.java b/core/src/main/java/ma/glasnost/orika/constructor/PrefixParamConstructorResolverStrategy.java
new file mode 100644
index 00000000..17c5f1e1
--- /dev/null
+++ b/core/src/main/java/ma/glasnost/orika/constructor/PrefixParamConstructorResolverStrategy.java
@@ -0,0 +1,21 @@
+package ma.glasnost.orika.constructor;
+
+import ma.glasnost.orika.impl.util.StringUtil;
+
+/**
+ * Finds constructor with param names that follow a prefix naming convention. For instance
+ * p-prefixed param names - (pName, pAge).
+ *
+ * @author Stanislav Petrov
+ */
+public class PrefixParamConstructorResolverStrategy extends SimpleConstructorResolverStrategy {
+
+    @Override
+    protected String[] mapTargetParamNames(String[] parameterNames) {
+        final String[] mappedParamNames = new String[parameterNames.length];
+        for (int idx = 0; idx < parameterNames.length; idx++) {
+            mappedParamNames[idx] = StringUtil.uncapitalize(parameterNames[idx].substring(1));
+        }
+        return mappedParamNames;
+    }
+}
diff --git a/core/src/main/java/ma/glasnost/orika/constructor/SimpleConstructorResolverStrategy.java b/core/src/main/java/ma/glasnost/orika/constructor/SimpleConstructorResolverStrategy.java
index 6e84ce23..5f79a8b7 100644
--- a/core/src/main/java/ma/glasnost/orika/constructor/SimpleConstructorResolverStrategy.java
+++ b/core/src/main/java/ma/glasnost/orika/constructor/SimpleConstructorResolverStrategy.java
@@ -17,7 +17,12 @@
  */
 package ma.glasnost.orika.constructor;
 
-import static ma.glasnost.orika.impl.Specifications.aMappingOfTheRequiredClassProperty;
+import com.thoughtworks.paranamer.AdaptiveParanamer;
+import com.thoughtworks.paranamer.AnnotationParanamer;
+import com.thoughtworks.paranamer.BytecodeReadingParanamer;
+import com.thoughtworks.paranamer.CachingParanamer;
+import com.thoughtworks.paranamer.ParameterNamesNotFoundException;
+import com.thoughtworks.paranamer.Paranamer;
 
 import java.lang.reflect.Constructor;
 import java.util.ArrayList;
@@ -37,12 +42,7 @@ import ma.glasnost.orika.metadata.Property;
 import ma.glasnost.orika.metadata.Type;
 import ma.glasnost.orika.metadata.TypeFactory;
 
-import com.thoughtworks.paranamer.AdaptiveParanamer;
-import com.thoughtworks.paranamer.AnnotationParanamer;
-import com.thoughtworks.paranamer.BytecodeReadingParanamer;
-import com.thoughtworks.paranamer.CachingParanamer;
-import com.thoughtworks.paranamer.ParameterNamesNotFoundException;
-import com.thoughtworks.paranamer.Paranamer;
+import static ma.glasnost.orika.impl.Specifications.aMappingOfTheRequiredClassProperty;
 
 /**
  * SimpleConstructorResolverStrategy attempts to resolve the appropriate constructor
@@ -53,195 +53,190 @@ import com.thoughtworks.paranamer.Paranamer;
  * property names of the destination class
  * <li>Return the first constructor in the list
  * </ol>
- *
  */
 public class SimpleConstructorResolverStrategy implements ConstructorResolverStrategy {
-    
-	private Paranamer paranamer = new CachingParanamer(new AdaptiveParanamer(new BytecodeReadingParanamer(), new AnnotationParanamer()));
-	
-    @SuppressWarnings({ "unchecked" })
+
+    private final Paranamer paranamer;
+
+    public SimpleConstructorResolverStrategy() {
+        paranamer = new CachingParanamer(new AdaptiveParanamer(new BytecodeReadingParanamer(), new AnnotationParanamer()));
+    }
+
+    @SuppressWarnings({"unchecked"})
     public <T, A, B> ConstructorMapping<T> resolve(ClassMap<A, B> classMap, Type<T> sourceType) {
         boolean aToB = classMap.getBType().equals(sourceType);
-        
         Type<?> targetClass = aToB ? classMap.getBType() : classMap.getAType();
         Type<?> sourceClass = aToB ? classMap.getAType() : classMap.getBType();
-        
         String[] declaredParameterNames = aToB ? classMap.getConstructorB() : classMap.getConstructorA();
-        
-        Map<String, FieldMap> targetParameters = new LinkedHashMap<String, FieldMap>();
-        if (declaredParameterNames != null) {
-        	/*
-        	 * An override to the property names was provided
-        	 */
-        	Set<FieldMap> fields = new HashSet<FieldMap>(classMap.getFieldsMapping());
-        	for (String arg: declaredParameterNames) {
-        		Iterator<FieldMap> iter = fields.iterator();
-        		while(iter.hasNext()) {
-        			FieldMap fieldMap = iter.next();
-        			if (!fieldMap.is(aMappingOfTheRequiredClassProperty())) {
-	        			if ( !aToB) {
-	        				fieldMap = fieldMap.flip();
-	        			}
-	        			if (fieldMap.getDestination().getName().equals(arg)) {
-	        				targetParameters.put(arg, fieldMap);
-	        				iter.remove();
-	        			}
-        			}
-        		}
-        	}
-        } else {
-        	/*
-        	 * Determine the set of constructor argument names
-        	 * from the field mapping
-        	 */
-        	for(FieldMap fieldMap: classMap.getFieldsMapping()) {
-        		if (!fieldMap.is(aMappingOfTheRequiredClassProperty())) {
-        			if (!aToB) {
-        				fieldMap = fieldMap.flip();
-        			}
-	        		targetParameters.put(fieldMap.getDestination().getName(), fieldMap);
-        		}
-        	}
-        	
-        }
-        
+
+        Map<String, FieldMap> targetParameters = getTargetParams(classMap.getFieldsMapping(), aToB, declaredParameterNames);
+        boolean byDefault = declaredParameterNames == null;
         boolean foundDeclaredConstructor = false;
-        Constructor<T>[] constructors = (Constructor<T>[]) targetClass.getRawType().getDeclaredConstructors();
-        TreeMap<Integer, ConstructorMapping<T>> constructorsByMatchedParams = new TreeMap<Integer, ConstructorMapping<T>>();
-        for (Constructor<T> constructor: constructors) {
-        	ConstructorMapping<T> constructorMapping = new ConstructorMapping<T>();
-        	constructorMapping.setDeclaredParameters(declaredParameterNames);
-        	boolean byDefault = declaredParameterNames == null;
-        	
-        	try {
-        		/*
-        		 * 1) A constructor's parameters are all matched by known parameter names
-        		 * 2) ...
-        		 */
-        		String[] parameterNames = paranamer.lookupParameterNames(constructor);
-        		if (Arrays.equals(parameterNames, declaredParameterNames)) {
-        		    foundDeclaredConstructor = true;
-        		}
-        		java.lang.reflect.Type[] genericParameterTypes = constructor.getGenericParameterTypes();
-        		Type<?>[] parameterTypes = new Type[genericParameterTypes.length];
-        		constructorMapping.setParameterNameInfoAvailable(true);
-        		if (targetParameters.keySet().containsAll(Arrays.asList(parameterNames))) {
-        		    
-        		    constructorMapping.setConstructor(constructor);
-        			for (int i=0; i < parameterNames.length; ++i) {
-        				String parameterName = parameterNames[i];
-        				parameterTypes[i] = TypeFactory.valueOf(genericParameterTypes[i]);
-        				FieldMap existingField = targetParameters.get(parameterName);
-        				FieldMap argumentMap = mapConstructorArgument(existingField, parameterTypes[i], byDefault);
-        				constructorMapping.getMappedFields().add(argumentMap);
-        			}
-        			constructorMapping.setParameterTypes(parameterTypes);
-        			constructorsByMatchedParams.put(parameterNames.length*1000, constructorMapping);
-        		}
-        	} catch (ParameterNamesNotFoundException e) {
-        		/*
-        		 * Could not find parameter names of the constructors; attempt to match constructors
-        		 * based on the types of the destination properties
-        		 */
-        	     List<FieldMap> targetTypes = new ArrayList<FieldMap>(targetParameters.values());
-    	    	 int matchScore = 0;
-    	    	 int exactMatches = 0;
-    	    	 java.lang.reflect.Type[] params = constructor.getGenericParameterTypes();
-    	    	 Type<?>[] parameterTypes = new Type[params.length];
-    	    	 
-    	    	 if (targetTypes.size() >= parameterTypes.length) {
-            	     for (int i=0; i < params.length; ++i) {
-            	    	java.lang.reflect.Type param = params[i];
-            	    	
-            	    	parameterTypes[i] = TypeFactory.valueOf(param);
-        	    		for (Iterator<FieldMap> iter = targetTypes.iterator(); iter.hasNext();) {
-        	    			FieldMap fieldMap = iter.next();
-        	    			Type<?> targetType = fieldMap.getDestination().getType();
-        	    			if ((parameterTypes[i].equals(targetType) && ++exactMatches != 0) 
-        	    			        || parameterTypes[i].isAssignableFrom(targetType) ) {
-        	    				++matchScore;
-        	    				
-        	    				String parameterName = fieldMap.getDestination().getName();
-                				FieldMap existingField = targetParameters.get(parameterName);
-                				FieldMap argumentMap = mapConstructorArgument(existingField, parameterTypes[i], byDefault);
-                				constructorMapping.getMappedFields().add(argumentMap);
-        	    				
-        	    				iter.remove();
-        	    				break;
-        	    			} 
-        	    		}
-        	    	 }
-            		 constructorMapping.setParameterTypes(parameterTypes);
-            	     constructorMapping.setConstructor(constructor);
-            	     constructorMapping.setDeclaredParameters(declaredParameterNames);
-            	     constructorsByMatchedParams.put((matchScore*1000 + exactMatches), constructorMapping); 
-    	    	 }
-        	}
+
+        final Constructor<T>[] constructors = (Constructor<T>[]) targetClass.getRawType().getDeclaredConstructors();
+        final TreeMap<Integer, ConstructorMapping<T>> constructorsByMatchedParams = new TreeMap<Integer, ConstructorMapping<T>>();
+        for (Constructor<T> constructor : constructors) {
+            final ConstructorMapping<T> constructorMapping = new ConstructorMapping<T>();
+            constructorMapping.setDeclaredParameters(declaredParameterNames);
+            final java.lang.reflect.Type[] genericParamTypes = constructor.getGenericParameterTypes();
+            try {
+                final String[] parameterNames = mapTargetParamNames(paranamer.lookupParameterNames(constructor));
+                constructorMapping.setParameterNameInfoAvailable(true);
+                if (targetParameters.keySet().containsAll(Arrays.asList(parameterNames))) {
+                    foundDeclaredConstructor = true;
+                    constructorMapping.setConstructor(constructor);
+                    mapConstructorArgs(constructorMapping, targetParameters, parameterNames, genericParamTypes, byDefault);
+                    constructorsByMatchedParams.put(parameterNames.length * 1000, constructorMapping);
+                }
+            } catch (ParameterNamesNotFoundException e) {
+                /*
+                 * Could not find parameter names of the constructors; attempt to match constructors
+                 * based on the types of the destination properties
+                 */
+                if (targetParameters.size() >= genericParamTypes.length) {
+                    matchByDestParamTypes(constructorMapping, targetParameters, genericParamTypes, byDefault, constructorsByMatchedParams);
+                    constructorMapping.setConstructor(constructor);
+                }
+            }
         }
-        
+        return prepareMatchedConstructorMapping(constructorsByMatchedParams, targetClass, sourceClass, declaredParameterNames, foundDeclaredConstructor, constructors);
+    }
+
+    /**
+     * Maps parameter names from target constructor.
+     *
+     * @param parameterNames Original parameter names.
+     * @return Changed parameter names.
+     */
+    protected String[] mapTargetParamNames(String[] parameterNames) {
+        return parameterNames;
+    }
+
+    private <T> ConstructorMapping<T> prepareMatchedConstructorMapping(TreeMap<Integer, ConstructorMapping<T>> constructorsByMatchedParams, Type<?> targetClass, Type<?> sourceClass, String[] declaredParameterNames, boolean foundDeclaredConstructor, Constructor<T>[] constructors) {
         if (constructorsByMatchedParams.size() > 0) {
             return constructorsByMatchedParams.get(constructorsByMatchedParams.lastKey());
         } else if (declaredParameterNames != null) {
-            if (foundDeclaredConstructor) {
-                StringBuilder msg = new StringBuilder();
-                msg.append("Declared constructor for ")
-                    .append(targetClass)
-                    .append("(");
-                for (String param: declaredParameterNames) {
-                    msg.append('"').append(param).append('"').append(", ");
+            return throwNotMatchedTargetConstructorEx(targetClass, sourceClass, declaredParameterNames, foundDeclaredConstructor);
+        } else {
+            /*
+             * User didn't specify any constructor, and we couldn't find any that seem compatible;
+             * TODO: can we really do anything in this case? maybe we should just throw an error
+             * describing some alternative options like creating a Converter or declaring their own
+             * custom ObjectFactory...
+             */
+            final ConstructorMapping<T> defaultMapping = new ConstructorMapping<T>();
+            defaultMapping.setConstructor(constructors.length == 0 ? null : constructors[0]);
+            return defaultMapping;
+        }
+    }
+
+    private <T> ConstructorMapping<T> throwNotMatchedTargetConstructorEx(Type<?> targetClass, Type<?> sourceClass, String[] declaredParameterNames, boolean foundDeclaredConstructor) {
+        final String errMsg;
+        final String declaredParamNamesTxt = Arrays.toString(declaredParameterNames);
+        if (foundDeclaredConstructor) {
+            errMsg = "Declared constructor for " +
+                    targetClass +
+                    "(" + declaredParamNamesTxt + ")" +
+                    " could not be matched to the source fields of " + sourceClass;
+        } else {
+            errMsg =
+                    "No constructors found for " + targetClass +
+                            " matching the specified constructor parameters " +
+                            (declaredParameterNames.length == 0 ? "(no-arg constructor)" : "(" + declaredParamNamesTxt + ")");
+        }
+        throw new IllegalStateException(errMsg);
+    }
+
+    private <T> void matchByDestParamTypes(ConstructorMapping<T> constructorMapping, Map<String, FieldMap> targetParameters, java.lang.reflect.Type[] genericParamTypes, boolean byDefault, TreeMap<Integer, ConstructorMapping<T>> constructorsByMatchedParams) {
+        final List<FieldMap> targetTypes = new ArrayList<FieldMap>(targetParameters.values());
+        int matchScore = 0;
+        int exactMatches = 0;
+        Type<?>[] parameterTypes = new Type[genericParamTypes.length];
+        for (int i = 0; i < genericParamTypes.length; ++i) {
+            java.lang.reflect.Type param = genericParamTypes[i];
+
+            parameterTypes[i] = TypeFactory.valueOf(param);
+            for (Iterator<FieldMap> iter = targetTypes.iterator(); iter.hasNext(); ) {
+                FieldMap fieldMap = iter.next();
+                Type<?> targetType = fieldMap.getDestination().getType();
+                if ((parameterTypes[i].equals(targetType) && ++exactMatches != 0)
+                        || parameterTypes[i].isAssignableFrom(targetType)) {
+                    ++matchScore;
+
+                    String parameterName = fieldMap.getDestination().getName();
+                    FieldMap existingField = targetParameters.get(parameterName);
+                    FieldMap argumentMap = mapConstructorArgument(existingField, parameterTypes[i], byDefault);
+                    constructorMapping.getMappedFields().add(argumentMap);
+
+                    iter.remove();
+                    break;
                 }
-                msg.setLength(msg.length()-2);
-                msg.append(")")
-                    .append(" could not be matched to the source fields of ")
-                    .append(sourceClass);
-                
-                throw new IllegalStateException(msg.toString());
-            } else {
-                StringBuilder msg = new StringBuilder();
-                msg.append("No constructors found for ")
-                    .append(targetClass)
-                    .append(" matching the specified constructor parameters (");
-                if (declaredParameterNames.length==0) {
-                    for (String param: declaredParameterNames) {
-                        msg.append('"').append(param).append('"').append(", ");
+            }
+        }
+        constructorMapping.setParameterTypes(parameterTypes);
+        constructorsByMatchedParams.put((matchScore * 1000 + exactMatches), constructorMapping);
+    }
+
+    private <T> void mapConstructorArgs(ConstructorMapping<T> constructorMapping, Map<String, FieldMap> targetParameters, String[] parameterNames, java.lang.reflect.Type[] genericParameterTypes, boolean byDefault) {
+        Type<?>[] parameterTypes = new Type[genericParameterTypes.length];
+        for (int i = 0; i < parameterNames.length; ++i) {
+            String parameterName = parameterNames[i];
+            parameterTypes[i] = TypeFactory.valueOf(genericParameterTypes[i]);
+            FieldMap existingField = targetParameters.get(parameterName);
+            FieldMap argumentMap = mapConstructorArgument(existingField, parameterTypes[i], byDefault);
+            constructorMapping.getMappedFields().add(argumentMap);
+        }
+        constructorMapping.setParameterTypes(parameterTypes);
+    }
+
+    private Map<String, FieldMap> getTargetParams(Set<FieldMap> fieldMaps, boolean aToB, String[] declaredParameterNames) {
+        final Map<String, FieldMap> targetParameters = new LinkedHashMap<String, FieldMap>();
+        if (declaredParameterNames != null) {
+            /*
+             * An override to the property names was provided
+             */
+            final Set<FieldMap> fields = new HashSet<FieldMap>(fieldMaps);
+            for (String arg : declaredParameterNames) {
+                Iterator<FieldMap> iter = fields.iterator();
+                while (iter.hasNext()) {
+                    FieldMap fieldMap = iter.next();
+                    if (fieldMap.is(aMappingOfTheRequiredClassProperty())) {
+                        continue;
+                    }
+                    if (!aToB) {
+                        fieldMap = fieldMap.flip();
+                    }
+                    if (fieldMap.getDestination().getName().equals(arg)) {
+                        targetParameters.put(arg, fieldMap);
+                        iter.remove();
                     }
-                    msg.setLength(msg.length()-1);
-                } else {
-                    msg.append("<default constructor>");
                 }
-                msg.append(")")
-                    .append(" could not be matched to the source fields of ")
-                    .append(sourceClass);
-                
-                
-            	throw new IllegalArgumentException("No constructors found for " + targetClass + 
-            			" matching the specified constructor parameters " + Arrays.toString(declaredParameterNames) +
-            			(declaredParameterNames.length == 0 ? " (no-arg constructor)": ""));
             }
         } else {
-        
-	        /* 
-	         * User didn't specify any constructor, and we couldn't find any that seem compatible;
-	         * TODO: can we really do anything in this case? maybe we should just throw an error 
-	         * describing some alternative options like creating a Converter or declaring their own
-	         * custom ObjectFactory...
-	         * */
-        	
-	        ConstructorMapping<T> defaultMapping = new ConstructorMapping<T>();
-	        defaultMapping.setConstructor(constructors.length == 0 ? null : constructors[0]);
-	        return defaultMapping;
+            /*
+             * Determine the set of constructor argument names from the field mapping.
+             */
+            for (FieldMap fieldMap : fieldMaps) {
+                if (fieldMap.is(aMappingOfTheRequiredClassProperty())) {
+                    continue;
+                }
+                if (!aToB) {
+                    fieldMap = fieldMap.flip();
+                }
+                targetParameters.put(fieldMap.getDestination().getName(), fieldMap);
+            }
         }
+        return targetParameters;
+    }
+
+    private FieldMap mapConstructorArgument(FieldMap existing, Type<?> argumentType, boolean byDefault) {
+        final Property destProp = new Property.Builder()
+                .name(existing.getDestination().getName())
+                .getter(existing.getDestination().getName())
+                .type(argumentType)
+                .build();
+        return new FieldMap(existing.getSource(), destProp, null,
+                null, MappingDirection.A_TO_B, false, existing.getConverterId(),
+                byDefault, null, null);
     }
-    
-	private FieldMap mapConstructorArgument(FieldMap existing, Type<?> argumentType, boolean byDefault) {
-		Property destProp = new Property.Builder()
-        		.name(existing.getDestination().getName())
-        		.getter(existing.getDestination().getName())
-    		    .type(argumentType)
-    		    .build();
-		FieldMap fieldMap = new FieldMap(existing.getSource(), destProp, null,
-				null, MappingDirection.A_TO_B, false, existing.getConverterId(), 
-				byDefault, null, null);
-		return fieldMap;
-	}
 }
diff --git a/core/src/main/java/ma/glasnost/orika/impl/util/StringUtil.java b/core/src/main/java/ma/glasnost/orika/impl/util/StringUtil.java
index 9610188b..63d84eb6 100644
--- a/core/src/main/java/ma/glasnost/orika/impl/util/StringUtil.java
+++ b/core/src/main/java/ma/glasnost/orika/impl/util/StringUtil.java
@@ -23,20 +23,61 @@ import static java.lang.Character.isJavaIdentifierStart;
 
 /**
  * @author matt.deboer@gmail.com
- *
  */
 public class StringUtil {
-    
+
     public static String capitalize(String string) {
         if ("".equals(string)) {
             return "";
         } else if (string.length() == 1) {
-            return string.substring(0,1).toUpperCase();
+            return string.substring(0, 1).toUpperCase();
         } else {
-            return string.substring(0,1).toUpperCase() + string.substring(1);
+            return string.substring(0, 1).toUpperCase() + string.substring(1);
         }
     }
 
+    /**
+     * Remark: Copied from commons-lang3 v3.5
+     *
+     * <p>Uncapitalizes a String, changing the first character to lower case as per {@link
+     * Character#toLowerCase(char)}. No other characters are changed.</p>
+     *
+     * <p>For a word based algorithm, see {@link org.apache.commons.lang3.text.WordUtils#uncapitalize(String)}.
+     * A {@code null} input String returns {@code null}.</p>
+     *
+     * <pre>
+     * StringUtils.uncapitalize(null)  = null
+     * StringUtils.uncapitalize("")    = ""
+     * StringUtils.uncapitalize("cat") = "cat"
+     * StringUtils.uncapitalize("Cat") = "cat"
+     * StringUtils.uncapitalize("CAT") = "cAT"
+     * </pre>
+     *
+     * @param str the String to uncapitalize, may be null
+     * @return the uncapitalized String, {@code null} if null String input
+     * @see org.apache.commons.lang3.text.WordUtils#uncapitalize(String)
+     * @see #capitalize(String)
+     * @since 2.0
+     */
+    public static String uncapitalize(final String str) {
+        int strLen;
+        if (str == null || (strLen = str.length()) == 0) {
+            return str;
+        }
+
+        final char firstChar = str.charAt(0);
+        final char newChar = Character.toLowerCase(firstChar);
+        if (firstChar == newChar) {
+            // already uncapitalized
+            return str;
+        }
+
+        char[] newChars = new char[strLen];
+        newChars[0] = newChar;
+        str.getChars(1, strLen, newChars, 1);
+        return String.valueOf(newChars);
+    }
+
     public static String toValidVariableName(String string) {
         StringBuilder output = new StringBuilder();
 
@@ -44,12 +85,11 @@ public class StringUtil {
             output.append("_");
         }
 
-        for (int i=0; i < string.length(); i++) {
+        for (int i = 0; i < string.length(); i++) {
             char character = string.charAt(i);
             if (isJavaIdentifierPart(character)) {
                 output.append(character);
-            }
-            else {
+            } else {
                 output.append("_");
             }
         }
diff --git a/pom.xml b/pom.xml
index 91eae0ee..a05df91a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -57,7 +57,7 @@
         <javassist.version>3.19.0-GA</javassist.version>
         <springframework.version>3.1.1.RELEASE</springframework.version>
         <hibernate.version>3.3.2.GA</hibernate.version>
-        <junit.version>4.11</junit.version>
+        <junit.version>4.12</junit.version>
         <easymock.version>3.0</easymock.version>
         <wagon-svn.version>1.8</wagon-svn.version>
         <h2.version>1.3.154</h2.version>


Test patch:
diff --git a/tests/src/main/java/DefaultPackageTestCase.java b/tests/src/main/java/DefaultPackageTestCase.java
index 88125d9b..67af1fa2 100644
--- a/tests/src/main/java/DefaultPackageTestCase.java
+++ b/tests/src/main/java/DefaultPackageTestCase.java
@@ -1,12 +1,12 @@
+import org.junit.Assert;
+import org.junit.Test;
+
 import ma.glasnost.orika.MapperFacade;
 import ma.glasnost.orika.MapperFactory;
 import ma.glasnost.orika.OrikaSystemProperties;
 import ma.glasnost.orika.impl.DefaultMapperFactory;
 import ma.glasnost.orika.impl.generator.EclipseJdtCompilerStrategy;
 
-import org.junit.Assert;
-import org.junit.Test;
-
 public class DefaultPackageTestCase {
     public static class Label {
         private String text;
@@ -34,7 +34,7 @@ public class DefaultPackageTestCase {
 
     @Test
     public void test() {
-        
+
         System.setProperty(OrikaSystemProperties.COMPILER_STRATEGY,
                 EclipseJdtCompilerStrategy.class.getName());
 
@@ -50,25 +50,6 @@ public class DefaultPackageTestCase {
         Label label = mapper.map(xmlLabel1, Label.class);
 
         Assert.assertEquals(xmlLabel1.getText(), label.getText());
-        
-    }
-    
-    public static void main(String[] args) {
-
-        System.setProperty(OrikaSystemProperties.COMPILER_STRATEGY,
-                EclipseJdtCompilerStrategy.class.getName());
-
-        System.setProperty(OrikaSystemProperties.WRITE_SOURCE_FILES, "true");
-        System.setProperty(OrikaSystemProperties.WRITE_CLASS_FILES, "true");
-
-        MapperFactory factory = new DefaultMapperFactory.Builder().build();
-        MapperFacade mapper = factory.getMapperFacade();
-
-        XmlLabel xmlLabel1 = new XmlLabel();
-        xmlLabel1.setText("label");
-
-        Label label = mapper.map(xmlLabel1, Label.class);
 
-        System.out.println("Done!");
     }
-}
\ No newline at end of file
+}
diff --git a/tests/src/main/java/ma/glasnost/orika/test/constructor/ConstructorMappingTestCase.java b/tests/src/main/java/ma/glasnost/orika/test/constructor/ConstructorMappingTestCase.java
index 20ed99b7..d9b46177 100644
--- a/tests/src/main/java/ma/glasnost/orika/test/constructor/ConstructorMappingTestCase.java
+++ b/tests/src/main/java/ma/glasnost/orika/test/constructor/ConstructorMappingTestCase.java
@@ -18,8 +18,9 @@
 
 package ma.glasnost.orika.test.constructor;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
+import org.apache.commons.collections.list.TreeList;
+import org.junit.Assert;
+import org.junit.Test;
 
 import java.net.URL;
 import java.net.URLStreamHandler;
@@ -30,11 +31,14 @@ import java.util.Date;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
 
-
 import ma.glasnost.orika.DefaultFieldMapper;
 import ma.glasnost.orika.MapperFacade;
 import ma.glasnost.orika.MapperFactory;
+import ma.glasnost.orika.MappingException;
+import ma.glasnost.orika.constructor.ConstructorResolverStrategy;
+import ma.glasnost.orika.constructor.PrefixParamConstructorResolverStrategy;
 import ma.glasnost.orika.converter.builtin.DateToStringConverter;
+import ma.glasnost.orika.impl.DefaultMapperFactory;
 import ma.glasnost.orika.metadata.Type;
 import ma.glasnost.orika.test.MappingUtil;
 import ma.glasnost.orika.test.common.types.TestCaseClasses.Author;
@@ -60,12 +64,12 @@ import ma.glasnost.orika.test.constructor.TestCaseClasses.Person;
 import ma.glasnost.orika.test.constructor.TestCaseClasses.PersonVO;
 import ma.glasnost.orika.test.constructor.TestCaseClasses.PersonVO2;
 import ma.glasnost.orika.test.constructor.TestCaseClasses.PersonVO3;
+import ma.glasnost.orika.test.constructor.TestCaseClasses.PersonVO4;
 import ma.glasnost.orika.test.constructor.TestCaseClasses.PrimitiveNumberHolder;
 import ma.glasnost.orika.test.constructor.TestCaseClasses.WrapperHolder;
 
-import org.apache.commons.collections.list.TreeList;
-import org.junit.Test;
-import org.junit.Assert;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
 
 public class ConstructorMappingTestCase {
     
@@ -465,8 +469,41 @@ public class ConstructorMappingTestCase {
     	Assert.assertEquals(dto1.portX, url.getPort());
     	
     }
-    
-    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+	@Test
+	public void testFindConstructor_mapParamNames_ok() {
+		ConstructorResolverStrategy constructorStrategy = new PrefixParamConstructorResolverStrategy();
+		DefaultMapperFactory.Builder builder = new DefaultMapperFactory.Builder();
+		builder.constructorResolverStrategy(constructorStrategy);
+		MapperFactory factory = builder.build();
+
+		testPersonV04Mapping(factory);
+	}
+
+	@Test(expected = MappingException.class)
+	public void testFindConstructor_doNotMapParamNames_ex() {
+		testPersonV04Mapping(MappingUtil.getMapperFactory());
+	}
+
+	private void testPersonV04Mapping(MapperFactory factory) {
+		Person person = newPerson();
+
+		PersonVO4 vo = factory.getMapperFacade().map(person, PersonVO4.class);
+
+		Assert.assertEquals(person.getFirstName(), vo.getFirstName());
+		Assert.assertEquals(person.getLastName(), vo.getLastName());
+		Assert.assertTrue(person.getAge() == vo.getAge());
+	}
+
+	private Person newPerson() {
+		Person person = new Person();
+		person.setFirstName("Abdelkrim");
+		person.setLastName("EL KHETTABI");
+		person.setAge(31L);
+		return person;
+	}
+
+	// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     // Common mapping validations
     // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     
diff --git a/tests/src/main/java/ma/glasnost/orika/test/constructor/TestCaseClasses.java b/tests/src/main/java/ma/glasnost/orika/test/constructor/TestCaseClasses.java
index cb57c4f2..1c0265ba 100644
--- a/tests/src/main/java/ma/glasnost/orika/test/constructor/TestCaseClasses.java
+++ b/tests/src/main/java/ma/glasnost/orika/test/constructor/TestCaseClasses.java
@@ -18,17 +18,17 @@
 
 package ma.glasnost.orika.test.constructor;
 
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.apache.commons.lang.builder.HashCodeBuilder;
+import org.apache.commons.lang.builder.ToStringBuilder;
+import org.apache.commons.lang.builder.ToStringStyle;
+
 import java.text.SimpleDateFormat;
 import java.util.Date;
 import java.util.concurrent.TimeUnit;
 
 import ma.glasnost.orika.test.common.types.TestCaseClasses.PrimitiveWrapperHolder;
 
-import org.apache.commons.lang.builder.EqualsBuilder;
-import org.apache.commons.lang.builder.HashCodeBuilder;
-import org.apache.commons.lang.builder.ToStringBuilder;
-import org.apache.commons.lang.builder.ToStringStyle;
-
 public interface TestCaseClasses {
 
 	public static class Person {
@@ -181,7 +181,32 @@ public interface TestCaseClasses {
             return dateOfBirth;
         }
     }
-    
+
+    static class PersonVO4 {
+
+        private final String firstName;
+        private final String lastName;
+        private final long age;
+
+        public PersonVO4(String pFirstName, String pLastName, long pAge) {
+            firstName = pFirstName;
+            lastName = pLastName;
+            age = pAge;
+        }
+
+        public String getFirstName() {
+            return firstName;
+        }
+
+        public String getLastName() {
+            return lastName;
+        }
+
+        public long getAge() {
+            return age;
+        }
+    }
+
     public class NestedPrimitiveHolder {
     	
     	private char charValue;
diff --git a/tests/src/main/java/ma/glasnost/orika/test/util/StringUtilTest.java b/tests/src/main/java/ma/glasnost/orika/test/util/StringUtilTest.java
index 01eb7a36..e571d704 100644
--- a/tests/src/main/java/ma/glasnost/orika/test/util/StringUtilTest.java
+++ b/tests/src/main/java/ma/glasnost/orika/test/util/StringUtilTest.java
@@ -2,10 +2,15 @@ package ma.glasnost.orika.test.util;
 
 import org.junit.Test;
 
+import ma.glasnost.orika.impl.util.StringUtil;
+
 import static ma.glasnost.orika.impl.util.StringUtil.toValidVariableName;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
 
 public class StringUtilTest {
+    private static final String FOO_UNCAP = "foo";
+    private static final String FOO_CAP = "Foo";
 
     @Test
     public void to_valid_variable_name_replaces_all_invalid_characters_with_underscore() {
@@ -16,4 +21,26 @@ public class StringUtilTest {
     public void to_valid_variable_name_add_underscore__when_string_start_with_number() {
         assertEquals("_42", toValidVariableName("42"));
     }
+
+    /**
+     * Remark: Copied from commons-lang3 v3.5
+     */
+    @Test
+    public void testUnCapitalize() {
+        assertNull(StringUtil.uncapitalize(null));
+
+        assertEquals("uncapitalize(String) failed",
+                FOO_UNCAP, StringUtil.uncapitalize(FOO_CAP));
+        assertEquals("uncapitalize(string) failed",
+                FOO_UNCAP, StringUtil.uncapitalize(FOO_UNCAP));
+        assertEquals("uncapitalize(empty-string) failed",
+                "", StringUtil.uncapitalize(""));
+        assertEquals("uncapitalize(single-char-string) failed",
+                "x", StringUtil.uncapitalize("X"));
+
+        // Examples from uncapitalize Javadoc
+        assertEquals("cat", StringUtil.uncapitalize("cat"));
+        assertEquals("cat", StringUtil.uncapitalize("Cat"));
+        assertEquals("cAT", StringUtil.uncapitalize("CAT"));
+    }
 }