SPR-6179, additional mapper test cases
This commit is contained in:
parent
6ea83afe1e
commit
e7c8f1ef8b
|
|
@ -27,8 +27,9 @@ public interface Mapper<S, T> {
|
|||
* 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);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<MappingFailure> 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<MappingFailure> 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);
|
||||
|
|
|
|||
|
|
@ -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<Object, Object> {
|
|||
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<Object, Object> {
|
|||
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<MappingFailure> failures = new LinkedList<MappingFailure>();
|
||||
for (Mapping mapping : this.mappings) {
|
||||
mapping.map(sourceContext, targetContext, failures);
|
||||
}
|
||||
Set<Mapping> autoMappings = getAutoMappings(source, target);
|
||||
Set<Mapping> 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<Mapping> getAutoMappings(Object source, Object target) {
|
||||
private Set<Mapping> getAutoMappings(EvaluationContext sourceContext, EvaluationContext targetContext) {
|
||||
if (this.autoMappingEnabled) {
|
||||
Set<Mapping> autoMappings = new LinkedHashSet<Mapping>();
|
||||
Set<String> sourceFields = getMappableFields(source);
|
||||
Set<String> targetFields = getMappableFields(target);
|
||||
Set<String> 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<Object, Object> {
|
|||
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<Object, Object> {
|
|||
|
||||
private boolean explicitlyMapped(String field) {
|
||||
for (Mapping mapping : this.mappings) {
|
||||
if (mapping.getSourceExpressionString().equals(field)) {
|
||||
if (mapping.getSourceExpressionString().startsWith(field)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<String, Object> source = new HashMap<String, Object>();
|
||||
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<String, Object> source = new HashMap<String, Object>();
|
||||
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<String, Object> source = new HashMap<String, Object>();
|
||||
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<String, Object> source = new HashMap<String, Object>();
|
||||
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<NestedDto, Nested>() {
|
||||
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<String> sports = new ArrayList<String>();
|
||||
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<String, Object> source = new HashMap<String, Object>();
|
||||
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<String, String> 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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 <i>from string</i>
|
||||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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> T convert(Object source, Class<T> 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 <code>null</code>.
|
||||
* 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 <code>null</code>.
|
||||
* 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<Class, GenericConverter> objectConverters = getConvertersForSource(Object.class);
|
||||
return getConverter(objectConverters, targetType);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
LinkedList<Class> classQueue = new LinkedList<Class>();
|
||||
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<Class> classQueue = new LinkedList<Class>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue