From b163f123ce7fb288ebc5f8cad8b2023f1ca4f587 Mon Sep 17 00:00:00 2001 From: Keith Donald Date: Thu, 12 Nov 2009 05:25:03 +0000 Subject: [PATCH] valueOf and Constructor-based conversion of source objects to targetTypes --- .../support/DefaultConversionService.java | 1 - .../support/GenericConversionService.java | 4 +- .../ObjectToObjectGenericConverter.java | 70 +++++++++++++++++++ .../GenericConversionServiceTests.java | 46 +++++++++++- 4 files changed, 118 insertions(+), 3 deletions(-) create mode 100644 org.springframework.core/src/main/java/org/springframework/core/convert/support/ObjectToObjectGenericConverter.java diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/support/DefaultConversionService.java b/org.springframework.core/src/main/java/org/springframework/core/convert/support/DefaultConversionService.java index dba0a1300a4..1321e4ef8b9 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/support/DefaultConversionService.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/support/DefaultConversionService.java @@ -36,7 +36,6 @@ public class DefaultConversionService extends GenericConversionService { addConverter(String.class, Character.class, new StringToCharacterConverter()); addConverter(String.class, Locale.class, new StringToLocaleConverter()); addConverter(Number.class, Character.class, new NumberToCharacterConverter()); - addConverter(Object.class, String.class, new ObjectToStringConverter()); addConverterFactory(String.class, Number.class, new StringToNumberConverterFactory()); addConverterFactory(String.class, Enum.class, new StringToEnumConverterFactory()); addConverterFactory(Number.class, Number.class, new NumberToNumberConverterFactory()); 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 724080d400b..1c6027e70d3 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 @@ -80,7 +80,9 @@ public class GenericConversionService implements ConversionService, ConverterReg addGenericConverter(Map.class, Object.class, new MapToObjectConverter(this)); addGenericConverter(Object.class, Object[].class, new ObjectToArrayConverter(this)); addGenericConverter(Object.class, Collection.class, new ObjectToCollectionConverter(this)); - addGenericConverter(Object.class, Map.class, new ObjectToMapConverter(this)); + addGenericConverter(Object.class, Map.class, new ObjectToMapConverter(this)); + addConverter(Object.class, String.class, new ObjectToStringConverter()); + addGenericConverter(Object.class, Object.class, new ObjectToObjectGenericConverter()); } /** diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/support/ObjectToObjectGenericConverter.java b/org.springframework.core/src/main/java/org/springframework/core/convert/support/ObjectToObjectGenericConverter.java new file mode 100644 index 00000000000..ac72b7f93a0 --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/support/ObjectToObjectGenericConverter.java @@ -0,0 +1,70 @@ +/* + * 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.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import org.springframework.core.convert.ConversionFailedException; +import org.springframework.core.convert.TypeDescriptor; +import org.springframework.util.ClassUtils; +import org.springframework.util.ReflectionUtils; + +/** + * Generic Converter that attempts to convert a source Object to a targetType by delegating to methods on the targetType. + * Calls the static valueOf(sourceType) method on the targetType to perform the conversion, if such a method exists. + * Else calls the targetType's Constructor that accepts a single sourceType argument, if such a Constructor exists. + * Else throws a ConversionFailedException. + * + * @author Keith Donald + * @since 3.0 + */ +final class ObjectToObjectGenericConverter implements GenericConverter { + + public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { + if (sourceType.isAssignableTo(targetType)) { + return source; + } + Class sourceClass = sourceType.getObjectType(); + Class targetClass = targetType.getObjectType(); + Object target; + Method method = ClassUtils.getStaticMethod(targetClass, "valueOf", sourceClass); + if (method != null) { + target = ReflectionUtils.invokeMethod(method, null, source); + } else { + Constructor constructor = ClassUtils.getConstructorIfAvailable(targetClass, sourceClass); + if (constructor != null) { + try { + target = constructor.newInstance(source); + } catch (IllegalArgumentException e) { + throw new ConversionFailedException(sourceType, targetType, source, e); + } catch (InstantiationException e) { + throw new ConversionFailedException(sourceType, targetType, source, e); + } catch (IllegalAccessException e) { + throw new ConversionFailedException(sourceType, targetType, source, e); + } catch (InvocationTargetException e) { + throw new ConversionFailedException(sourceType, targetType, source, e); + } + } else { + throw new IllegalStateException("No static valueOf(" + sourceClass.getName() + + ") method or Constructor(" + sourceClass.getName() + ") exists on " + targetClass.getName()); + } + } + return target; + } +} 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 6089147cbfb..1191c036a4c 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 @@ -67,6 +67,7 @@ public class GenericConversionServiceTests { @Test public void converterNotFound() { try { + conversionService.removeConvertible(Object.class, Object.class); conversionService.convert("3", Integer.class); fail("Should have thrown an exception"); } catch (ConverterNotFoundException e) { @@ -125,6 +126,7 @@ public class GenericConversionServiceTests { @Test public void convertObjectToPrimitive() { + conversionService.removeConvertible(Object.class, Object.class); assertFalse(conversionService.canConvert(String.class, boolean.class)); conversionService.addConverter(new StringToBooleanConverter()); assertTrue(conversionService.canConvert(String.class, boolean.class)); @@ -660,6 +662,46 @@ public class GenericConversionServiceTests { assertEquals(new Long(1), result.get(1L)); } + @Test + public void convertObjectToObjectValueOFMethod() { + assertEquals(new Integer(3), conversionService.convert("3", Integer.class)); + } + + @Test + public void convertObjectToObjectConstructor() { + assertEquals(new SSN("123456789"), conversionService.convert("123456789", SSN.class)); + assertEquals("123456789", conversionService.convert(new SSN("123456789"), String.class)); + } + + @Test(expected=ConversionFailedException.class) + public void convertObjectToObjectNoValueOFMethodOrConstructor() { + conversionService.convert(new Long(3), Integer.class); + } + + private static class SSN { + private String value; + + public SSN(String value) { + this.value = value; + } + + public boolean equals(Object o) { + if (!(o instanceof SSN)) { + return false; + } + SSN ssn = (SSN) o; + return this.value.equals(ssn.value); + } + + public int hashCode() { + return value.hashCode(); + } + + public String toString() { + return value; + } + } + @Test public void genericConverterDelegatingBackToConversionServiceConverterNotFound() { conversionService.addGenericConverter(Object.class, Object[].class, new ObjectToArrayConverter( @@ -675,6 +717,8 @@ public class GenericConversionServiceTests { public void parent() { GenericConversionService parent = new GenericConversionService(); conversionService.setParent(parent); + conversionService.removeConvertible(Object.class, Object.class); + parent.removeConvertible(Object.class, Object.class); assertFalse(conversionService.canConvert(String.class, Integer.class)); try { conversionService.convert("3", Integer.class); @@ -682,7 +726,7 @@ public class GenericConversionServiceTests { } } - + public static enum FooEnum { BAR, BAZ }