From c664010001c840532080ceb2b92fb93498d24780 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Thu, 29 Aug 2013 19:15:22 +0200 Subject: [PATCH] Added conversion support for Java 8's ZoneId class and the 'of' method convention Issue: SPR-1528 --- .../beans/PropertyEditorRegistrySupport.java | 17 ++ .../beans/propertyeditors/TimeZoneEditor.java | 12 +- .../beans/propertyeditors/ZoneIdEditor.java | 44 ++++++ .../support/ObjectToObjectConverter.java | 18 ++- .../support/DefaultConversionTests.java | 149 +++++++++--------- 5 files changed, 154 insertions(+), 86 deletions(-) create mode 100644 spring-beans/src/main/java/org/springframework/beans/propertyeditors/ZoneIdEditor.java diff --git a/spring-beans/src/main/java/org/springframework/beans/PropertyEditorRegistrySupport.java b/spring-beans/src/main/java/org/springframework/beans/PropertyEditorRegistrySupport.java index 1281e90328..42a563f565 100644 --- a/spring-beans/src/main/java/org/springframework/beans/PropertyEditorRegistrySupport.java +++ b/spring-beans/src/main/java/org/springframework/beans/PropertyEditorRegistrySupport.java @@ -65,6 +65,7 @@ import org.springframework.beans.propertyeditors.TimeZoneEditor; import org.springframework.beans.propertyeditors.URIEditor; import org.springframework.beans.propertyeditors.URLEditor; import org.springframework.beans.propertyeditors.UUIDEditor; +import org.springframework.beans.propertyeditors.ZoneIdEditor; import org.springframework.core.convert.ConversionService; import org.springframework.core.io.Resource; import org.springframework.core.io.support.ResourceArrayPropertyEditor; @@ -84,6 +85,19 @@ import org.springframework.util.ClassUtils; */ public class PropertyEditorRegistrySupport implements PropertyEditorRegistry { + private static Class zoneIdClass; + + static { + try { + zoneIdClass = PropertyEditorRegistrySupport.class.getClassLoader().loadClass("java.time.ZoneId"); + } + catch (ClassNotFoundException ex) { + // Java 8 ZoneId class not available + zoneIdClass = null; + } + } + + private ConversionService conversionService; private boolean defaultEditorsActive = false; @@ -202,6 +216,9 @@ public class PropertyEditorRegistrySupport implements PropertyEditorRegistry { this.defaultEditors.put(URI.class, new URIEditor()); this.defaultEditors.put(URL.class, new URLEditor()); this.defaultEditors.put(UUID.class, new UUIDEditor()); + if (zoneIdClass != null) { + this.defaultEditors.put(zoneIdClass, new ZoneIdEditor()); + } // Default instances of collection editors. // Can be overridden by registering custom instances of those as custom editors. diff --git a/spring-beans/src/main/java/org/springframework/beans/propertyeditors/TimeZoneEditor.java b/spring-beans/src/main/java/org/springframework/beans/propertyeditors/TimeZoneEditor.java index 632f60127c..0735c47d61 100644 --- a/spring-beans/src/main/java/org/springframework/beans/propertyeditors/TimeZoneEditor.java +++ b/spring-beans/src/main/java/org/springframework/beans/propertyeditors/TimeZoneEditor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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. @@ -21,11 +21,12 @@ import java.util.TimeZone; /** * Editor for {@code java.util.TimeZone}, translating timezone IDs into - * TimeZone objects. Does not expose a text representation for TimeZone objects. + * TimeZone objects. Exposed the TimeZone ID as a text representation. * * @author Juergen Hoeller * @since 3.0 * @see java.util.TimeZone + * @see ZoneIdEditor */ public class TimeZoneEditor extends PropertyEditorSupport { @@ -34,13 +35,10 @@ public class TimeZoneEditor extends PropertyEditorSupport { setValue(TimeZone.getTimeZone(text)); } - /** - * This implementation returns {@code null} to indicate that - * there is no appropriate text representation. - */ @Override public String getAsText() { - return null; + TimeZone value = (TimeZone) getValue(); + return (value != null ? value.getID() : ""); } } diff --git a/spring-beans/src/main/java/org/springframework/beans/propertyeditors/ZoneIdEditor.java b/spring-beans/src/main/java/org/springframework/beans/propertyeditors/ZoneIdEditor.java new file mode 100644 index 0000000000..1c95051c12 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/propertyeditors/ZoneIdEditor.java @@ -0,0 +1,44 @@ +/* + * Copyright 2002-2013 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.beans.propertyeditors; + +import java.beans.PropertyEditorSupport; +import java.time.ZoneId; + +/** + * Editor for {@code java.time.ZoneId}, translating zone ID Strings into + * ZoneId objects. Exposed the TimeZone ID as a text representation. + * + * @author Juergen Hoeller + * @since 4.0 + * @see java.time.ZoneId + * @see TimeZoneEditor + */ +public class ZoneIdEditor extends PropertyEditorSupport { + + @Override + public void setAsText(String text) throws IllegalArgumentException { + setValue(ZoneId.of(text)); + } + + @Override + public String getAsText() { + ZoneId value = (ZoneId) getValue(); + return (value != null ? value.getId() : ""); + } + +} diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/ObjectToObjectConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/ObjectToObjectConverter.java index e1273221b8..c40b33459f 100644 --- a/spring-core/src/main/java/org/springframework/core/convert/support/ObjectToObjectConverter.java +++ b/spring-core/src/main/java/org/springframework/core/convert/support/ObjectToObjectConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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. @@ -29,13 +29,13 @@ import org.springframework.util.ClassUtils; import org.springframework.util.ReflectionUtils; /** - * Generic Converter that attempts to convert a source Object to a target type + * Generic converter that attempts to convert a source Object to a target type * by delegating to methods on the target type. * - *

