diff --git a/org.springframework.context/src/main/java/org/springframework/mapping/Mapper.java b/org.springframework.context/src/main/java/org/springframework/mapping/Mapper.java index f6b4802183e..f2d9d7e93e8 100644 --- a/org.springframework.context/src/main/java/org/springframework/mapping/Mapper.java +++ b/org.springframework.context/src/main/java/org/springframework/mapping/Mapper.java @@ -27,8 +27,9 @@ public interface Mapper { * Map the source to the target. * @param source the source to map from * @param target the target to map to + * @return the mapped target object * @throws MappingException if the mapping process failed */ - void map(S source, T target); + Object map(S source, T target); } diff --git a/org.springframework.context/src/main/java/org/springframework/mapping/MappingException.java b/org.springframework.context/src/main/java/org/springframework/mapping/MappingException.java index 821354fd8a4..bcf32e381cb 100644 --- a/org.springframework.context/src/main/java/org/springframework/mapping/MappingException.java +++ b/org.springframework.context/src/main/java/org/springframework/mapping/MappingException.java @@ -60,11 +60,12 @@ public class MappingException extends RuntimeException { public void printStackTrace(PrintStream ps) { super.printStackTrace(ps); synchronized (ps) { - ps.println("Failure cause traces:"); + ps.println(); + ps.println("Mapping Failure Traces:"); int i = 1; for (Iterator it = this.mappingFailures.iterator(); it.hasNext(); i++) { MappingFailure failure = it.next(); - ps.println("- MappingFailure #" + i + " Cause: "); + ps.println("- MappingFailure #" + i + ":"); Throwable t = failure.getCause(); if (t != null) { t.printStackTrace(ps); @@ -79,11 +80,12 @@ public class MappingException extends RuntimeException { public void printStackTrace(PrintWriter pw) { super.printStackTrace(pw); synchronized (pw) { - pw.println("Failure cause traces:"); + pw.println(); + pw.println("Mapping Failure Traces:"); int i = 1; for (Iterator it = this.mappingFailures.iterator(); it.hasNext(); i++) { MappingFailure failure = it.next(); - pw.println("- MappingFailure #" + i + " Cause: "); + pw.println("- MappingFailure #" + i + ":"); Throwable t = failure.getCause(); if (t != null) { t.printStackTrace(pw); diff --git a/org.springframework.context/src/main/java/org/springframework/mapping/support/SpelMapper.java b/org.springframework.context/src/main/java/org/springframework/mapping/support/SpelMapper.java index 8ad0c30b4bc..efeb5649b1b 100644 --- a/org.springframework.context/src/main/java/org/springframework/mapping/support/SpelMapper.java +++ b/org.springframework.context/src/main/java/org/springframework/mapping/support/SpelMapper.java @@ -25,6 +25,7 @@ import java.util.Set; import org.springframework.core.convert.converter.ConverterRegistry; import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.expression.EvaluationContext; +import org.springframework.expression.EvaluationException; import org.springframework.expression.Expression; import org.springframework.expression.ParseException; import org.springframework.expression.spel.standard.SpelExpressionParser; @@ -60,27 +61,27 @@ public class SpelMapper implements Mapper { this.autoMappingEnabled = autoMappingEnabled; } - public MappingConfiguration addMapping(String expression) { - return addMapping(expression, expression); + public MappingConfiguration addMapping(String fieldExpression) { + return addMapping(fieldExpression, fieldExpression); } public ConverterRegistry getConverterRegistry() { return conversionService; } - public MappingConfiguration addMapping(String sourceExpression, String targetExpression) { + public MappingConfiguration addMapping(String sourceFieldExpression, String targetFieldExpression) { Expression sourceExp; try { - sourceExp = sourceExpressionParser.parseExpression(sourceExpression); + sourceExp = sourceExpressionParser.parseExpression(sourceFieldExpression); } catch (ParseException e) { - throw new IllegalArgumentException("The mapping source '" + sourceExpression + throw new IllegalArgumentException("The mapping source '" + sourceFieldExpression + "' is not a parseable value expression", e); } Expression targetExp; try { - targetExp = targetExpressionParser.parseExpression(targetExpression); + targetExp = targetExpressionParser.parseExpression(targetFieldExpression); } catch (ParseException e) { - throw new IllegalArgumentException("The mapping target '" + targetExpression + throw new IllegalArgumentException("The mapping target '" + targetFieldExpression + "' is not a parseable property expression", e); } Mapping mapping = new Mapping(sourceExp, targetExp); @@ -88,33 +89,33 @@ public class SpelMapper implements Mapper { return mapping; } - public void map(Object source, Object target) { + public Object map(Object source, Object target) { EvaluationContext sourceContext = getMappingContext(source); EvaluationContext targetContext = getMappingContext(target); List failures = new LinkedList(); for (Mapping mapping : this.mappings) { mapping.map(sourceContext, targetContext, failures); } - Set autoMappings = getAutoMappings(source, target); + Set autoMappings = getAutoMappings(sourceContext, targetContext); for (Mapping mapping : autoMappings) { mapping.map(sourceContext, targetContext, failures); } if (!failures.isEmpty()) { throw new MappingException(failures); } + return target; } private EvaluationContext getMappingContext(Object object) { return mappableTypeFactory.getMappableType(object).getEvaluationContext(object, this.conversionService); } - private Set getAutoMappings(Object source, Object target) { + private Set getAutoMappings(EvaluationContext sourceContext, EvaluationContext targetContext) { if (this.autoMappingEnabled) { Set autoMappings = new LinkedHashSet(); - Set sourceFields = getMappableFields(source); - Set targetFields = getMappableFields(target); + Set sourceFields = getMappableFields(sourceContext.getRootObject().getValue()); for (String field : sourceFields) { - if (!explicitlyMapped(field) && targetFields.contains(field)) { + if (!explicitlyMapped(field)) { Expression sourceExpression; Expression targetExpression; try { @@ -129,8 +130,13 @@ public class SpelMapper implements Mapper { throw new IllegalArgumentException("The mapping target '" + field + "' is not a parseable value expression", e); } - Mapping mapping = new Mapping(sourceExpression, targetExpression); - autoMappings.add(mapping); + try { + if (targetExpression.isWritable(targetContext)) { + autoMappings.add(new Mapping(sourceExpression, targetExpression)); + } + } catch (EvaluationException e) { + + } } } return autoMappings; @@ -145,7 +151,7 @@ public class SpelMapper implements Mapper { private boolean explicitlyMapped(String field) { for (Mapping mapping : this.mappings) { - if (mapping.getSourceExpressionString().equals(field)) { + if (mapping.getSourceExpressionString().startsWith(field)) { return true; } } diff --git a/org.springframework.context/src/test/java/org/springframework/mapping/support/SpelMapperTests.java b/org.springframework.context/src/test/java/org/springframework/mapping/support/SpelMapperTests.java index e8fda5fd7fd..c90b0e01c95 100644 --- a/org.springframework.context/src/test/java/org/springframework/mapping/support/SpelMapperTests.java +++ b/org.springframework.context/src/test/java/org/springframework/mapping/support/SpelMapperTests.java @@ -1,6 +1,7 @@ package org.springframework.mapping.support; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; import java.util.ArrayList; import java.util.HashMap; @@ -31,15 +32,14 @@ public class SpelMapperTests { @Test public void mapExplicit() throws MappingException { - mapper.setAutoMappingEnabled(false); - mapper.addMapping("name"); - Map source = new HashMap(); source.put("name", "Keith"); source.put("age", 31); Person target = new Person(); + mapper.setAutoMappingEnabled(false); + mapper.addMapping("name"); mapper.map(source, target); assertEquals("Keith", target.name); @@ -48,8 +48,6 @@ public class SpelMapperTests { @Test public void mapAutomaticWithExplictOverrides() { - mapper.addMapping("test", "age"); - Map source = new HashMap(); source.put("name", "Keith"); source.put("test", "3"); @@ -57,6 +55,7 @@ public class SpelMapperTests { Person target = new Person(); + mapper.addMapping("test", "age"); mapper.map(source, target); assertEquals("Keith", target.name); @@ -65,15 +64,29 @@ public class SpelMapperTests { } @Test - public void mapSameSourceFieldToMultipleTargets() { - mapper.addMapping("test", "name"); - mapper.addMapping("test", "favoriteSport"); + public void mapAutomaticIgnoreUnknownField() { + Map source = new HashMap(); + source.put("name", "Keith"); + source.put("age", 31); + source.put("unknown", "foo"); + Person target = new Person(); + + mapper.map(source, target); + + assertEquals("Keith", target.name); + assertEquals(31, target.age); + } + + @Test + public void mapSameSourceFieldToMultipleTargets() { Map source = new HashMap(); source.put("test", "FOOTBALL"); Person target = new Person(); + mapper.addMapping("test", "name"); + mapper.addMapping("test", "favoriteSport"); mapper.map(source, target); assertEquals("FOOTBALL", target.name); @@ -92,7 +105,6 @@ public class SpelMapperTests { mapper.addMapping("fullName", "name"); mapper.addMapping("sport", "favoriteSport"); - mapper.map(source, target); assertEquals("Keith Donald", target.name); @@ -100,24 +112,46 @@ public class SpelMapperTests { assertEquals(Sport.FOOTBALL, target.favoriteSport); } + @Test + public void mapBeanDeep() { + PersonDto source = new PersonDto(); + source.age = "0"; + NestedDto nested = new NestedDto(); + nested.foo = "bar"; + source.setNested(nested); + + Person target = new Person(); + + mapper.addMapping("nested.foo", "nested.foo"); + mapper.map(source, target); + + assertEquals("bar", target.nested.foo); + } + @Test public void mapBeanNested() { PersonDto source = new PersonDto(); source.setFullName("Keith Donald"); source.setAge("31"); source.setSport("FOOTBALL"); + NestedDto nested = new NestedDto(); + nested.foo = "bar"; + source.setNested(nested); Person target = new Person(); - mapper.addMapping("fullName", "nested.fullName"); - mapper.addMapping("age", "nested.age"); - mapper.addMapping("sport", "nested.sport"); - + mapper.addMapping("fullName", "name"); + mapper.addMapping("sport", "favoriteSport"); + mapper.addMapping("nested", "nested").setConverter(new Converter() { + public Nested convert(NestedDto source) { + return (Nested) new SpelMapper().map(source, new Nested()); + } + }); mapper.map(source, target); - assertEquals("Keith Donald", target.getNested().getFullName()); - assertEquals("31", target.nested.age); - assertEquals("FOOTBALL", target.nested.sport); + assertEquals("Keith Donald", target.name); + assertEquals(31, target.age); + assertEquals(Sport.FOOTBALL, target.favoriteSport); } @Test @@ -132,13 +166,30 @@ public class SpelMapperTests { mapper.setAutoMappingEnabled(false); mapper.addMapping("sports", "favoriteSports"); - mapper.map(source, target); assertEquals(Sport.FOOTBALL, target.favoriteSports.get(0)); assertEquals(Sport.BASKETBALL, target.favoriteSports.get(1)); } + @Test + public void mapListFlatten() { + PersonDto source = new PersonDto(); + List sports = new ArrayList(); + sports.add("FOOTBALL"); + sports.add("BASKETBALL"); + source.setSports(sports); + + Person target = new Person(); + + mapper.setAutoMappingEnabled(false); + mapper.addMapping("sports[0]", "favoriteSport"); + mapper.map(source, target); + + assertEquals(Sport.FOOTBALL, target.favoriteSport); + assertNull(target.favoriteSports); + } + @Test public void mapMap() { PersonDto source = new PersonDto(); @@ -187,7 +238,7 @@ public class SpelMapperTests { public void mapFailure() { Map source = new HashMap(); source.put("name", "Keith"); - source.put("age", "bogus"); + source.put("age", "invalid"); Person target = new Person(); try { mapper.map(source, target); @@ -208,7 +259,7 @@ public class SpelMapperTests { private Map friendRankings; - private NestedDto nestedDto; + private NestedDto nested; public String getFullName() { return fullName; @@ -250,12 +301,12 @@ public class SpelMapperTests { this.friendRankings = friendRankings; } - public NestedDto getNestedDto() { - return nestedDto; + public NestedDto getNested() { + return nested; } - public void setNestedDto(NestedDto nestedDto) { - this.nestedDto = nestedDto; + public void setNested(NestedDto nested) { + this.nested = nested; } } @@ -277,7 +328,7 @@ public class SpelMapperTests { private Sport favoriteSport; - private PersonDto nested; + private Nested nested; // private Person cyclic; @@ -317,11 +368,11 @@ public class SpelMapperTests { this.favoriteSport = favoriteSport; } - public PersonDto getNested() { + public Nested getNested() { return nested; } - public void setNested(PersonDto nested) { + public void setNested(Nested nested) { this.nested = nested; } @@ -364,6 +415,20 @@ public class SpelMapperTests { } } + public static class Nested { + + private String foo; + + public String getFoo() { + return foo; + } + + public void setFoo(String foo) { + this.foo = foo; + } + + } + public enum Sport { FOOTBALL, BASKETBALL } diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/TypeDescriptor.java b/org.springframework.core/src/main/java/org/springframework/core/convert/TypeDescriptor.java index 4d9cf47844d..d69d2066143 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/TypeDescriptor.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/TypeDescriptor.java @@ -159,6 +159,14 @@ public class TypeDescriptor { } } + /** + * Is this type a primitive type? + */ + public boolean isPrimitive() { + Class type = getType(); + return (type != null && type.isPrimitive()); + } + /** * Is this type an array type? */ @@ -195,7 +203,7 @@ public class TypeDescriptor { return TypeDescriptor.valueOf(getCollectionElementType()); } else { return TypeDescriptor.NULL; - } + } } } diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/support/DefaultConversionService.java b/org.springframework.core/src/main/java/org/springframework/core/convert/support/DefaultConversionService.java index 0af53dd94bd..70c221837ba 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/support/DefaultConversionService.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/support/DefaultConversionService.java @@ -16,6 +16,8 @@ package org.springframework.core.convert.support; +import java.util.Collection; +import java.util.Map; /** * Default implementation of a conversion service. Will automatically register from string @@ -31,6 +33,21 @@ public class DefaultConversionService extends GenericConversionService { * Create a new default conversion service, installing the default converters. */ public DefaultConversionService() { + addGenericConverter(Object[].class, Object[].class, new ArrayToArrayConverter(this)); + addGenericConverter(Object[].class, Collection.class, new ArrayToCollectionConverter(this)); + addGenericConverter(Object[].class, Map.class, new ArrayToMapConverter(this)); + addGenericConverter(Object[].class, Object.class, new ArrayToObjectConverter(this)); + addGenericConverter(Collection.class, Collection.class, new CollectionToCollectionConverter(this)); + addGenericConverter(Collection.class, Object[].class, new CollectionToArrayConverter(this)); + addGenericConverter(Collection.class, Map.class, new CollectionToMapConverter(this)); + addGenericConverter(Collection.class, Object.class, new CollectionToObjectConverter(this)); + addGenericConverter(Map.class, Map.class, new MapToMapConverter(this)); + addGenericConverter(Map.class, Object[].class, new MapToArrayConverter(this)); + addGenericConverter(Map.class, Collection.class, new MapToCollectionConverter(this)); + addGenericConverter(Map.class, Object.class, new MapToObjectConverter(this)); + addGenericConverter(Object.class, Object[].class, new ObjectToArrayConverter(this)); + addGenericConverter(Object.class, Collection.class, new ObjectToCollectionConverter(this)); + addGenericConverter(Object.class, Map.class, new ObjectToMapConverter(this)); addConverter(new StringToBooleanConverter()); addConverter(new StringToCharacterConverter()); addConverter(new StringToLocaleConverter()); diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/support/GenericConversionService.java b/org.springframework.core/src/main/java/org/springframework/core/convert/support/GenericConversionService.java index ebebebaecda..4f5284e4576 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/support/GenericConversionService.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/support/GenericConversionService.java @@ -16,6 +16,8 @@ package org.springframework.core.convert.support; +import static org.springframework.core.convert.support.ConversionUtils.invokeConverter; + import java.lang.reflect.Array; import java.util.Collection; import java.util.Collections; @@ -25,6 +27,7 @@ import java.util.Map; import java.util.Set; import org.springframework.core.GenericTypeResolver; +import org.springframework.core.convert.ConversionFailedException; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.ConverterNotFoundException; import org.springframework.core.convert.TypeDescriptor; @@ -32,13 +35,11 @@ import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.ConverterFactory; import org.springframework.core.convert.converter.ConverterInfo; import org.springframework.core.convert.converter.ConverterRegistry; -import static org.springframework.core.convert.support.ConversionUtils.*; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; /** - * Base implementation of a conversion service. - * + * Base ConversionService implementation suitable for use in most environments. * @author Keith Donald * @author Juergen Hoeller * @since 3.0 @@ -63,8 +64,12 @@ public class GenericConversionService implements ConversionService, ConverterReg } }; - + /** + * Create a new GenericConversionService. + * Generic converters for Collection types are registered. + */ public GenericConversionService() { + // TODO should these only be registered in DefaultConversionService? addGenericConverter(Object[].class, Object[].class, new ArrayToArrayConverter(this)); addGenericConverter(Object[].class, Collection.class, new ArrayToCollectionConverter(this)); addGenericConverter(Object[].class, Map.class, new ArrayToMapConverter(this)); @@ -82,7 +87,6 @@ public class GenericConversionService implements ConversionService, ConverterReg addGenericConverter(Object.class, Map.class, new ObjectToMapConverter(this)); } - /** * Registers the converters in the set provided. * JavaBean-friendly alternative to calling {@link #addConverter(Converter)}. @@ -113,13 +117,12 @@ public class GenericConversionService implements ConversionService, ConverterReg } /** - * Returns the parent of this conversion service. Could be null. + * Returns the parent of this conversion service. May be null. */ public ConversionService getParent() { return this.parent; } - // implementing ConverterRegistry public void addConverter(Converter converter) { @@ -130,7 +133,7 @@ public class GenericConversionService implements ConversionService, ConverterReg } Class sourceType = typeInfo[0]; Class targetType = typeInfo[1]; - getSourceMap(sourceType).put(targetType, new ConverterGenericConverter(converter)); + getSourceMap(sourceType).put(targetType, new ConverterAdapter(converter)); } public void addConverterFactory(ConverterFactory converterFactory) { @@ -141,43 +144,37 @@ public class GenericConversionService implements ConversionService, ConverterReg } Class sourceType = typeInfo[0]; Class targetType = typeInfo[1]; - getSourceMap(sourceType).put(targetType, new ConverterFactoryGenericConverter(converterFactory)); + getSourceMap(sourceType).put(targetType, new ConverterFactoryAdapter(converterFactory)); } public void removeConvertible(Class sourceType, Class targetType) { getSourceMap(sourceType).remove(targetType); } - // implementing ConversionService public boolean canConvert(Class sourceType, Class targetType) { return canConvert(TypeDescriptor.valueOf(sourceType), TypeDescriptor.valueOf(targetType)); } - @SuppressWarnings("unchecked") public T convert(Object source, Class targetType) { - Assert.notNull(targetType, "The targetType to convert to is required"); return (T) convert(source, TypeDescriptor.forObject(source), TypeDescriptor.valueOf(targetType)); } public boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType) { - Assert.notNull(sourceType, "The sourceType to convert from is required"); - Assert.notNull(targetType, "The targetType to convert to is required"); - if (targetType == TypeDescriptor.NULL) { + assertNotNull(sourceType, targetType); + if (sourceType == TypeDescriptor.NULL || targetType == TypeDescriptor.NULL) { return true; } return getConverter(sourceType, targetType) != null; } public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { - Assert.notNull(sourceType, "The sourceType to convert to is required"); - Assert.notNull(targetType, "The targetType to convert to is required"); - if (source == null) { - return convertNull(sourceType, targetType); + assertNotNull(sourceType, targetType); + if (sourceType == TypeDescriptor.NULL) { + Assert.isTrue(source == null, "The source must be null if sourceType == TypeDescriptor.NULL"); + return convertNullSource(sourceType, targetType); } - Assert.isTrue(sourceType != TypeDescriptor.NULL, - "The source TypeDescriptor must not be TypeDescriptor.NULL when source != null"); if (targetType == TypeDescriptor.NULL) { return null; } @@ -188,7 +185,6 @@ public class GenericConversionService implements ConversionService, ConverterReg return invokeConverter(converter, source, sourceType, targetType); } - // subclassing hooks /** @@ -197,19 +193,25 @@ public class GenericConversionService implements ConversionService, ConverterReg * @param targetType the target type to convert to * @param converter the generic converter. */ + // TODO should this be public? protected void addGenericConverter(Class sourceType, Class targetType, GenericConverter converter) { getSourceMap(sourceType).put(targetType, converter); } /** - * Hook method to convert a null value. - * Default implementation simply returns null. - * Subclasses may override to return a custom null objects for specific target types. - * @param sourceType the sourceType - * @param targetType the tagetType - * @return the null object + * Hook method to convert a null source. + * Default implementation returns null. + * Throws a {@link ConversionFailedException} if the targetType is a primitive type, as null cannot be assigned to a primitive type. + * Subclasses may override to return custom null objects for specific target types. + * @param sourceType the sourceType to convert from + * @param targetType the targetType to convert to + * @return the converted null object */ - protected Object convertNull(TypeDescriptor sourceType, TypeDescriptor targetType) { + protected Object convertNullSource(TypeDescriptor sourceType, TypeDescriptor targetType) { + if (targetType.isPrimitive()) { + throw new ConversionFailedException(sourceType, targetType, null, new IllegalArgumentException( + "A null value cannot be assigned to a primitive type")); + } return null; } @@ -228,23 +230,24 @@ public class GenericConversionService implements ConversionService, ConverterReg GenericConverter converter = findConverterByClassPair(sourceType.getObjectType(), targetType.getObjectType()); if (converter != null) { return converter; - } - else if (this.parent != null && this.parent.canConvert(sourceType, targetType)) { + } else if (this.parent != null && this.parent.canConvert(sourceType, targetType)) { return this.parentConverterAdapter; - } - else { + } else { if (sourceType.isAssignableTo(targetType)) { return NO_OP_CONVERTER; - } - else { + } else { return null; } } } - // internal helpers + private void assertNotNull(TypeDescriptor sourceType, TypeDescriptor targetType) { + Assert.notNull(sourceType, "The sourceType to convert to is required"); + Assert.notNull(targetType, "The targetType to convert to is required"); + } + private Class[] getRequiredTypeInfo(Object converter, Class genericIfc) { Class[] typeInfo = new Class[2]; if (converter instanceof ConverterInfo) { @@ -252,8 +255,7 @@ public class GenericConversionService implements ConversionService, ConverterReg typeInfo[0] = info.getSourceType(); typeInfo[1] = info.getTargetType(); return typeInfo; - } - else { + } else { return GenericTypeResolver.resolveTypeArguments(converter.getClass(), genericIfc); } } @@ -276,8 +278,7 @@ public class GenericConversionService implements ConversionService, ConverterReg } Map objectConverters = getConvertersForSource(Object.class); return getConverter(objectConverters, targetType); - } - else { + } else { LinkedList classQueue = new LinkedList(); classQueue.addFirst(sourceType); while (!classQueue.isEmpty()) { @@ -292,8 +293,7 @@ public class GenericConversionService implements ConversionService, ConverterReg if (componentType.getSuperclass() != null) { classQueue.addFirst(Array.newInstance(componentType.getSuperclass(), 0).getClass()); } - } - else { + } else { if (currentClass.getSuperclass() != null) { classQueue.addFirst(currentClass.getSuperclass()); } @@ -340,8 +340,7 @@ public class GenericConversionService implements ConversionService, ConverterReg } } return converters.get(Object.class); - } - else { + } else { LinkedList classQueue = new LinkedList(); classQueue.addFirst(targetType); while (!classQueue.isEmpty()) { @@ -355,8 +354,7 @@ public class GenericConversionService implements ConversionService, ConverterReg if (componentType.getSuperclass() != null) { classQueue.addFirst(Array.newInstance(componentType.getSuperclass(), 0).getClass()); } - } - else { + } else { if (currentClass.getSuperclass() != null) { classQueue.addFirst(currentClass.getSuperclass()); } @@ -370,4 +368,36 @@ public class GenericConversionService implements ConversionService, ConverterReg } } + private final class ConverterAdapter implements GenericConverter { + + private final Converter converter; + + public ConverterAdapter(Converter converter) { + this.converter = converter; + } + + public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { + if (source == null) { + return convertNullSource(sourceType, targetType); + } + return this.converter.convert(source); + } + } + + private final class ConverterFactoryAdapter implements GenericConverter { + + private final ConverterFactory converterFactory; + + public ConverterFactoryAdapter(ConverterFactory converterFactory) { + this.converterFactory = converterFactory; + } + + public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { + if (source == null) { + return convertNullSource(sourceType, targetType); + } + return this.converterFactory.getConverter(targetType.getObjectType()).convert(source); + } + } + } diff --git a/org.springframework.core/src/test/java/org/springframework/core/convert/support/GenericConversionServiceTests.java b/org.springframework.core/src/test/java/org/springframework/core/convert/support/GenericConversionServiceTests.java index 2721b3efeee..7940f0085bf 100644 --- a/org.springframework.core/src/test/java/org/springframework/core/convert/support/GenericConversionServiceTests.java +++ b/org.springframework.core/src/test/java/org/springframework/core/convert/support/GenericConversionServiceTests.java @@ -91,14 +91,13 @@ public class GenericConversionServiceTests { assertNull(conversionService.convert(null, Integer.class)); } - @Test(expected = IllegalArgumentException.class) public void convertNullTargetClass() { - assertEquals("3", conversionService.convert("3", (Class) null)); + assertNull(conversionService.convert("3", (Class) null)); } @Test public void convertNullTypeDescriptor() { - assertEquals(null, conversionService.convert(3, TypeDescriptor.valueOf(String.class), TypeDescriptor.NULL)); + assertNull(conversionService.convert("3", TypeDescriptor.valueOf(String.class), TypeDescriptor.NULL)); } @Test @@ -132,6 +131,7 @@ public class GenericConversionServiceTests { @Test public void convertArrayToArray() { + conversionService.addGenericConverter(Object[].class, Object[].class, new ArrayToArrayConverter(conversionService)); conversionService.addConverterFactory(new StringToNumberConverterFactory()); Integer[] result = conversionService.convert(new String[] { "1", "2", "3" }, Integer[].class); assertEquals(new Integer(1), result[0]); diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/StandardTypeConverter.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/StandardTypeConverter.java index f201bf18653..2ff7aaa949f 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/StandardTypeConverter.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/StandardTypeConverter.java @@ -28,9 +28,8 @@ import org.springframework.expression.spel.SpelMessage; import org.springframework.util.Assert; /** - * Default implementation of the {@link TypeConverter} interface, - * delegating to a core Spring {@link ConversionService}. - * + * Default implementation of the {@link TypeConverter} interface, delegating to a core Spring {@link ConversionService}. + * * @author Juergen Hoeller * @author Andy Clement * @since 3.0 @@ -56,12 +55,12 @@ public class StandardTypeConverter implements TypeConverter { public Object convertValue(Object value, TypeDescriptor typeDescriptor) throws EvaluationException { try { return this.conversionService.convert(value, TypeDescriptor.forObject(value), typeDescriptor); - } - catch (ConverterNotFoundException cenfe) { - throw new SpelEvaluationException(cenfe, SpelMessage.TYPE_CONVERSION_ERROR, value.getClass(), typeDescriptor.asString()); - } - catch (ConversionException ce) { - throw new SpelEvaluationException(ce, SpelMessage.TYPE_CONVERSION_ERROR, value.getClass(), typeDescriptor.asString()); + } catch (ConverterNotFoundException cenfe) { + throw new SpelEvaluationException(cenfe, SpelMessage.TYPE_CONVERSION_ERROR, value != null ? value + .getClass() : null, typeDescriptor.asString()); + } catch (ConversionException ce) { + throw new SpelEvaluationException(ce, SpelMessage.TYPE_CONVERSION_ERROR, value != null ? value.getClass() + : null, typeDescriptor.asString()); } }