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 3957698833b..f759498dd75 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 @@ -182,6 +182,7 @@ public class GenericConversionService implements ConversionService, ConverterReg * Hook to initialize the "generic" converters that require the full TypeDescriptor context to perform their conversion operations. */ protected void initGenericConverters() { + addGenericConverter(String[].class, Map.class, new StringArrayToMapGenericConverter(this)); addGenericConverter(Object[].class, Object[].class, new ArrayToArrayGenericConverter(this)); addGenericConverter(Object[].class, Collection.class, new ArrayToCollectionGenericConverter(this)); addGenericConverter(Object[].class, String.class, new ArrayToStringGenericConverter(this)); @@ -193,6 +194,7 @@ public class GenericConversionService implements ConversionService, ConverterReg addGenericConverter(Map.class, Map.class, new MapToMapGenericConverter(this)); addGenericConverter(String.class, Object[].class, new StringToArrayGenericConverter(this)); addGenericConverter(String.class, Collection.class, new StringToCollectionGenericConverter(this)); + addGenericConverter(String.class, Map.class, new StringToMapGenericConverter(this)); addGenericConverter(Object.class, Object[].class, new ObjectToArrayGenericConverter(this)); addGenericConverter(Object.class, Collection.class, new ObjectToCollectionGenericConverter(this)); } diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/support/MapEntryConverter.java b/org.springframework.core/src/main/java/org/springframework/core/convert/support/MapEntryConverter.java new file mode 100644 index 00000000000..e6b183154b2 --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/support/MapEntryConverter.java @@ -0,0 +1,62 @@ +/** + * + */ +package org.springframework.core.convert.support; + +import static org.springframework.core.convert.support.ConversionUtils.invokeConverter; + +import org.springframework.core.convert.ConverterNotFoundException; +import org.springframework.core.convert.TypeDescriptor; + +class MapEntryConverter { + + private GenericConverter keyConverter; + + private GenericConverter valueConverter; + + private TypeDescriptor sourceKeyType; + + private TypeDescriptor sourceValueType; + + private TypeDescriptor targetKeyType; + + private TypeDescriptor targetValueType; + + public MapEntryConverter(TypeDescriptor sourceKeyType, TypeDescriptor sourceValueType, TypeDescriptor targetKeyType, + TypeDescriptor targetValueType, boolean keysCompatible, boolean valuesCompatible, + GenericConversionService conversionService) { + if (sourceKeyType != TypeDescriptor.NULL && targetKeyType != TypeDescriptor.NULL && !keysCompatible) { + this.keyConverter = conversionService.getConverter(sourceKeyType, targetKeyType); + if (this.keyConverter == null) { + throw new ConverterNotFoundException(sourceKeyType, targetKeyType); + } + this.sourceKeyType = sourceKeyType; + this.targetKeyType = targetKeyType; + } + if (sourceValueType != TypeDescriptor.NULL && targetValueType != TypeDescriptor.NULL && !valuesCompatible) { + this.valueConverter = conversionService.getConverter(sourceValueType, targetValueType); + if (this.valueConverter == null) { + throw new ConverterNotFoundException(sourceValueType, targetValueType); + } + this.targetKeyType = targetKeyType; + this.targetValueType = targetValueType; + } + } + + public Object convertKey(Object sourceKey) { + if (sourceKey != null && this.keyConverter != null) { + return invokeConverter(this.keyConverter, sourceKey, this.sourceKeyType, this.targetKeyType); + } else { + return sourceKey; + } + } + + public Object convertValue(Object sourceValue) { + if (sourceValue != null && this.valueConverter != null) { + return invokeConverter(this.valueConverter, sourceValue, this.sourceValueType, this.targetValueType); + } else { + return sourceValue; + } + } + +} \ No newline at end of file diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/support/MapToMapGenericConverter.java b/org.springframework.core/src/main/java/org/springframework/core/convert/support/MapToMapGenericConverter.java index 93c2621901c..cce7d6327bf 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/support/MapToMapGenericConverter.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/support/MapToMapGenericConverter.java @@ -1,11 +1,9 @@ package org.springframework.core.convert.support; -import static org.springframework.core.convert.support.ConversionUtils.invokeConverter; import java.util.Map; import org.springframework.core.CollectionFactory; -import org.springframework.core.convert.ConverterNotFoundException; import org.springframework.core.convert.TypeDescriptor; final class MapToMapGenericConverter implements GenericConverter { @@ -20,13 +18,13 @@ final class MapToMapGenericConverter implements GenericConverter { Map sourceMap = (Map) source; TypeDescriptor targetKeyType = targetType.getMapKeyTypeDescriptor(); TypeDescriptor targetValueType = targetType.getMapValueTypeDescriptor(); - if (targetKeyType == null && targetValueType == null) { + if (targetKeyType == TypeDescriptor.NULL && targetValueType == TypeDescriptor.NULL) { return compatibleMapWithoutEntryConversion(sourceMap, targetType); } TypeDescriptor[] sourceEntryTypes = getMapEntryTypes(sourceMap); TypeDescriptor sourceKeyType = sourceEntryTypes[0]; TypeDescriptor sourceValueType = sourceEntryTypes[1]; - if (sourceKeyType == null && sourceValueType == null) { + if (sourceKeyType == TypeDescriptor.NULL && sourceValueType == TypeDescriptor.NULL) { return compatibleMapWithoutEntryConversion(sourceMap, targetType); } boolean keysCompatible = false; @@ -44,7 +42,9 @@ final class MapToMapGenericConverter implements GenericConverter { MapEntryConverter converter = new MapEntryConverter(sourceKeyType, sourceValueType, targetKeyType, targetValueType, keysCompatible, valuesCompatible, conversionService); for (Object entry : sourceMap.entrySet()) { Map.Entry sourceMapEntry = (Map.Entry) entry; - targetMap.put(converter.convertKey(sourceMapEntry.getKey()), converter.convertValue(sourceMapEntry.getValue())); + Object targetKey = converter.convertKey(sourceMapEntry.getKey()); + Object targetValue = converter.convertValue(sourceMapEntry.getValue()); + targetMap.put(targetKey, targetValue); } return targetMap; } @@ -79,57 +79,4 @@ final class MapToMapGenericConverter implements GenericConverter { } } - private static class MapEntryConverter { - - private GenericConverter keyConverter; - - private GenericConverter valueConverter; - - private TypeDescriptor sourceKeyType; - - private TypeDescriptor sourceValueType; - - private TypeDescriptor targetKeyType; - - private TypeDescriptor targetValueType; - - public MapEntryConverter(TypeDescriptor sourceKeyType, TypeDescriptor sourceValueType, TypeDescriptor targetKeyType, - TypeDescriptor targetValueType, boolean keysCompatible, boolean valuesCompatible, - GenericConversionService conversionService) { - if (sourceKeyType != TypeDescriptor.NULL && targetKeyType != TypeDescriptor.NULL && !keysCompatible) { - this.keyConverter = conversionService.getConverter(sourceKeyType, targetKeyType); - if (this.keyConverter == null) { - throw new ConverterNotFoundException(sourceKeyType, targetKeyType); - } - this.sourceKeyType = sourceKeyType; - this.targetKeyType = targetKeyType; - } - if (sourceValueType != TypeDescriptor.NULL && targetValueType != TypeDescriptor.NULL && !valuesCompatible) { - this.valueConverter = conversionService.getConverter(sourceValueType, targetValueType); - if (this.valueConverter == null) { - throw new ConverterNotFoundException(sourceValueType, targetValueType); - } - this.targetKeyType = targetKeyType; - this.targetValueType = targetValueType; - } - } - - public Object convertKey(Object sourceKey) { - if (sourceKey != null && this.keyConverter != null) { - return invokeConverter(this.keyConverter, sourceKey, this.sourceKeyType, this.targetKeyType); - } else { - return sourceKey; - } - } - - public Object convertValue(Object sourceValue) { - if (sourceValue != null && this.valueConverter != null) { - return invokeConverter(this.valueConverter, sourceValue, this.sourceValueType, this.targetValueType); - } else { - return sourceValue; - } - } - - } - } diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/support/StringArrayToMapGenericConverter.java b/org.springframework.core/src/main/java/org/springframework/core/convert/support/StringArrayToMapGenericConverter.java new file mode 100644 index 00000000000..e19406d1586 --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/support/StringArrayToMapGenericConverter.java @@ -0,0 +1,84 @@ +/* + * Copyright 2002-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.core.convert.support; + +import java.lang.reflect.Array; +import java.util.Map; + +import org.springframework.core.CollectionFactory; +import org.springframework.core.convert.TypeDescriptor; + +final class StringArrayToMapGenericConverter implements GenericConverter { + + private final GenericConversionService conversionService; + + public StringArrayToMapGenericConverter(GenericConversionService conversionService) { + this.conversionService = conversionService; + } + + public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { + TypeDescriptor targetKeyType = targetType.getMapKeyTypeDescriptor(); + TypeDescriptor targetValueType = targetType.getMapValueTypeDescriptor(); + if (targetKeyType == TypeDescriptor.NULL && targetValueType == TypeDescriptor.NULL) { + return mapWithoutConversion(source, targetType); + } + TypeDescriptor sourceElementType = sourceType.getElementTypeDescriptor(); + boolean keysCompatible = false; + if (sourceElementType.isAssignableTo(targetKeyType)) { + keysCompatible = true; + } + boolean valuesCompatible = false; + if (sourceElementType.isAssignableTo(targetValueType)) { + valuesCompatible = true; + } + if (keysCompatible && valuesCompatible) { + return mapWithoutConversion(source, targetType); + } + int length = Array.getLength(source); + Map target = CollectionFactory.createMap(targetType.getType(), length); + MapEntryConverter converter = new MapEntryConverter(sourceElementType, sourceElementType, targetKeyType, targetValueType, keysCompatible, valuesCompatible, conversionService); + for (int i = 0; i < length; i++) { + String property = (String) Array.get(source, i); + String[] fields = property.split("="); + if (fields.length < 2) { + throw new IllegalArgumentException("Invalid String property '" + property + + "'; properties should be in the format name=value"); + } + Object targetKey = converter.convertKey(fields[0]); + Object targetValue = converter.convertValue(fields[1]); + target.put(targetKey, targetValue); + } + return target; + } + + private Map mapWithoutConversion(Object source, TypeDescriptor targetType) { + int length = Array.getLength(source); + Map target = CollectionFactory.createMap(targetType.getType(), length); + for (int i = 0; i < length; i++) { + String property = (String) Array.get(source, i); + String[] fields = property.split("="); + if (fields.length < 2) { + throw new IllegalArgumentException("Invalid String property '" + property + + "'; properties should be in the format name=value"); + } + String key = fields[0]; + String value = fields[1]; + target.put(key, value); + } + return target; + } + +} diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/support/StringToMapGenericConverter.java b/org.springframework.core/src/main/java/org/springframework/core/convert/support/StringToMapGenericConverter.java new file mode 100644 index 00000000000..c5d04b1212f --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/support/StringToMapGenericConverter.java @@ -0,0 +1,34 @@ +/* + * Copyright 2002-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.core.convert.support; + +import org.springframework.core.convert.TypeDescriptor; + +final class StringToMapGenericConverter implements GenericConverter { + + private final StringArrayToMapGenericConverter converter; + + public StringToMapGenericConverter(GenericConversionService conversionService) { + this.converter = new StringArrayToMapGenericConverter(conversionService); + } + + public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { + String string = (String) source; + String[] properties = string.split(" "); + return this.converter.convert(properties, TypeDescriptor.valueOf(String[].class), targetType); + } + +} \ No newline at end of file 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 9bfc080cb8a..d9f55108c27 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 @@ -206,7 +206,7 @@ public class GenericConversionServiceTests { String result = conversionService.convert(new String[0], String.class); assertEquals("", result); } - + @Test public void convertArrayToObject() { Object[] array = new Object[] { 3L }; @@ -272,7 +272,8 @@ public class GenericConversionServiceTests { public void convertCollectionToStringWithElementConversion() throws Exception { conversionService.addConverter(new ObjectToStringConverter()); List list = Arrays.asList(new Integer[] { 3, 5 }); - String result = (String) conversionService.convert(list, new TypeDescriptor(getClass().getField("genericList")), TypeDescriptor.valueOf(String.class)); + String result = (String) conversionService.convert(list, + new TypeDescriptor(getClass().getField("genericList")), TypeDescriptor.valueOf(String.class)); assertEquals("3,5", result); } @@ -302,8 +303,8 @@ public class GenericConversionServiceTests { conversionService.addConverterFactory(new StringToEnumConverterFactory()); Map map = (Map) conversionService.convert(foo, TypeDescriptor .valueOf(Map.class), new TypeDescriptor(getClass().getField("genericMap"))); - assertEquals(map.get(1), FooEnum.BAR); - assertEquals(map.get(2), FooEnum.BAZ); + assertEquals(FooEnum.BAR, map.get(1)); + assertEquals(FooEnum.BAZ, map.get(2)); } @Test @@ -346,7 +347,8 @@ public class GenericConversionServiceTests { @Test public void convertStringToCollectionWithElementConversion() throws Exception { conversionService.addConverterFactory(new StringToNumberConverterFactory()); - List result = (List) conversionService.convert("1,2,3", TypeDescriptor.valueOf(String.class), new TypeDescriptor(getClass().getField("genericList"))); + List result = (List) conversionService.convert("1,2,3", TypeDescriptor.valueOf(String.class), + new TypeDescriptor(getClass().getField("genericList"))); assertEquals(3, result.size()); assertEquals(new Integer(1), result.get(0)); assertEquals(new Integer(2), result.get(1)); @@ -391,6 +393,42 @@ public class GenericConversionServiceTests { assertEquals(new Integer(3), result[0]); } + @Test + public void convertStringArrayToMap() { + Map result = conversionService.convert(new String[] { "foo=bar", "bar=baz", "baz=boop" }, Map.class); + assertEquals("bar", result.get("foo")); + assertEquals("baz", result.get("bar")); + assertEquals("boop", result.get("baz")); + } + + @Test + public void convertStringArrayToMapWithElementConversion() throws Exception { + conversionService.addConverterFactory(new StringToNumberConverterFactory()); + conversionService.addConverterFactory(new StringToEnumConverterFactory()); + Map result = (Map) conversionService.convert(new String[] { "1=BAR", "2=BAZ" }, TypeDescriptor + .valueOf(String[].class), new TypeDescriptor(getClass().getField("genericMap"))); + assertEquals(FooEnum.BAR, result.get(1)); + assertEquals(FooEnum.BAZ, result.get(2)); + } + + @Test + public void convertStringToMap() { + Map result = conversionService.convert("foo=bar bar=baz baz=boop", Map.class); + assertEquals("bar", result.get("foo")); + assertEquals("baz", result.get("bar")); + assertEquals("boop", result.get("baz")); + } + + @Test + public void convertStringToMapWithElementConversion() throws Exception { + conversionService.addConverterFactory(new StringToNumberConverterFactory()); + conversionService.addConverterFactory(new StringToEnumConverterFactory()); + Map result = (Map) conversionService.convert("1=BAR 2=BAZ", TypeDescriptor + .valueOf(String.class), new TypeDescriptor(getClass().getField("genericMap"))); + assertEquals(FooEnum.BAR, result.get(1)); + assertEquals(FooEnum.BAZ, result.get(2)); + } + @Test public void genericConverterDelegatingBackToConversionServiceConverterNotFound() { try {