From 48909886a214265e3f2b3765bf2bf4a1c26bc791 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Mon, 30 Dec 2013 19:08:36 +0100 Subject: [PATCH] Added support for the Java 8 style 'from'/'to' method conventions Also introduced a default ZonedDateTime-Calendar converter which is not covered by the default convention due to the 'from' method only being defined on GregorianCalendar. Issue: SPR-11259 --- .../support/DefaultConversionService.java | 12 ++-- .../FallbackObjectToStringConverter.java | 13 ++-- .../support/ObjectToObjectConverter.java | 59 ++++++++++++------- .../support/ZoneIdToTimeZoneConverter.java | 12 ++-- ... => ZonedDateTimeToCalendarConverter.java} | 23 ++++---- 5 files changed, 72 insertions(+), 47 deletions(-) rename spring-core/src/main/java/org/springframework/core/convert/support/{TimeZoneToZoneIdConverter.java => ZonedDateTimeToCalendarConverter.java} (55%) diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/DefaultConversionService.java b/spring-core/src/main/java/org/springframework/core/convert/support/DefaultConversionService.java index 3daea6307e6..b9538a7d23b 100644 --- a/spring-core/src/main/java/org/springframework/core/convert/support/DefaultConversionService.java +++ b/spring-core/src/main/java/org/springframework/core/convert/support/DefaultConversionService.java @@ -38,7 +38,7 @@ import org.springframework.util.ClassUtils; public class DefaultConversionService extends GenericConversionService { /** Java 8's java.time package available? */ - private static final boolean zoneIdAvailable = + private static final boolean jsr310Available = ClassUtils.isPresent("java.time.ZoneId", DefaultConversionService.class.getClassLoader()); @@ -64,8 +64,8 @@ public class DefaultConversionService extends GenericConversionService { addCollectionConverters(converterRegistry); converterRegistry.addConverter(new ByteBufferConverter((ConversionService) converterRegistry)); - if (zoneIdAvailable) { - ZoneIdConverterRegistrar.registerZoneIdConverters(converterRegistry); + if (jsr310Available) { + Jsr310ConverterRegistrar.registerZoneIdConverters(converterRegistry); } converterRegistry.addConverter(new ObjectToObjectConverter()); @@ -129,13 +129,13 @@ public class DefaultConversionService extends GenericConversionService { /** - * Inner class to avoid a hard-coded dependency on Java 8's {@link java.time.ZoneId}. + * Inner class to avoid a hard-coded dependency on Java 8's {@code java.time} package. */ - private static final class ZoneIdConverterRegistrar { + private static final class Jsr310ConverterRegistrar { public static void registerZoneIdConverters(ConverterRegistry converterRegistry) { - converterRegistry.addConverter(new TimeZoneToZoneIdConverter()); converterRegistry.addConverter(new ZoneIdToTimeZoneConverter()); + converterRegistry.addConverter(new ZonedDateTimeToCalendarConverter()); } } diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/FallbackObjectToStringConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/FallbackObjectToStringConverter.java index 767a0acb467..90f15754852 100644 --- a/spring-core/src/main/java/org/springframework/core/convert/support/FallbackObjectToStringConverter.java +++ b/spring-core/src/main/java/org/springframework/core/convert/support/FallbackObjectToStringConverter.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. @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.core.convert.support; import java.io.StringWriter; @@ -21,10 +22,12 @@ import java.util.Set; import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.converter.ConditionalGenericConverter; +import org.springframework.util.ClassUtils; /** * Simply calls {@link Object#toString()} to convert any supported Object to a String. - * Supports CharSequence, StringWriter, and any class with a String constructor or {@code valueOf(String)} method. + * Supports CharSequence, StringWriter, and any class with a String constructor or + * {@code valueOf(String)} method. * *

Used by the default ConversionService as a fallback if there are no other explicit * to-String converters registered. @@ -46,8 +49,10 @@ final class FallbackObjectToStringConverter implements ConditionalGenericConvert if (String.class.equals(sourceClass)) { return false; } - return CharSequence.class.isAssignableFrom(sourceClass) || StringWriter.class.isAssignableFrom(sourceClass) || - ObjectToObjectConverter.hasValueOfMethodOrConstructor(sourceClass, String.class); + return (CharSequence.class.isAssignableFrom(sourceClass) || + StringWriter.class.isAssignableFrom(sourceClass) || + ObjectToObjectConverter.getOfMethod(sourceClass, String.class) != null || + ClassUtils.getConstructorIfAvailable(sourceClass, String.class) != null); } @Override 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 c40b33459fe..a7df486600a 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 @@ -32,8 +32,9 @@ import org.springframework.util.ReflectionUtils; * Generic converter that attempts to convert a source Object to a target type * by delegating to methods on the target type. * - *

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 + *

Calls a static {@code valueOf(sourceType)} or Java 8 style {@code of|from(sourceType)} + * method on the target type to perform the conversion, if such a method exists. Otherwise, + * it checks for a {@code to[targetType.simpleName]} method on the source type 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. * @@ -54,7 +55,9 @@ final class ObjectToObjectConverter implements ConditionalGenericConverter { // no conversion required return false; } - return hasValueOfMethodOrConstructor(targetType.getType(), sourceType.getType()); + return (String.class.equals(targetType.getType()) ? + (ClassUtils.getConstructorIfAvailable(String.class, sourceType.getType()) != null) : + hasToMethodOrOfMethodOrConstructor(targetType.getType(), sourceType.getType())); } @Override @@ -64,18 +67,23 @@ final class ObjectToObjectConverter implements ConditionalGenericConverter { } Class sourceClass = sourceType.getType(); Class targetClass = targetType.getType(); - Method method = getValueOfMethodOn(targetClass, sourceClass); try { - if (method != null) { - ReflectionUtils.makeAccessible(method); - return method.invoke(null, source); - } - else { - Constructor constructor = getConstructor(targetClass, sourceClass); - if (constructor != null) { - return constructor.newInstance(source); + if (!String.class.equals(targetClass)) { + Method method = getToMethod(targetClass, sourceClass); + if (method != null) { + ReflectionUtils.makeAccessible(method); + return method.invoke(source); + } + method = getOfMethod(targetClass, sourceClass); + if (method != null) { + ReflectionUtils.makeAccessible(method); + return method.invoke(null, source); } } + Constructor constructor = ClassUtils.getConstructorIfAvailable(targetClass, sourceClass); + if (constructor != null) { + return constructor.newInstance(source); + } } catch (InvocationTargetException ex) { throw new ConversionFailedException(sourceType, targetType, source, ex.getTargetException()); @@ -83,24 +91,31 @@ final class ObjectToObjectConverter implements ConditionalGenericConverter { catch (Throwable ex) { throw new ConversionFailedException(sourceType, targetType, source, ex); } - throw new IllegalStateException("No static valueOf(" + sourceClass.getName() + + throw new IllegalStateException("No static valueOf/of/from(" + sourceClass.getName() + ") method or Constructor(" + sourceClass.getName() + ") exists on " + targetClass.getName()); } - static boolean hasValueOfMethodOrConstructor(Class clazz, Class sourceParameterType) { - return getValueOfMethodOn(clazz, sourceParameterType) != null || getConstructor(clazz, sourceParameterType) != null; + + private static boolean hasToMethodOrOfMethodOrConstructor(Class targetClass, Class sourceClass) { + return (getToMethod(targetClass, sourceClass) != null || + getOfMethod(targetClass, sourceClass) != null || + ClassUtils.getConstructorIfAvailable(targetClass, sourceClass) != null); } - private static Method getValueOfMethodOn(Class clazz, Class sourceParameterType) { - Method method = ClassUtils.getStaticMethod(clazz, "valueOf", sourceParameterType); + private static Method getToMethod(Class targetClass, Class sourceClass) { + Method method = ClassUtils.getMethodIfAvailable(sourceClass, "to" + targetClass.getSimpleName()); + return (method != null && targetClass.equals(method.getReturnType()) ? method : null); + } + + static Method getOfMethod(Class targetClass, Class sourceClass) { + Method method = ClassUtils.getStaticMethod(targetClass, "valueOf", sourceClass); if (method == null) { - method = ClassUtils.getStaticMethod(clazz, "of", sourceParameterType); + method = ClassUtils.getStaticMethod(targetClass, "of", sourceClass); + if (method == null) { + method = ClassUtils.getStaticMethod(targetClass, "from", sourceClass); + } } return method; } - private static Constructor getConstructor(Class clazz, Class sourceParameterType) { - return ClassUtils.getConstructorIfAvailable(clazz, sourceParameterType); - } - } diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/ZoneIdToTimeZoneConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/ZoneIdToTimeZoneConverter.java index 261c4e3ffe1..e0416ea40e3 100644 --- a/spring-core/src/main/java/org/springframework/core/convert/support/ZoneIdToTimeZoneConverter.java +++ b/spring-core/src/main/java/org/springframework/core/convert/support/ZoneIdToTimeZoneConverter.java @@ -22,17 +22,19 @@ import java.util.TimeZone; import org.springframework.core.convert.converter.Converter; /** - * Simple Converter from Java 8's {@link java.time.ZoneId} to {@link java.util.TimeZone}. + * Simple converter from Java 8's {@link java.time.ZoneId} to {@link java.util.TimeZone}. * - *

Note that Spring's default ConversionService setup understands the 'of' convention that - * the JSR-310 {@code java.time} package consistently uses. That convention is implemented + *

Note that Spring's default ConversionService setup understands the 'from'/'to' convention + * that the JSR-310 {@code java.time} package consistently uses. That convention is implemented * reflectively in {@link ObjectToObjectConverter}, not in specific JSR-310 converters. + * It covers {@link java.util.TimeZone#toZoneId()} as well, and also + * {@link java.util.Date#from(java.time.Instant)} and {@link java.util.Date#toInstant()}. * * @author Juergen Hoeller * @since 4.0 - * @see TimeZoneToZoneIdConverter + * @see TimeZone#getTimeZone(java.time.ZoneId) */ -class ZoneIdToTimeZoneConverter implements Converter { +final class ZoneIdToTimeZoneConverter implements Converter { @Override public TimeZone convert(ZoneId source) { diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/TimeZoneToZoneIdConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/ZonedDateTimeToCalendarConverter.java similarity index 55% rename from spring-core/src/main/java/org/springframework/core/convert/support/TimeZoneToZoneIdConverter.java rename to spring-core/src/main/java/org/springframework/core/convert/support/ZonedDateTimeToCalendarConverter.java index 991ee4a4905..02d6017dc4d 100644 --- a/spring-core/src/main/java/org/springframework/core/convert/support/TimeZoneToZoneIdConverter.java +++ b/spring-core/src/main/java/org/springframework/core/convert/support/ZonedDateTimeToCalendarConverter.java @@ -16,27 +16,30 @@ package org.springframework.core.convert.support; -import java.time.ZoneId; -import java.util.TimeZone; +import java.time.ZonedDateTime; +import java.util.Calendar; +import java.util.GregorianCalendar; import org.springframework.core.convert.converter.Converter; /** - * Simple Converter from {@link java.util.TimeZone} to Java 8's {@link java.time.ZoneId}. + * Simple converter from Java 8's {@link java.time.ZonedDateTime} to {@link java.util.Calendar}. * - *

Note that Spring's default ConversionService setup understands the 'of' convention that - * the JSR-310 {@code java.time} package consistently uses. That convention is implemented + *

Note that Spring's default ConversionService setup understands the 'from'/'to' convention + * that the JSR-310 {@code java.time} package consistently uses. That convention is implemented * reflectively in {@link ObjectToObjectConverter}, not in specific JSR-310 converters. + * It covers {@link java.util.GregorianCalendar#toZonedDateTime()} as well, and also + * {@link java.util.Date#from(java.time.Instant)} and {@link java.util.Date#toInstant()}. * * @author Juergen Hoeller - * @since 4.0 - * @see ZoneIdToTimeZoneConverter + * @since 4.0.1 + * @see java.util.GregorianCalendar#from(java.time.ZonedDateTime) */ -class TimeZoneToZoneIdConverter implements Converter { +final class ZonedDateTimeToCalendarConverter implements Converter { @Override - public ZoneId convert(TimeZone source) { - return source.toZoneId(); + public Calendar convert(ZonedDateTime source) { + return GregorianCalendar.from(source); } }