Browse Source

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
pull/432/head
Juergen Hoeller 12 years ago
parent
commit
48909886a2
  1. 12
      spring-core/src/main/java/org/springframework/core/convert/support/DefaultConversionService.java
  2. 13
      spring-core/src/main/java/org/springframework/core/convert/support/FallbackObjectToStringConverter.java
  3. 59
      spring-core/src/main/java/org/springframework/core/convert/support/ObjectToObjectConverter.java
  4. 12
      spring-core/src/main/java/org/springframework/core/convert/support/ZoneIdToTimeZoneConverter.java
  5. 23
      spring-core/src/main/java/org/springframework/core/convert/support/ZonedDateTimeToCalendarConverter.java

12
spring-core/src/main/java/org/springframework/core/convert/support/DefaultConversionService.java

@ -38,7 +38,7 @@ import org.springframework.util.ClassUtils; @@ -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 { @@ -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 { @@ -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());
}
}

13
spring-core/src/main/java/org/springframework/core/convert/support/FallbackObjectToStringConverter.java

@ -1,5 +1,5 @@ @@ -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 @@ @@ -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; @@ -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.
*
* <p>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 @@ -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

59
spring-core/src/main/java/org/springframework/core/convert/support/ObjectToObjectConverter.java

@ -32,8 +32,9 @@ import org.springframework.util.ReflectionUtils; @@ -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.
*
* <p>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
* <p>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 { @@ -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 { @@ -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 { @@ -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);
}
}

12
spring-core/src/main/java/org/springframework/core/convert/support/ZoneIdToTimeZoneConverter.java

@ -22,17 +22,19 @@ import java.util.TimeZone; @@ -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}.
*
* <p>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
* <p>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<ZoneId, TimeZone> {
final class ZoneIdToTimeZoneConverter implements Converter<ZoneId, TimeZone> {
@Override
public TimeZone convert(ZoneId source) {

23
spring-core/src/main/java/org/springframework/core/convert/support/TimeZoneToZoneIdConverter.java → spring-core/src/main/java/org/springframework/core/convert/support/ZonedDateTimeToCalendarConverter.java

@ -16,27 +16,30 @@ @@ -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}.
*
* <p>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
* <p>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<TimeZone, ZoneId> {
final class ZonedDateTimeToCalendarConverter implements Converter<ZonedDateTime, Calendar> {
@Override
public ZoneId convert(TimeZone source) {
return source.toZoneId();
public Calendar convert(ZonedDateTime source) {
return GregorianCalendar.from(source);
}
}
Loading…
Cancel
Save