List.class is
+ * specified. Supports type conversion of array elements when a concrete parameterized collection class is provided,
+ * such as IntegerList.class .
+ *
+ * Note that type erasure prevents arbitrary access to generic collection element type information at runtime,
+ * preventing the ability to convert elements for collections declared as properties.
+ *
+ * Mainly used internally by {@link ConversionService} implementations.
+ *
+ * @author Keith Donald
+ */
+@SuppressWarnings("unchecked")
+class ArrayToCollection implements SuperConverter {
+
+ private ConversionService conversionService;
+
+ private ConversionExecutor elementConverter;
+
+ /**
+ * Creates a new array to collection converter.
+ * @param conversionService the conversion service to use to lookup the converter to apply to array elements added
+ * to the target collection
+ */
+ public ArrayToCollection(ConversionService conversionService) {
+ this.conversionService = conversionService;
+ }
+
+ /**
+ * Creates a new array to collection converter.
+ * @param elementConverter A specific converter to use on array elements when adding them to the target collection
+ */
+ public ArrayToCollection(ConversionExecutor elementConverter) {
+ this.elementConverter = elementConverter;
+ }
+
+ public Object convert(Object source, Class targetClass) throws Exception {
+ if (source == null) {
+ return null;
+ }
+ Class collectionImplClass = getCollectionImplClass(targetClass);
+ Constructor constructor = collectionImplClass.getConstructor((Class[]) null);
+ Collection collection = (Collection) constructor.newInstance((Object[]) null);
+ ConversionExecutor converter = getArrayElementConverter(source, targetClass);
+ int length = Array.getLength(source);
+ for (int i = 0; i < length; i++) {
+ Object value = Array.get(source, i);
+ if (converter != null) {
+ value = converter.execute(value);
+ }
+ collection.add(value);
+ }
+ return collection;
+ }
+
+ public Object convertBack(Object target) throws Exception {
+ throw new UnsupportedOperationException("Should never be called");
+ }
+
+ public Object convertBack(Object target, Class sourceClass) throws Exception {
+ if (target == null) {
+ return null;
+ }
+ Collection collection = (Collection) target;
+ Object array = Array.newInstance(sourceClass.getComponentType(), collection.size());
+ int i = 0;
+ for (Iterator it = collection.iterator(); it.hasNext(); i++) {
+ Object value = it.next();
+ if (value != null) {
+ ConversionExecutor converter;
+ if (elementConverter != null) {
+ converter = elementConverter;
+ } else {
+ converter = conversionService.getConversionExecutor(value.getClass(), sourceClass
+ .getComponentType());
+ }
+ value = converter.execute(value);
+ }
+ Array.set(array, i, value);
+ }
+ return array;
+ }
+
+ private Class getCollectionImplClass(Class targetClass) {
+ if (targetClass.isInterface()) {
+ if (List.class.equals(targetClass)) {
+ return ArrayList.class;
+ } else if (Set.class.equals(targetClass)) {
+ return LinkedHashSet.class;
+ } else if (SortedSet.class.equals(targetClass)) {
+ return TreeSet.class;
+ } else {
+ throw new IllegalArgumentException("Unsupported collection interface [" + targetClass.getName() + "]");
+ }
+ } else {
+ return targetClass;
+ }
+ }
+
+ private ConversionExecutor getArrayElementConverter(Object source, Class targetClass) {
+ if (elementConverter != null) {
+ return elementConverter;
+ } else {
+ Class elementType = GenericCollectionTypeResolver.getCollectionType(targetClass);
+ if (elementType != null) {
+ Class componentType = source.getClass().getComponentType();
+ return conversionService.getConversionExecutor(componentType, elementType);
+ }
+ return null;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/service/CollectionToArray.java b/org.springframework.core/src/main/java/org/springframework/core/convert/service/CollectionToArray.java
new file mode 100644
index 00000000000..b690a1e725e
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/convert/service/CollectionToArray.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2004-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.service;
+
+import java.lang.reflect.Array;
+import java.util.Collection;
+import java.util.Iterator;
+
+import org.springframework.core.convert.ConversionExecutor;
+import org.springframework.core.convert.ConversionService;
+import org.springframework.core.convert.converter.SuperConverter;
+
+/**
+ * Special converter that converts from a source array to a target collection. Supports the selection of an
+ * "approximate" collection implementation when a target collection interface such as List.class is
+ * specified. Supports type conversion of array elements when a concrete parameterized collection class is provided,
+ * such as IntegerList.class .
+ *
+ * Note that type erasure prevents arbitrary access to generic collection element type information at runtime,
+ * preventing the ability to convert elements for collections declared as properties.
+ *
+ * Mainly used internally by {@link ConversionService} implementations.
+ *
+ * @author Keith Donald
+ */
+@SuppressWarnings("unchecked")
+class CollectionToArray implements SuperConverter {
+
+ private ConversionService conversionService;
+
+ private ConversionExecutor elementConverter;
+
+ /**
+ * Creates a new array to collection converter.
+ * @param conversionService the conversion service to use to lookup the converter to apply to array elements added
+ * to the target collection
+ */
+ public CollectionToArray(ConversionService conversionService) {
+ this.conversionService = conversionService;
+ }
+
+ /**
+ * Creates a new array to collection converter.
+ * @param elementConverter A specific converter to use on array elements when adding them to the target collection
+ */
+ public CollectionToArray(ConversionExecutor elementConverter) {
+ this.elementConverter = elementConverter;
+ }
+
+ public Object convert(Object source, Class targetClass) throws Exception {
+ Collection collection = (Collection) source;
+ Object array = Array.newInstance(targetClass.getComponentType(), collection.size());
+ int i = 0;
+ for (Iterator it = collection.iterator(); it.hasNext(); i++) {
+ Object value = it.next();
+ if (value != null) {
+ ConversionExecutor converter;
+ if (elementConverter != null) {
+ converter = elementConverter;
+ } else {
+ converter = conversionService.getConversionExecutor(value.getClass(), targetClass
+ .getComponentType());
+ }
+ value = converter.execute(value);
+ }
+ Array.set(array, i, value);
+ }
+ return array;
+ }
+
+ public Object convertBack(Object target) throws Exception {
+ throw new UnsupportedOperationException("Should never be called");
+ }
+
+}
\ No newline at end of file
diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/service/CollectionToCollection.java b/org.springframework.core/src/main/java/org/springframework/core/convert/service/CollectionToCollection.java
new file mode 100644
index 00000000000..799987475e0
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/convert/service/CollectionToCollection.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2004-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.service;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+import org.springframework.core.GenericCollectionTypeResolver;
+import org.springframework.core.convert.ConversionExecutor;
+import org.springframework.core.convert.ConversionService;
+import org.springframework.core.convert.converter.SuperConverter;
+
+/**
+ * A converter that can convert from one collection type to another.
+ *
+ * @author Keith Donald
+ */
+@SuppressWarnings("unchecked")
+class CollectionToCollection implements SuperConverter {
+
+ private ConversionService conversionService;
+
+ private ConversionExecutor elementConverter;
+
+ /**
+ * Creates a new collection-to-collection converter
+ * @param conversionService the conversion service to use to convert collection elements to add to the target
+ * collection
+ */
+ public CollectionToCollection(ConversionService conversionService) {
+ this.conversionService = conversionService;
+ }
+
+ /**
+ * Creates a new collection-to-collection converter
+ * @param elementConverter a specific converter to use to convert collection elements added to the target collection
+ */
+ public CollectionToCollection(ConversionExecutor elementConverter) {
+ this.elementConverter = elementConverter;
+ }
+
+ public Class getSourceClass() {
+ return Collection.class;
+ }
+
+ public Class getSuperTargetClass() {
+ return Collection.class;
+ }
+
+ public Object convert(Object source, Class targetClass) throws Exception {
+ if (source == null) {
+ return null;
+ }
+ Class targetCollectionImpl = getCollectionImplClass(targetClass);
+ Collection targetCollection = (Collection) targetCollectionImpl.getConstructor((Class[]) null).newInstance(
+ (Object[]) null);
+ ConversionExecutor elementConverter = getElementConverter(source, targetClass);
+ Collection sourceCollection = (Collection) source;
+ Iterator it = sourceCollection.iterator();
+ while (it.hasNext()) {
+ Object value = it.next();
+ if (elementConverter != null) {
+ value = elementConverter.execute(value);
+ }
+ targetCollection.add(value);
+ }
+ return targetCollection;
+ }
+
+ public Object convertBack(Object target) throws Exception {
+ throw new UnsupportedOperationException("Not supported");
+ }
+
+ // this code is duplicated in ArrayToCollection.java and ObjectToCollection too
+ private Class getCollectionImplClass(Class targetClass) {
+ if (targetClass.isInterface()) {
+ if (List.class.equals(targetClass)) {
+ return ArrayList.class;
+ } else if (Set.class.equals(targetClass)) {
+ return LinkedHashSet.class;
+ } else if (SortedSet.class.equals(targetClass)) {
+ return TreeSet.class;
+ } else {
+ throw new IllegalArgumentException("Unsupported collection interface [" + targetClass.getName() + "]");
+ }
+ } else {
+ return targetClass;
+ }
+ }
+
+ private ConversionExecutor getElementConverter(Object source, Class targetClass) {
+ if (elementConverter != null) {
+ return elementConverter;
+ } else {
+ Class elementType = GenericCollectionTypeResolver.getCollectionType(targetClass);
+ if (elementType != null) {
+ Class componentType = source.getClass().getComponentType();
+ return conversionService.getConversionExecutor(componentType, elementType);
+ }
+ return null;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/service/DefaultConversionService.java b/org.springframework.core/src/main/java/org/springframework/core/convert/service/DefaultConversionService.java
new file mode 100644
index 00000000000..10c6da011d9
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/convert/service/DefaultConversionService.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2004-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.service;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Date;
+import java.util.Locale;
+
+import org.springframework.core.convert.converter.NumberToNumber;
+import org.springframework.core.convert.converter.StringToBigDecimal;
+import org.springframework.core.convert.converter.StringToBigInteger;
+import org.springframework.core.convert.converter.StringToBoolean;
+import org.springframework.core.convert.converter.StringToByte;
+import org.springframework.core.convert.converter.StringToCharacter;
+import org.springframework.core.convert.converter.StringToDouble;
+import org.springframework.core.convert.converter.StringToEnum;
+import org.springframework.core.convert.converter.StringToFloat;
+import org.springframework.core.convert.converter.StringToInteger;
+import org.springframework.core.convert.converter.StringToLocale;
+import org.springframework.core.convert.converter.StringToLong;
+import org.springframework.core.convert.converter.StringToShort;
+
+/**
+ * Default, local implementation of a conversion service. Will automatically register from string converters for
+ * a number of standard Java types like Class, Number, Boolean and so on.
+ *
+ * @author Keith Donald
+ */
+public class DefaultConversionService extends GenericConversionService {
+
+ /**
+ * Creates a new default conversion service, installing the default converters.
+ */
+ public DefaultConversionService() {
+ addDefaultConverters();
+ addDefaultAliases();
+ }
+
+ /**
+ * Add all default converters to the conversion service.
+ */
+ protected void addDefaultConverters() {
+ addConverter(new StringToByte());
+ addConverter(new StringToBoolean());
+ addConverter(new StringToCharacter());
+ addConverter(new StringToShort());
+ addConverter(new StringToInteger());
+ addConverter(new StringToLong());
+ addConverter(new StringToFloat());
+ addConverter(new StringToDouble());
+ addConverter(new StringToBigInteger());
+ addConverter(new StringToBigDecimal());
+ addConverter(new StringToLocale());
+ addConverter(new StringToEnum());
+ addConverter(new NumberToNumber());
+ addConverter(new ObjectToCollection(this));
+ addConverter(new CollectionToCollection(this));
+ }
+
+ protected void addDefaultAliases() {
+ addAlias("string", String.class);
+ addAlias("byte", Byte.class);
+ addAlias("boolean", Boolean.class);
+ addAlias("char", Character.class);
+ addAlias("short", Short.class);
+ addAlias("int", Integer.class);
+ addAlias("long", Long.class);
+ addAlias("float", Float.class);
+ addAlias("double", Double.class);
+ addAlias("bigInteger", BigInteger.class);
+ addAlias("bigDecimal", BigDecimal.class);
+ addAlias("locale", Locale.class);
+ addAlias("enum", Enum.class);
+ addAlias("date", Date.class);
+ }
+
+}
\ No newline at end of file
diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/service/GenericConversionService.java b/org.springframework.core/src/main/java/org/springframework/core/convert/service/GenericConversionService.java
new file mode 100644
index 00000000000..649b7b9205b
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/convert/service/GenericConversionService.java
@@ -0,0 +1,313 @@
+/*
+ * Copyright 2004-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.service;
+
+import java.lang.reflect.Modifier;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import org.springframework.core.GenericTypeResolver;
+import org.springframework.core.convert.ConversionException;
+import org.springframework.core.convert.ConversionExecutor;
+import org.springframework.core.convert.ConversionExecutorNotFoundException;
+import org.springframework.core.convert.ConversionService;
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.core.convert.converter.SuperConverter;
+import org.springframework.util.Assert;
+
+/**
+ * Base implementation of a conversion service. Initially empty, e.g. no converters are registered by default.
+ *
+ * @author Keith Donald
+ */
+@SuppressWarnings("unchecked")
+public class GenericConversionService implements ConversionService {
+
+ /**
+ * An indexed map of Converters. Each Map.Entry key is a source class (S) that can be converted from. Each Map.Entry
+ * value is a Map that defines the targetClass-to-Converter mappings for that source.
+ */
+ private final Map sourceClassConverters = new HashMap();
+
+ /**
+ * Indexes classes by well-known aliases.
+ */
+ private final Map aliasMap = new HashMap+ConversionService implementation. +
+ + \ No newline at end of file diff --git a/org.springframework.core/src/test/java/org/springframework/core/convert/service/DefaultConversionServiceTests.java b/org.springframework.core/src/test/java/org/springframework/core/convert/service/DefaultConversionServiceTests.java new file mode 100644 index 00000000000..96d07d198ce --- /dev/null +++ b/org.springframework.core/src/test/java/org/springframework/core/convert/service/DefaultConversionServiceTests.java @@ -0,0 +1,159 @@ +/* + * Copyright 2004-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.service; + +import java.util.AbstractList; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; + +import junit.framework.TestCase; + +import org.springframework.core.convert.ConversionExecutor; +import org.springframework.core.convert.ConversionExecutorNotFoundException; +import org.springframework.core.convert.ConversionService; +import org.springframework.core.convert.converter.Converter; +import org.springframework.core.convert.converter.StringToBoolean; + +/** + * Test case for the default conversion service. + * + * @author Keith Donald + */ +public class DefaultConversionServiceTests extends TestCase { + + ConversionService service = new DefaultConversionService(); + + public void testConversionForwardIndex() { + ConversionExecutor