diff --git a/spring-binding/src/main/java/org/springframework/binding/convert/ConversionService.java b/spring-binding/src/main/java/org/springframework/binding/convert/ConversionService.java
index 4a0f6c23..d24bd751 100644
--- a/spring-binding/src/main/java/org/springframework/binding/convert/ConversionService.java
+++ b/spring-binding/src/main/java/org/springframework/binding/convert/ConversionService.java
@@ -15,6 +15,8 @@
*/
package org.springframework.binding.convert;
+import java.util.Set;
+
/**
* A service interface for retrieving type conversion executors. The returned command objects are thread-safe and may be
* safely cached for use by client code.
@@ -35,8 +37,8 @@ public interface ConversionService {
public Object executeConversion(Object source, Class targetClass) throws ConversionException;
/**
- * Return the conversion executor capable of converting source objects of the specified sourceClass
- * to instances of the targetClass.
+ * Return the default conversion executor capable of converting source objects of the specified
+ * sourceClass to instances of the targetClass.
*
* The returned ConversionExecutor is thread-safe and may safely be cached for use in client code.
* @param sourceClass the source class to convert from (required)
@@ -47,17 +49,31 @@ public interface ConversionService {
public ConversionExecutor getConversionExecutor(Class sourceClass, Class targetClass)
throws ConversionExecutorNotFoundException;
+ /**
+ * Return the custom conversion executor capable of converting source objects of the specified
+ * sourceClass to instances of the targetClass.
+ *
+ * The returned ConversionExecutor is thread-safe and may safely be cached for use in client code.
+ * @param id the id of the custom conversion executor (required)
+ * @param sourceClass the source class to convert from (required)
+ * @param targetClass the target class to convert to (required)
+ * @return the executor that can execute instance type conversion, never null
+ * @throws ConversionExecutorNotFoundException when no suitable conversion executor could be found
+ */
+ public ConversionExecutor getConversionExecutor(String id, Class sourceClass, Class targetClass)
+ throws ConversionExecutorNotFoundException;
+
/**
* Return all conversion executors capable of converting from the provided sourceClass. For
- * example, ConversionExecutor[] getConversionExecutor(String.class) would return all converters that
- * convert from String to some other Object.
+ * example, getConversionExecutor(String.class) would return all converters that convert from String
+ * to some other Object. Mainly useful for adapting a set of converters to some other environment.
* @param sourceClass the source class converting from
* @return the conversion executors that can convert from that source class
*/
- public ConversionExecutor[] getConversionExecutors(Class sourceClass);
+ public Set getConversionExecutors(Class sourceClass);
/**
- * Lookup a class by it alias. For example, long for java.lang.Long
+ * Lookup a class by its well-known alias. For example, long for java.lang.Long
* @param alias the class alias
* @return the class, or null if no alias exists
*/
diff --git a/spring-binding/src/main/java/org/springframework/binding/convert/converters/FormattedStringToNumber.java b/spring-binding/src/main/java/org/springframework/binding/convert/converters/FormattedStringToNumber.java
index 379aff32..0a7073f2 100644
--- a/spring-binding/src/main/java/org/springframework/binding/convert/converters/FormattedStringToNumber.java
+++ b/spring-binding/src/main/java/org/springframework/binding/convert/converters/FormattedStringToNumber.java
@@ -50,6 +50,10 @@ public class FormattedStringToNumber extends StringToObject {
private boolean lenient;
+ public FormattedStringToNumber() {
+ super(Number.class);
+ }
+
public FormattedStringToNumber(Class numberClass) {
super(numberClass);
}
diff --git a/spring-binding/src/main/java/org/springframework/binding/convert/service/GenericConversionService.java b/spring-binding/src/main/java/org/springframework/binding/convert/service/GenericConversionService.java
index bff36789..bc50561d 100644
--- a/spring-binding/src/main/java/org/springframework/binding/convert/service/GenericConversionService.java
+++ b/spring-binding/src/main/java/org/springframework/binding/convert/service/GenericConversionService.java
@@ -16,13 +16,12 @@
package org.springframework.binding.convert.service;
import java.lang.reflect.Modifier;
-import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
-import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -52,6 +51,12 @@ public class GenericConversionService implements ConversionService {
*/
private final Map sourceClassConverters = new HashMap();
+ /**
+ * A map of custom converters. Custom converters are assigned a unique identifier that can be used to lookup the
+ * converter. This allows multiple converters for the same source->target class to be registered.
+ */
+ private final Map customConverters = new HashMap();
+
/**
* Indexes classes by well-known aliases.
*/
@@ -83,14 +88,6 @@ public class GenericConversionService implements ConversionService {
public void addConverter(Converter converter) {
Class sourceClass = converter.getSourceClass();
Class targetClass = converter.getTargetClass();
- if (sourceClass.isPrimitive()) {
- throw new IllegalArgumentException("Invalid Converter " + converter
- + "; A primitive type is not allowed for the [sourceClass] argument");
- }
- if (targetClass.isPrimitive()) {
- throw new IllegalArgumentException("Invalid Converter " + converter
- + "; A primitive type is not allowed for the [targetClass] argument");
- }
Map sourceMap = getSourceMap(sourceClass);
sourceMap.put(targetClass, converter);
if (converter instanceof TwoWayConverter) {
@@ -99,6 +96,15 @@ public class GenericConversionService implements ConversionService {
}
}
+ /**
+ * Add given custom converter to this conversion service.
+ * @param id the id of the custom converter instance
+ * @param converter the converter
+ */
+ public void addConverter(String id, Converter converter) {
+ customConverters.put(id, converter);
+ }
+
/**
* Add an alias for given target type.
*/
@@ -154,12 +160,42 @@ public class GenericConversionService implements ConversionService {
return parent.getConversionExecutor(sourceClass, targetClass);
} else {
throw new ConversionExecutorNotFoundException(sourceClass, targetClass,
- "No ConversionExecutor found for converting from sourceClass '" + sourceClass.getName()
- + "' to target class '" + targetClass.getName() + "'");
+ "No ConversionExecutor found for converting from sourceClass [" + sourceClass.getName()
+ + "] to target class [" + targetClass.getName() + "]");
}
}
}
+ public ConversionExecutor getConversionExecutor(String id, Class sourceClass, Class targetClass)
+ throws ConversionExecutorNotFoundException {
+ Converter converter = (Converter) customConverters.get(id);
+ if (converter == null) {
+ throw new ConversionExecutorNotFoundException(sourceClass, targetClass,
+ "No custom ConversionExecutor found with id '" + id + "' for converting from sourceClass ["
+ + sourceClass.getName() + "] to targetClass [" + targetClass.getName() + "]");
+ }
+ if (converter.getSourceClass().isAssignableFrom(sourceClass)) {
+ if (!converter.getTargetClass().isAssignableFrom(targetClass)) {
+ throw new ConversionExecutorNotFoundException(sourceClass, targetClass,
+ "Custom ConversionExecutor with id '" + id + "' cannot convert from sourceClass ["
+ + sourceClass.getName() + "] to targetClass [" + targetClass.getName() + "]");
+ }
+ return new StaticConversionExecutor(sourceClass, targetClass, converter);
+ } else if (converter.getTargetClass().isAssignableFrom(sourceClass) && converter instanceof TwoWayConverter) {
+ if (!converter.getSourceClass().isAssignableFrom(targetClass)) {
+ throw new ConversionExecutorNotFoundException(sourceClass, targetClass,
+ "Custom ConversionExecutor with id '" + id + "' cannot convert from sourceClass ["
+ + sourceClass.getName() + "] to targetClass [" + targetClass.getName() + "]");
+ }
+ TwoWayConverter twoWay = (TwoWayConverter) converter;
+ return new StaticConversionExecutor(sourceClass, targetClass, new ReverseConverter(twoWay));
+ } else {
+ throw new ConversionExecutorNotFoundException(sourceClass, targetClass,
+ "Custom ConversionExecutor with id '" + id + "' cannot convert from sourceClass ["
+ + sourceClass.getName() + "] to targetClass [" + targetClass.getName() + "]");
+ }
+ }
+
private Converter findRegisteredConverter(Class sourceClass, Class targetClass) {
if (sourceClass.isInterface()) {
LinkedList classQueue = new LinkedList();
@@ -224,20 +260,27 @@ public class GenericConversionService implements ConversionService {
// subclassing support
- public ConversionExecutor[] getConversionExecutors(Class sourceClass) {
+ public Set getConversionExecutors(Class sourceClass) {
+ Set parentExecutors;
+ if (parent != null) {
+ parentExecutors = parent.getConversionExecutors(sourceClass);
+ } else {
+ parentExecutors = Collections.EMPTY_SET;
+ }
Map sourceMap = getSourceMap(sourceClass);
- if (sourceMap.isEmpty()) {
- return new ConversionExecutor[0];
+ if (parentExecutors.isEmpty() && sourceMap.isEmpty()) {
+ return Collections.EMPTY_SET;
}
Set entries = sourceMap.entrySet();
- List conversionExecutors = new ArrayList(entries.size());
+ Set conversionExecutors = new HashSet(entries.size() + parentExecutors.size());
for (Iterator it = entries.iterator(); it.hasNext();) {
Map.Entry entry = (Map.Entry) it.next();
Class targetClass = (Class) entry.getKey();
Converter converter = (Converter) entry.getValue();
conversionExecutors.add(new StaticConversionExecutor(sourceClass, targetClass, converter));
}
- return (ConversionExecutor[]) conversionExecutors.toArray(new ConversionExecutor[entries.size()]);
+ conversionExecutors.addAll(parentExecutors);
+ return conversionExecutors;
}
/**
diff --git a/spring-binding/src/main/java/org/springframework/binding/expression/beanwrapper/BeanWrapperExpression.java b/spring-binding/src/main/java/org/springframework/binding/expression/beanwrapper/BeanWrapperExpression.java
index 049c9345..0888c269 100644
--- a/spring-binding/src/main/java/org/springframework/binding/expression/beanwrapper/BeanWrapperExpression.java
+++ b/spring-binding/src/main/java/org/springframework/binding/expression/beanwrapper/BeanWrapperExpression.java
@@ -16,6 +16,8 @@
package org.springframework.binding.expression.beanwrapper;
import java.beans.PropertyEditorSupport;
+import java.util.Iterator;
+import java.util.Set;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.beans.BeansException;
@@ -88,9 +90,9 @@ public class BeanWrapperExpression implements Expression {
public void setValue(Object context, Object value) {
try {
BeanWrapperImpl beanWrapper = new BeanWrapperImpl(context);
- ConversionExecutor[] converters = conversionService.getConversionExecutors(String.class);
- for (int i = 0; i < converters.length; i++) {
- ConversionExecutor converter = converters[i];
+ Set converters = conversionService.getConversionExecutors(String.class);
+ for (Iterator it = converters.iterator(); it.hasNext();) {
+ ConversionExecutor converter = (ConversionExecutor) it.next();
beanWrapper.registerCustomEditor(converter.getTargetClass(), new PropertyEditorConverter(converter));
}
beanWrapper.setPropertyValue(expression, value);
diff --git a/spring-binding/src/test/java/org/springframework/binding/convert/service/DefaultConversionServiceTests.java b/spring-binding/src/test/java/org/springframework/binding/convert/service/DefaultConversionServiceTests.java
index 55fac979..426678dd 100644
--- a/spring-binding/src/test/java/org/springframework/binding/convert/service/DefaultConversionServiceTests.java
+++ b/spring-binding/src/test/java/org/springframework/binding/convert/service/DefaultConversionServiceTests.java
@@ -15,13 +15,17 @@
*/
package org.springframework.binding.convert.service;
+import java.math.BigDecimal;
+import java.security.Principal;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
+import java.util.Set;
import junit.framework.TestCase;
@@ -39,6 +43,7 @@ import org.springframework.binding.format.DefaultNumberFormatFactory;
* @author Keith Donald
*/
public class DefaultConversionServiceTests extends TestCase {
+
public void testConvertCompatibleTypes() {
DefaultConversionService service = new DefaultConversionService();
List lst = new ArrayList();
@@ -85,7 +90,7 @@ public class DefaultConversionServiceTests extends TestCase {
public void testRegisterConverter() {
GenericConversionService service = new GenericConversionService();
- FormattedStringToNumber converter = new FormattedStringToNumber(Integer.class);
+ FormattedStringToNumber converter = new FormattedStringToNumber();
DefaultNumberFormatFactory numberFormatFactory = new DefaultNumberFormatFactory();
numberFormatFactory.setLocale(Locale.US);
converter.setNumberFormatFactory(numberFormatFactory);
@@ -98,6 +103,21 @@ public class DefaultConversionServiceTests extends TestCase {
assertEquals("3,000", string);
}
+ public void testRegisterCustomConverter() {
+ DefaultConversionService service = new DefaultConversionService();
+ FormattedStringToNumber converter = new FormattedStringToNumber();
+ DefaultNumberFormatFactory numberFormatFactory = new DefaultNumberFormatFactory();
+ numberFormatFactory.setLocale(Locale.US);
+ converter.setNumberFormatFactory(numberFormatFactory);
+ service.addConverter("usaNumber", converter);
+ ConversionExecutor executor = service.getConversionExecutor("usaNumber", String.class, Integer.class);
+ Integer three = (Integer) executor.execute("3,000");
+ assertEquals(3000, three.intValue());
+ ConversionExecutor executor2 = service.getConversionExecutor("usaNumber", Integer.class, String.class);
+ String string = (String) executor2.execute(new Integer(3000));
+ assertEquals("3,000", string);
+ }
+
public void testConversionPrimitive() {
DefaultConversionService service = new DefaultConversionService();
ConversionExecutor executor = service.getConversionExecutor(String.class, int.class);
@@ -200,6 +220,42 @@ public class DefaultConversionServiceTests extends TestCase {
assertEquals(new Integer(123), result[0]);
}
+ public void testGetConversionExecutorsForSource() {
+ DefaultConversionService service1 = new DefaultConversionService();
+ service1.addConverter(new CustomConverter());
+ GenericConversionService service2 = new GenericConversionService();
+ FormattedStringToNumber formatterConverter = new FormattedStringToNumber(BigDecimal.class);
+ service2.addConverter(formatterConverter);
+ service2.setParent(service1);
+ Set converters = service2.getConversionExecutors(String.class);
+ Iterator it = converters.iterator();
+ while (it.hasNext()) {
+ ConversionExecutor executor = (ConversionExecutor) it.next();
+ if (executor.getTargetClass().equals(BigDecimal.class)) {
+ StaticConversionExecutor se = (StaticConversionExecutor) executor;
+ assertSame(formatterConverter, se.getConverter());
+ }
+ }
+ assertEquals(14, converters.size());
+ }
+
+ private static class CustomConverter implements Converter {
+
+ public Object convertSourceToTargetClass(Object source, Class targetClass) throws Exception {
+ // TODO Auto-generated method stub
+ throw new UnsupportedOperationException("Auto-generated method stub");
+ }
+
+ public Class getSourceClass() {
+ return String.class;
+ }
+
+ public Class getTargetClass() {
+ return Principal.class;
+ }
+
+ }
+
// public void testGenericTypeConversionOGNL() {
// DefaultConversionService service = new DefaultConversionService();
// Map map = new HashMap();