diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/service/ArrayToArray.java b/org.springframework.core/src/main/java/org/springframework/core/convert/service/ArrayToArray.java new file mode 100644 index 00000000000..8737613d40d --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/service/ArrayToArray.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.lang.reflect.Array; + +import org.springframework.core.convert.ConversionExecutor; +import org.springframework.core.convert.ConversionService; +import org.springframework.core.convert.converter.SuperConverter; + +/** + * Special one-way converter that converts from a source array to a target array. Supports type conversion of the + * individual array elements; for example, the ability to convert a String[] to an Integer[]. Mainly used internally by + * {@link ConversionService} implementations. + * + * @author Keith Donald + */ +@SuppressWarnings("unchecked") +class ArrayToArray implements SuperConverter { + + private ConversionService conversionService; + + private ConversionExecutor elementConverter; + + /** + * Creates a new array-to-array converter. + * @param conversionService the service to use to lookup conversion executors for individual array elements + * dynamically + */ + public ArrayToArray(ConversionService conversionService) { + this.conversionService = conversionService; + } + + /** + * Creates a new array-to-array converter. + * @param elementConverter a specific conversion executor to use to convert elements in the source array to elements + * in the target array. + */ + public ArrayToArray(ConversionExecutor elementConverter) { + this.elementConverter = elementConverter; + } + + public Class getSourceClass() { + return Object[].class; + } + + public Class getSuperTargetClass() { + return Object[].class; + } + + public Object convert(Object source, Class targetClass) throws Exception { + if (source == null) { + return null; + } + Class sourceComponentType = source.getClass().getComponentType(); + Class targetComponentType = targetClass.getComponentType(); + int length = Array.getLength(source); + Object targetArray = Array.newInstance(targetComponentType, length); + ConversionExecutor converter = getElementConverter(sourceComponentType, targetComponentType); + for (int i = 0; i < length; i++) { + Object value = Array.get(source, i); + Array.set(targetArray, i, converter.execute(value)); + } + return targetArray; + } + + public Object convertBack(Object target) throws Exception { + throw new UnsupportedOperationException("Not supported"); + } + + private ConversionExecutor getElementConverter(Class sourceComponentType, Class targetComponentType) { + if (elementConverter != null) { + return elementConverter; + } else { + return conversionService.getConversionExecutor(sourceComponentType, targetComponentType); + } + } +} diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/service/ArrayToCollection.java b/org.springframework.core/src/main/java/org/springframework/core/convert/service/ArrayToCollection.java new file mode 100644 index 00000000000..92404851338 --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/service/ArrayToCollection.java @@ -0,0 +1,147 @@ +/* + * 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.lang.reflect.Constructor; +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; + +/** + * 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 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>(); + + /** + * An optional parent conversion service. + */ + private ConversionService parent; + + /** + * Returns the parent of this conversion service. Could be null. + */ + public ConversionService getParent() { + return parent; + } + + /** + * Set the parent of this conversion service. This is optional. + */ + public void setParent(ConversionService parent) { + this.parent = parent; + } + + /** + * Register the Converter with this conversion service. + * @param converter the converter to register + */ + public void addConverter(Converter converter) { + List typeInfo = getTypeInfo(converter); + Class sourceClass = (Class) typeInfo.get(0); + Class targetClass = (Class) typeInfo.get(1); + // index forward + Map sourceMap = getSourceMap(sourceClass); + sourceMap.put(targetClass, converter); + // index reverse + sourceMap = getSourceMap(targetClass); + sourceMap.put(sourceClass, new ReverseConverter(converter)); + } + + /** + * Register the SuperConverter with this conversion service. + * @param converter the super converter to register + */ + public void addConverter(SuperConverter converter) { + // TODO + } + + /** + * Add a convenient alias for the target type. {@link #getClassForAlias(String)} can then be used to lookup the type + * given the alias. + * @see #getClassForAlias(String) + */ + public void addAlias(String alias, Class targetType) { + aliasMap.put(alias, targetType); + } + + public Object executeConversion(Object source, Class targetClass) throws ConversionExecutorNotFoundException, + ConversionException { + if (source != null) { + ConversionExecutor conversionExecutor = getConversionExecutor(source.getClass(), targetClass); + return conversionExecutor.execute(source); + } else { + return null; + } + } + + public Object executeConversion(String converterId, Object source, Class targetClass) + throws ConversionExecutorNotFoundException, ConversionException { + if (source != null) { + ConversionExecutor conversionExecutor = getConversionExecutor(converterId, source.getClass(), targetClass); + return conversionExecutor.execute(source); + } else { + return null; + } + } + + public ConversionExecutor getConversionExecutor(Class sourceClass, Class targetClass) + throws ConversionExecutorNotFoundException { + Assert.notNull(sourceClass, "The source class to convert from is required"); + Assert.notNull(targetClass, "The target class to convert to is required"); + if (targetClass.isAssignableFrom(sourceClass)) { + return new StaticConversionExecutor(sourceClass, targetClass, new NoOpConverter()); + } + sourceClass = convertToWrapperClassIfNecessary(sourceClass); + targetClass = convertToWrapperClassIfNecessary(targetClass); + // special handling for arrays since they are not indexable classes + if (sourceClass.isArray()) { + if (targetClass.isArray()) { + return new StaticSuperConversionExecutor(sourceClass, targetClass, new ArrayToArray(this)); + } else if (Collection.class.isAssignableFrom(targetClass)) { + if (!targetClass.isInterface() && Modifier.isAbstract(targetClass.getModifiers())) { + throw new IllegalArgumentException("Conversion target class [" + targetClass.getName() + + "] is invalid; cannot convert to abstract collection types--" + + "request an interface or concrete implementation instead"); + } + return new StaticSuperConversionExecutor(sourceClass, targetClass, new ArrayToCollection(this)); + } + } + if (targetClass.isArray()) { + if (Collection.class.isAssignableFrom(sourceClass)) { + return new StaticSuperConversionExecutor(sourceClass, targetClass, new CollectionToArray(this)); + } else { + return new StaticSuperConversionExecutor(sourceClass, targetClass, new ObjectToArray(this)); + } + } + Converter converter = findRegisteredConverter(sourceClass, targetClass); + if (converter != null) { + // we found a converter + return new StaticConversionExecutor(sourceClass, targetClass, converter); + } else { + if (parent != null) { + // try the parent + return parent.getConversionExecutor(sourceClass, targetClass); + } else { + throw new ConversionExecutorNotFoundException(sourceClass, targetClass, + "No ConversionExecutor found for converting from sourceClass [" + sourceClass.getName() + + "] to target class [" + targetClass.getName() + "]"); + } + } + } + + public ConversionExecutor getConversionExecutor(String converterId, Class sourceClass, Class targetClass) + throws ConversionExecutorNotFoundException { + throw new UnsupportedOperationException("Not yet implemented"); + } + + public Class getClassForAlias(String name) throws IllegalArgumentException { + Class clazz = (Class) aliasMap.get(name); + if (clazz != null) { + return clazz; + } else { + if (parent != null) { + return parent.getClassForAlias(name); + } else { + return null; + } + } + } + + // internal helpers + + private List getTypeInfo(Converter converter) { + List typeInfo = new ArrayList(2); + Class classToIntrospect = converter.getClass(); + while (classToIntrospect != null) { + Type[] genericInterfaces = converter.getClass().getGenericInterfaces(); + for (Type genericInterface : genericInterfaces) { + if (genericInterface instanceof ParameterizedType) { + ParameterizedType parameterizedInterface = (ParameterizedType) genericInterface; + if (Converter.class.equals(parameterizedInterface.getRawType())) { + Type s = parameterizedInterface.getActualTypeArguments()[0]; + Type t = parameterizedInterface.getActualTypeArguments()[1]; + typeInfo.add(getParameterClass(s, converter.getClass())); + typeInfo.add(getParameterClass(t, converter.getClass())); + } + } + } + classToIntrospect = classToIntrospect.getSuperclass(); + } + return typeInfo; + } + + private Class getParameterClass(Type parameterType, Class converterClass) { + if (parameterType instanceof TypeVariable) { + parameterType = GenericTypeResolver.resolveTypeVariable((TypeVariable) parameterType, converterClass); + } + if (parameterType instanceof Class) { + return (Class) parameterType; + } + // when would this happen? + return null; + } + + private Map getSourceMap(Class sourceClass) { + Map sourceMap = (Map) sourceClassConverters.get(sourceClass); + if (sourceMap == null) { + sourceMap = new HashMap, Converter>(); + sourceClassConverters.put(sourceClass, sourceMap); + } + return sourceMap; + } + + private Class convertToWrapperClassIfNecessary(Class targetType) { + if (targetType.isPrimitive()) { + if (targetType.equals(int.class)) { + return Integer.class; + } else if (targetType.equals(short.class)) { + return Short.class; + } else if (targetType.equals(long.class)) { + return Long.class; + } else if (targetType.equals(float.class)) { + return Float.class; + } else if (targetType.equals(double.class)) { + return Double.class; + } else if (targetType.equals(byte.class)) { + return Byte.class; + } else if (targetType.equals(boolean.class)) { + return Boolean.class; + } else if (targetType.equals(char.class)) { + return Character.class; + } else { + throw new IllegalStateException("Should never happen - primitive type is not a primitive?"); + } + } else { + return targetType; + } + } + + private Converter findRegisteredConverter(Class sourceClass, Class targetClass) { + if (sourceClass.isInterface()) { + LinkedList classQueue = new LinkedList(); + classQueue.addFirst(sourceClass); + while (!classQueue.isEmpty()) { + Class currentClass = (Class) classQueue.removeLast(); + Map sourceTargetConverters = findConvertersForSource(currentClass); + Converter converter = findTargetConverter(sourceTargetConverters, targetClass); + if (converter != null) { + return converter; + } + Class[] interfaces = currentClass.getInterfaces(); + for (int i = 0; i < interfaces.length; i++) { + classQueue.addFirst(interfaces[i]); + } + } + Map objectConverters = findConvertersForSource(Object.class); + return findTargetConverter(objectConverters, targetClass); + } else { + LinkedList classQueue = new LinkedList(); + classQueue.addFirst(sourceClass); + while (!classQueue.isEmpty()) { + Class currentClass = (Class) classQueue.removeLast(); + Map sourceTargetConverters = findConvertersForSource(currentClass); + Converter converter = findTargetConverter(sourceTargetConverters, targetClass); + if (converter != null) { + return converter; + } + if (currentClass.getSuperclass() != null) { + classQueue.addFirst(currentClass.getSuperclass()); + } + Class[] interfaces = currentClass.getInterfaces(); + for (int i = 0; i < interfaces.length; i++) { + classQueue.addFirst(interfaces[i]); + } + } + return null; + } + } + + private Map findConvertersForSource(Class sourceClass) { + Map sourceConverters = (Map) sourceClassConverters.get(sourceClass); + return sourceConverters != null ? sourceConverters : Collections.emptyMap(); + } + + private Converter findTargetConverter(Map sourceTargetConverters, Class targetClass) { + if (sourceTargetConverters.isEmpty()) { + return null; + } + return (Converter) sourceTargetConverters.get(targetClass); + } +} \ No newline at end of file diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/service/NoOpConverter.java b/org.springframework.core/src/main/java/org/springframework/core/convert/service/NoOpConverter.java new file mode 100644 index 00000000000..777a361aab4 --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/service/NoOpConverter.java @@ -0,0 +1,35 @@ +/* + * 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 org.springframework.core.convert.converter.Converter; + +/** + * Package private converter that is a "no op". + * + * @author Keith Donald + */ +@SuppressWarnings("unchecked") +class NoOpConverter implements Converter { + + public Object convert(Object source) throws Exception { + return source; + } + + public Object convertBack(Object target) throws Exception { + return target; + } +} \ No newline at end of file diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/service/ObjectToArray.java b/org.springframework.core/src/main/java/org/springframework/core/convert/service/ObjectToArray.java new file mode 100644 index 00000000000..eb7507ad1b9 --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/service/ObjectToArray.java @@ -0,0 +1,79 @@ +/* + * 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 org.springframework.core.convert.ConversionExecutor; +import org.springframework.core.convert.ConversionService; +import org.springframework.core.convert.converter.SuperConverter; + +/** + * Special two-way converter that converts an object to an single-element array. Mainly used internally by + * {@link ConversionService} implementations. + * + * @author Keith Donald + */ +@SuppressWarnings("unchecked") +class ObjectToArray implements SuperConverter { + + private ConversionService conversionService; + + private ConversionExecutor elementConverter; + + /** + * Creates a new object to array converter. + * @param conversionService the conversion service to resolve the converter to use to convert the object added to + * the target array. + */ + public ObjectToArray(ConversionService conversionService) { + this.conversionService = conversionService; + } + + /** + * Creates a new object to array converter. + * @param elementConverter a specific converter to use to convert the object added to the target array. + */ + public ObjectToArray(ConversionExecutor elementConverter) { + this.elementConverter = elementConverter; + } + + public Class getSourceClass() { + return Object.class; + } + + public Class getSuperTargetClass() { + return Object[].class; + } + + public Object convert(Object source, Class targetClass) throws Exception { + Class componentType = targetClass.getComponentType(); + Object array = Array.newInstance(componentType, 1); + ConversionExecutor converter; + if (elementConverter != null) { + converter = elementConverter; + } else { + converter = conversionService.getConversionExecutor(source.getClass(), componentType); + } + Array.set(array, 0, converter.execute(source)); + return array; + } + + public Object convertBack(Object target) throws Exception { + throw new UnsupportedOperationException("Not supported"); + } + +} \ No newline at end of file diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/service/ObjectToCollection.java b/org.springframework.core/src/main/java/org/springframework/core/convert/service/ObjectToCollection.java new file mode 100644 index 00000000000..f3169c81514 --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/service/ObjectToCollection.java @@ -0,0 +1,121 @@ +/* + * 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.Constructor; +import java.util.ArrayList; +import java.util.Collection; +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; + +/** + * Special two-way converter that converts an object to an single-element collection. Supports type conversion of the + * individual element with parameterized collection implementations. + * + * @author Keith Donald + */ +@SuppressWarnings("unchecked") +class ObjectToCollection implements SuperConverter { + + private ConversionService conversionService; + + private ConversionExecutor elementConverter; + + /** + * Creates a new object to collection converter + * @param conversionService the conversion service to lookup the converter to use to convert an object when adding + * it to a target collection + */ + public ObjectToCollection(ConversionService conversionService) { + this.conversionService = conversionService; + } + + /** + * Creates a new object to collection converter + * @param elementConverter a specific converter to execute on an object when adding it to a target collection + */ + public ObjectToCollection(ConversionExecutor elementConverter) { + this.elementConverter = elementConverter; + } + + public Class getSourceClass() { + return Object.class; + } + + public Class getSuperTargetClass() { + return Collection.class; + } + + 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 = getElementConverter(source, targetClass); + Object value; + if (converter != null) { + value = converter.execute(source); + } else { + value = source; + } + collection.add(value); + return collection; + } + + public Object convertBack(Object target) throws Exception { + throw new UnsupportedOperationException("Not supported"); + } + + // this code is duplicated in ArrayToCollection and CollectionToCollection + 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/ReverseConverter.java b/org.springframework.core/src/main/java/org/springframework/core/convert/service/ReverseConverter.java new file mode 100644 index 00000000000..dbd4de98bfa --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/service/ReverseConverter.java @@ -0,0 +1,36 @@ +/* + * 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 org.springframework.core.convert.converter.Converter; + +@SuppressWarnings("unchecked") +class ReverseConverter implements Converter { + + private Converter converter; + + public ReverseConverter(Converter converter) { + this.converter = converter; + } + + public Object convert(Object source) throws Exception { + return converter.convertBack(source); + } + + public Object convertBack(Object target) throws Exception { + throw new IllegalStateException("Should not be called"); + } +} diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/service/StaticConversionExecutor.java b/org.springframework.core/src/main/java/org/springframework/core/convert/service/StaticConversionExecutor.java new file mode 100644 index 00000000000..a4e41c5998b --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/service/StaticConversionExecutor.java @@ -0,0 +1,81 @@ +/* + * Copyright 2004-2008 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 org.springframework.core.convert.ConversionExecutionException; +import org.springframework.core.convert.ConversionExecutor; +import org.springframework.core.convert.converter.Converter; +import org.springframework.core.style.ToStringCreator; +import org.springframework.util.Assert; + +@SuppressWarnings("unchecked") +class StaticConversionExecutor implements ConversionExecutor { + + private final Class sourceClass; + + private final Class targetClass; + + private final Converter converter; + + public StaticConversionExecutor(Class sourceClass, Class targetClass, Converter converter) { + Assert.notNull(sourceClass, "The source class is required"); + Assert.notNull(targetClass, "The target class is required"); + Assert.notNull(converter, "The converter is required"); + this.sourceClass = sourceClass; + this.targetClass = targetClass; + this.converter = converter; + } + + public Class getSourceClass() { + return sourceClass; + } + + public Class getTargetClass() { + return targetClass; + } + + public Object execute(Object source) throws ConversionExecutionException { + if (source == null) { + return null; + } + if (!sourceClass.isInstance(source)) { + throw new ConversionExecutionException(source, getSourceClass(), getTargetClass(), "Source object " + + source + " to convert is expected to be an instance of [" + getSourceClass().getName() + "]"); + } + try { + return converter.convert(source); + } catch (Exception e) { + throw new ConversionExecutionException(source, getSourceClass(), getTargetClass(), e); + } + } + + public boolean equals(Object o) { + if (!(o instanceof StaticConversionExecutor)) { + return false; + } + StaticConversionExecutor other = (StaticConversionExecutor) o; + return sourceClass.equals(other.sourceClass) && targetClass.equals(other.targetClass); + } + + public int hashCode() { + return sourceClass.hashCode() + targetClass.hashCode(); + } + + public String toString() { + return new ToStringCreator(this).append("sourceClass", sourceClass).append("targetClass", targetClass) + .toString(); + } +} \ No newline at end of file diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/service/StaticSuperConversionExecutor.java b/org.springframework.core/src/main/java/org/springframework/core/convert/service/StaticSuperConversionExecutor.java new file mode 100644 index 00000000000..8cb543c3c66 --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/service/StaticSuperConversionExecutor.java @@ -0,0 +1,81 @@ +/* + * Copyright 2004-2008 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 org.springframework.core.convert.ConversionExecutionException; +import org.springframework.core.convert.ConversionExecutor; +import org.springframework.core.convert.converter.SuperConverter; +import org.springframework.core.style.ToStringCreator; +import org.springframework.util.Assert; + +@SuppressWarnings("unchecked") +class StaticSuperConversionExecutor implements ConversionExecutor { + + private final Class sourceClass; + + private final Class targetClass; + + private final SuperConverter converter; + + public StaticSuperConversionExecutor(Class sourceClass, Class targetClass, SuperConverter converter) { + Assert.notNull(sourceClass, "The source class is required"); + Assert.notNull(targetClass, "The target class is required"); + Assert.notNull(converter, "The super converter is required"); + this.sourceClass = sourceClass; + this.targetClass = targetClass; + this.converter = converter; + } + + public Class getSourceClass() { + return sourceClass; + } + + public Class getTargetClass() { + return targetClass; + } + + public Object execute(Object source) throws ConversionExecutionException { + if (source == null) { + return null; + } + if (!sourceClass.isInstance(source)) { + throw new ConversionExecutionException(source, getSourceClass(), getTargetClass(), "Source object " + + source + " to convert is expected to be an instance of [" + getSourceClass().getName() + "]"); + } + try { + return converter.convert(source, targetClass); + } catch (Exception e) { + throw new ConversionExecutionException(source, getSourceClass(), getTargetClass(), e); + } + } + + public boolean equals(Object o) { + if (!(o instanceof StaticSuperConversionExecutor)) { + return false; + } + StaticSuperConversionExecutor other = (StaticSuperConversionExecutor) o; + return sourceClass.equals(other.sourceClass) && targetClass.equals(other.targetClass); + } + + public int hashCode() { + return sourceClass.hashCode() + targetClass.hashCode(); + } + + public String toString() { + return new ToStringCreator(this).append("sourceClass", sourceClass).append("targetClass", targetClass) + .toString(); + } +} \ No newline at end of file diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/service/package.html b/org.springframework.core/src/main/java/org/springframework/core/convert/service/package.html new file mode 100644 index 00000000000..e62832fd8b1 --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/service/package.html @@ -0,0 +1,7 @@ + + +

+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 executor = service.getConversionExecutor(String.class, Integer.class); + Integer three = executor.execute("3"); + assertEquals(3, three.intValue()); + } + + public void testConversionReverseIndex() { + ConversionExecutor executor = service.getConversionExecutor(Integer.class, String.class); + String threeString = executor.execute(new Integer(3)); + assertEquals("3", threeString); + } + + public void testConversionCompatibleTypes() { + ArrayList source = new ArrayList(); + assertSame(source, service.getConversionExecutor(ArrayList.class, List.class).execute(source)); + } + + public void testConversionOverrideDefaultConverter() { + Converter customConverter = new StringToBoolean("ja", "nee"); + ((GenericConversionService) service).addConverter(customConverter); + ConversionExecutor executor = service.getConversionExecutor(String.class, Boolean.class); + assertTrue(executor.execute("ja").booleanValue()); + } + + public void testConverterLookupTargetClassNotSupported() { + try { + service.getConversionExecutor(String.class, HashMap.class); + fail("Should have thrown an exception"); + } catch (ConversionExecutorNotFoundException e) { + } + } + + public void testConversionToPrimitive() { + DefaultConversionService service = new DefaultConversionService(); + ConversionExecutor executor = service.getConversionExecutor(String.class, int.class); + Integer three = (Integer) executor.execute("3"); + assertEquals(3, three.intValue()); + } + + public void testConversionArrayToArray() { + ConversionExecutor executor = service.getConversionExecutor(String[].class, Integer[].class); + Integer[] result = (Integer[]) executor.execute(new String[] { "1", "2", "3" }); + assertEquals(new Integer(1), result[0]); + assertEquals(new Integer(2), result[1]); + assertEquals(new Integer(3), result[2]); + } + + public void testConversionArrayToPrimitiveArray() { + ConversionExecutor executor = service.getConversionExecutor(String[].class, int[].class); + int[] result = (int[]) executor.execute(new String[] { "1", "2", "3" }); + assertEquals(1, result[0]); + assertEquals(2, result[1]); + assertEquals(3, result[2]); + } + + public void testConversionArrayToList() { + ConversionExecutor executor = service.getConversionExecutor(String[].class, List.class); + List result = (List) executor.execute(new String[] { "1", "2", "3" }); + assertEquals("1", result.get(0)); + assertEquals("2", result.get(1)); + assertEquals("3", result.get(2)); + } + + public void testConversionToArray() { + ConversionExecutor executor = service.getConversionExecutor(Collection.class, String[].class); + List list = new ArrayList(); + list.add("1"); + list.add("2"); + list.add("3"); + String[] result = (String[]) executor.execute(list); + assertEquals("1", result[0]); + assertEquals("2", result[1]); + assertEquals("3", result[2]); + } + + public void testConversionListToArrayWithComponentConversion() { + ConversionExecutor executor = service.getConversionExecutor(Collection.class, Integer[].class); + List list = new ArrayList(); + list.add("1"); + list.add("2"); + list.add("3"); + Integer[] result = (Integer[]) executor.execute(list); + assertEquals(new Integer(1), result[0]); + assertEquals(new Integer(2), result[1]); + assertEquals(new Integer(3), result[2]); + } + + public void testConversionArrayToConcreteList() { + ConversionExecutor executor = service.getConversionExecutor(String[].class, LinkedList.class); + LinkedList result = (LinkedList) executor.execute(new String[] { "1", "2", "3" }); + assertEquals("1", result.get(0)); + assertEquals("2", result.get(1)); + assertEquals("3", result.get(2)); + } + + public void testConversionArrayToAbstractList() { + try { + service.getConversionExecutor(String[].class, AbstractList.class); + } catch (IllegalArgumentException e) { + + } + } + + public void testConversionStringToArray() { + ConversionExecutor executor = service.getConversionExecutor(String.class, String[].class); + String[] result = (String[]) executor.execute("1,2,3"); + assertEquals(1, result.length); + assertEquals("1,2,3", result[0]); + } + + public void testConversionStringToArrayWithElementConversion() { + ConversionExecutor executor = service.getConversionExecutor(String.class, Integer[].class); + Integer[] result = (Integer[]) executor.execute("123"); + assertEquals(1, result.length); + assertEquals(new Integer(123), result[0]); + } + +} \ No newline at end of file