Calls the static {@code valueOf(sourceType)} method on the target type - * to perform the conversion, if such a method exists. Else calls the target type's - * Constructor that accepts a single sourceType argument, if such a Constructor exists. - * Else throws a ConversionFailedException. + *

Calls a static {@code valueOf(sourceType)} or Java 8 style {@code of(sourceType)} method + * on the target type to perform the conversion, if such a method exists. Otherwise, it calls + * the target type's constructor that accepts a single {@code sourceType} argument, if such + * a constructor exists. If neither strategy works, it throws a ConversionFailedException. * * @author Keith Donald * @author Juergen Hoeller @@ -92,7 +92,11 @@ final class ObjectToObjectConverter implements ConditionalGenericConverter { } private static Method getValueOfMethodOn(Class clazz, Class sourceParameterType) { - return ClassUtils.getStaticMethod(clazz, "valueOf", sourceParameterType); + Method method = ClassUtils.getStaticMethod(clazz, "valueOf", sourceParameterType); + if (method == null) { + method = ClassUtils.getStaticMethod(clazz, "of", sourceParameterType); + } + return method; } private static Constructor getConstructor(Class clazz, Class sourceParameterType) { diff --git a/spring-core/src/test/java/org/springframework/core/convert/support/DefaultConversionTests.java b/spring-core/src/test/java/org/springframework/core/convert/support/DefaultConversionTests.java index 1a037f05c1..03ff8c13b1 100644 --- a/spring-core/src/test/java/org/springframework/core/convert/support/DefaultConversionTests.java +++ b/spring-core/src/test/java/org/springframework/core/convert/support/DefaultConversionTests.java @@ -16,19 +16,10 @@ package org.springframework.core.convert.support; -import static org.hamcrest.Matchers.equalTo; -import static org.junit.Assert.*; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; - import java.awt.Color; import java.math.BigDecimal; import java.math.BigInteger; +import java.time.ZoneId; import java.util.AbstractList; import java.util.ArrayList; import java.util.Arrays; @@ -45,6 +36,7 @@ import java.util.Properties; import java.util.Set; import org.junit.Test; + import org.springframework.core.MethodParameter; import org.springframework.core.convert.ConversionFailedException; import org.springframework.core.convert.ConverterNotFoundException; @@ -52,6 +44,9 @@ import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.ConverterRegistry; +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + /** * @author Keith Donald * @author Juergen Hoeller @@ -709,6 +704,11 @@ public class DefaultConversionTests { assertEquals("123456789", conversionService.convert(new SSN("123456789"), String.class)); } + @Test + public void convertObjectToStringWithJavaTimeOfMethodPresent() { + assertTrue(conversionService.convert(ZoneId.of("GMT+1"), String.class).startsWith("GMT+")); + } + @Test public void convertObjectToStringNotSupported() { assertFalse(conversionService.canConvert(TestEntity.class, String.class)); @@ -725,73 +725,16 @@ public class DefaultConversionTests { assertEquals("123456789", conversionService.convert(new SSN("123456789"), String.class)); } + @Test + public void convertObjectToObjectWithJavaTimeOfMethod() { + assertEquals(ZoneId.of("GMT+1"), conversionService.convert("GMT+1", ZoneId.class)); + } + @Test(expected=ConverterNotFoundException.class) public void convertObjectToObjectNoValueOFMethodOrConstructor() { conversionService.convert(new Long(3), SSN.class); } - public Object assignableTarget; - - private static class SSN { - - private String value; - - public SSN(String value) { - this.value = value; - } - - @Override - public boolean equals(Object o) { - if (!(o instanceof SSN)) { - return false; - } - SSN ssn = (SSN) o; - return this.value.equals(ssn.value); - } - - @Override - public int hashCode() { - return value.hashCode(); - } - - @Override - public String toString() { - return value; - } - } - - private static class ISBN { - - private String value; - - private ISBN(String value) { - this.value = value; - } - - @Override - public boolean equals(Object o) { - if (!(o instanceof ISBN)) { - return false; - } - ISBN isbn = (ISBN) o; - return this.value.equals(isbn.value); - } - - @Override - public int hashCode() { - return value.hashCode(); - } - - @Override - public String toString() { - return value; - } - - public static ISBN valueOf(String value) { - return new ISBN(value); - } - } - @Test public void convertObjectToObjectFinderMethod() { TestEntity e = conversionService.convert(1L, TestEntity.class); @@ -864,4 +807,66 @@ public class DefaultConversionTests { } } + public Object assignableTarget; + + private static class SSN { + + private String value; + + public SSN(String value) { + this.value = value; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof SSN)) { + return false; + } + SSN ssn = (SSN) o; + return this.value.equals(ssn.value); + } + + @Override + public int hashCode() { + return value.hashCode(); + } + + @Override + public String toString() { + return value; + } + } + + private static class ISBN { + + private String value; + + private ISBN(String value) { + this.value = value; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof ISBN)) { + return false; + } + ISBN isbn = (ISBN) o; + return this.value.equals(isbn.value); + } + + @Override + public int hashCode() { + return value.hashCode(); + } + + @Override + public String toString() { + return value; + } + + public static ISBN valueOf(String value) { + return new ISBN(value); + } + } + }