DATACMNS-937 - Support for JavaSlang's Option type as nullable wrapper.

Repository methods can now return JavaSlang's Option as alternative to JDK 8's Optional or Guavas Optional.
This commit is contained in:
Oliver Gierke
2016-11-19 12:45:02 +01:00
parent f3433a4fd4
commit 71fc87ad42
3 changed files with 160 additions and 4 deletions

View File

@@ -19,11 +19,13 @@ import scala.Function0;
import scala.Option;
import scala.runtime.AbstractFunction0;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import java.util.function.Supplier;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
@@ -33,6 +35,7 @@ import org.springframework.core.convert.support.ConfigurableConversionService;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.concurrent.ListenableFuture;
import com.google.common.base.Optional;
@@ -43,10 +46,11 @@ import com.google.common.base.Optional;
* <ul>
* <li>{@code java.util.Optional}</li>
* <li>{@code com.google.common.base.Optional}</li>
* <li>{@code scala.Option}</li>
* <li>{@code scala.Option} - as of 1.12</li>
* <li>{@code java.util.concurrent.Future}</li>
* <li>{@code java.util.concurrent.CompletableFuture}</li>
* <li>{@code org.springframework.util.concurrent.ListenableFuture<}</li>
* <li>{@code javaslang.control.Option} - as of 1.13</li>
* <li>Reactive wrappers supported by {@link ReactiveWrappers}</li>
* </ul>
*
@@ -70,6 +74,8 @@ public abstract class QueryExecutionConverters {
QueryExecutionConverters.class.getClassLoader());
private static final boolean SCALA_PRESENT = ClassUtils.isPresent("scala.Option",
QueryExecutionConverters.class.getClassLoader());
private static final boolean JAVASLANG_PRESENT = ClassUtils.isPresent("javaslang.control.Option",
QueryExecutionConverters.class.getClassLoader());
private static final Set<Class<?>> WRAPPER_TYPES = new HashSet<Class<?>>();
private static final Set<Class<?>> UNWRAPPER_TYPES = new HashSet<Class<?>>();
@@ -105,6 +111,11 @@ public abstract class QueryExecutionConverters {
UNWRAPPERS.add(ScalOptionUnwrapper.INSTANCE);
}
if (JAVASLANG_PRESENT) {
WRAPPER_TYPES.add(NullableWrapperToJavaSlangOptionConverter.getWrapperType());
UNWRAPPERS.add(JavaSlangOptionUnwrapper.INSTANCE);
}
if (ReactiveWrappers.isAvailable()) {
WRAPPER_TYPES.addAll(ReactiveWrappers.getNoValueTypes());
WRAPPER_TYPES.addAll(ReactiveWrappers.getSingleValueTypes());
@@ -174,6 +185,10 @@ public abstract class QueryExecutionConverters {
conversionService.addConverter(new NullableWrapperToScalaOptionConverter(conversionService));
}
if (JAVASLANG_PRESENT) {
conversionService.addConverter(new NullableWrapperToJavaSlangOptionConverter(conversionService));
}
if (ASYNC_RESULT_PRESENT) {
conversionService.addConverter(new NullableWrapperToFutureConverter(conversionService));
}
@@ -414,6 +429,51 @@ public abstract class QueryExecutionConverters {
}
}
/**
* Converter to convert from {@link NullableWrapper} into JavaSlang's {@link javaslang.control.Option}.
*
* @author Oliver Gierke
* @since 1.13
*/
private static class NullableWrapperToJavaSlangOptionConverter extends AbstractWrapperTypeConverter {
private static final Method OF_METHOD;
private static final Method NONE_METHOD;
static {
OF_METHOD = ReflectionUtils.findMethod(getWrapperType(), "of", Object.class);
NONE_METHOD = ReflectionUtils.findMethod(getWrapperType(), "none");
}
/**
* Creates a new {@link NullableWrapperToJavaSlangOptionConverter} using the given {@link ConversionService}.
*
* @param conversionService must not be {@literal null}.
*/
public NullableWrapperToJavaSlangOptionConverter(ConversionService conversionService) {
super(conversionService, createEmptyOption(), getWrapperType());
}
public static Class<?> getWrapperType() {
return javaslang.control.Option.class;
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.util.QueryExecutionConverters.AbstractWrapperTypeConverter#wrap(java.lang.Object)
*/
@Override
@SuppressWarnings("unchecked")
protected Object wrap(Object source) {
return (javaslang.control.Option<Object>) ReflectionUtils.invokeMethod(OF_METHOD, null, source);
}
@SuppressWarnings("unchecked")
private static javaslang.control.Option<Object> createEmptyOption() {
return (javaslang.control.Option<Object>) ReflectionUtils.invokeMethod(NONE_METHOD, null);
}
}
/**
* A {@link Converter} to unwrap Guava {@link Optional} instances.
*
@@ -486,4 +546,37 @@ public abstract class QueryExecutionConverters {
return source instanceof Option ? ((Option<?>) source).getOrElse(alternative) : source;
}
}
/**
* Converter to unwrap JavaSlang {@link javaslang.control.Option} instances.
*
* @author Oliver Gierke
* @since 1.13
*/
private static enum JavaSlangOptionUnwrapper implements Converter<Object, Object> {
INSTANCE;
private static final Supplier<Object> NULL_SUPPLIER = new Supplier<Object>() {
/*
* (non-Javadoc)
* @see java.util.function.Supplier#get()
*/
public Object get() {
return null;
}
};
/*
* (non-Javadoc)
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
*/
@Override
@SuppressWarnings("unchecked")
public Object convert(Object source) {
return source instanceof javaslang.control.Option
? ((javaslang.control.Option<Object>) source).getOrElse(NULL_SUPPLIER) : source;
}
}
}