package org.springframework.data.repository.util;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.Value;
import scala.Function0;
import scala.Option;
import scala.runtime.AbstractFunction0;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Future;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.annotation.Nonnull;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.ConditionalGenericConverter;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.GenericConverter;
import org.springframework.core.convert.support.ConfigurableConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Slice;
import org.springframework.data.geo.GeoResults;
import org.springframework.data.util.StreamUtils;
import org.springframework.data.util.Streamable;
import org.springframework.data.util.TypeInformation;
import org.springframework.lang.Nullable;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ConcurrentReferenceHashMap;
import org.springframework.util.concurrent.ListenableFuture;
import com.google.common.base.Optional;
public abstract class QueryExecutionConverters {
private static final boolean GUAVA_PRESENT = ClassUtils.isPresent("com.google.common.base.Optional",
QueryExecutionConverters.class.getClassLoader());
private static final boolean JDK_8_PRESENT = ClassUtils.isPresent("java.util.Optional",
QueryExecutionConverters.class.getClassLoader());
private static final boolean SCALA_PRESENT = ClassUtils.isPresent("scala.Option",
QueryExecutionConverters.class.getClassLoader());
private static final boolean VAVR_PRESENT = ClassUtils.isPresent("io.vavr.control.Option",
QueryExecutionConverters.class.getClassLoader());
private static final Set<WrapperType> WRAPPER_TYPES = new HashSet<WrapperType>();
private static final Set<WrapperType> UNWRAPPER_TYPES = new HashSet<WrapperType>();
private static final Set<Converter<Object, Object>> UNWRAPPERS = new HashSet<Converter<Object, Object>>();
private static final Set<Class<?>> ALLOWED_PAGEABLE_TYPES = new HashSet<Class<?>>();
private static final Map<Class<?>, ExecutionAdapter> EXECUTION_ADAPTER = new HashMap<>();
private static final Map<Class<?>, Boolean> SUPPORTS_CACHE = new ConcurrentReferenceHashMap<>();
static {
WRAPPER_TYPES.add(WrapperType.singleValue(Future.class));
UNWRAPPER_TYPES.add(WrapperType.singleValue(Future.class));
WRAPPER_TYPES.add(WrapperType.singleValue(ListenableFuture.class));
UNWRAPPER_TYPES.add(WrapperType.singleValue(ListenableFuture.class));
ALLOWED_PAGEABLE_TYPES.add(Slice.class);
ALLOWED_PAGEABLE_TYPES.add(Page.class);
ALLOWED_PAGEABLE_TYPES.add(List.class);
if (GUAVA_PRESENT) {
WRAPPER_TYPES.add(NullableWrapperToGuavaOptionalConverter.getWrapperType());
UNWRAPPER_TYPES.add(NullableWrapperToGuavaOptionalConverter.getWrapperType());
UNWRAPPERS.add(GuavaOptionalUnwrapper.INSTANCE);
}
if (JDK_8_PRESENT) {
WRAPPER_TYPES.add(NullableWrapperToJdk8OptionalConverter.getWrapperType());
UNWRAPPER_TYPES.add(NullableWrapperToJdk8OptionalConverter.getWrapperType());
UNWRAPPERS.add(Jdk8OptionalUnwrapper.INSTANCE);
}
if (JDK_8_PRESENT) {
WRAPPER_TYPES.add(NullableWrapperToCompletableFutureConverter.getWrapperType());
UNWRAPPER_TYPES.add(NullableWrapperToCompletableFutureConverter.getWrapperType());
}
if (SCALA_PRESENT) {
WRAPPER_TYPES.add(NullableWrapperToScalaOptionConverter.getWrapperType());
UNWRAPPER_TYPES.add(NullableWrapperToScalaOptionConverter.getWrapperType());
UNWRAPPERS.add(ScalOptionUnwrapper.INSTANCE);
}
if (VAVR_PRESENT) {
WRAPPER_TYPES.add(NullableWrapperToVavrOptionConverter.getWrapperType());
WRAPPER_TYPES.add(VavrCollections.ToJavaConverter.INSTANCE.getWrapperType());
UNWRAPPERS.add(VavrOptionUnwrapper.INSTANCE);
WRAPPER_TYPES.add(WrapperType.singleValue(io.vavr.control.Try.class));
EXECUTION_ADAPTER.put(io.vavr.control.Try.class, it -> io.vavr.control.Try.of(it::get));
ALLOWED_PAGEABLE_TYPES.add(io.vavr.collection.Seq.class);
}
if (ReactiveWrappers.isAvailable()) {
WRAPPER_TYPES
.addAll(ReactiveWrappers.getNoValueTypes().stream().map(WrapperType::noValue).collect(Collectors.toList()));
WRAPPER_TYPES.addAll(
ReactiveWrappers.getSingleValueTypes().stream().map(WrapperType::singleValue).collect(Collectors.toList()));
WRAPPER_TYPES.addAll(
ReactiveWrappers.getMultiValueTypes().stream().map(WrapperType::multiValue).collect(Collectors.toList()));
}
}
private QueryExecutionConverters() {}
public static boolean supports(Class<?> type) {
Assert.notNull(type, "Type must not be null!");
return SUPPORTS_CACHE.computeIfAbsent(type, key -> {
for (WrapperType candidate : WRAPPER_TYPES) {
if (candidate.getType().isAssignableFrom(key)) {
return true;
}
}
return false;
});
}
public static boolean supportsUnwrapping(Class<?> type) {
Assert.notNull(type, "Type must not be null!");
for (WrapperType candidate : UNWRAPPER_TYPES) {
if (candidate.getType().isAssignableFrom(type)) {
return true;
}
}
return false;
}
public static boolean isSingleValue(Class<?> type) {
for (WrapperType candidate : WRAPPER_TYPES) {
if (candidate.getType().isAssignableFrom(type)) {
return candidate.isSingleValue();
}
}
return false;
}
public static Set<Class<?>> getAllowedPageableTypes() {
return Collections.unmodifiableSet(ALLOWED_PAGEABLE_TYPES);
}
public static void registerConvertersIn(ConfigurableConversionService conversionService) {
Assert.notNull(conversionService, "ConversionService must not be null!");
conversionService.removeConvertible(Collection.class, Object.class);
if (GUAVA_PRESENT) {
conversionService.addConverter(new NullableWrapperToGuavaOptionalConverter(conversionService));
}
if (JDK_8_PRESENT) {
conversionService.addConverter(new NullableWrapperToJdk8OptionalConverter(conversionService));
conversionService.addConverter(new NullableWrapperToCompletableFutureConverter(conversionService));
}
if (SCALA_PRESENT) {
conversionService.addConverter(new NullableWrapperToScalaOptionConverter(conversionService));
}
if (VAVR_PRESENT) {
conversionService.addConverter(new NullableWrapperToVavrOptionConverter(conversionService));
conversionService.addConverter(VavrCollections.FromJavaConverter.INSTANCE);
}
conversionService.addConverter(new NullableWrapperToFutureConverter(conversionService));
conversionService.addConverter(new IterableToStreamableConverter());
}
@Nullable
public static Object unwrap(@Nullable Object source) {
if (source == null || !supports(source.getClass())) {
return source;
}
for (Converter<Object, Object> converter : UNWRAPPERS) {
Object result = converter.convert(source);
if (result != source) {
return result;
}
}
return source;
}
public static TypeInformation<?> unwrapWrapperTypes(TypeInformation<?> type) {
Assert.notNull(type, "type must not be null");
Class<?> rawType = type.getType();
boolean needToUnwrap = type.isCollectionLike()
|| Slice.class.isAssignableFrom(rawType)
|| GeoResults.class.isAssignableFrom(rawType)
|| rawType.isArray()
|| supports(rawType)
|| Stream.class.isAssignableFrom(rawType);
return needToUnwrap ? unwrapWrapperTypes(type.getRequiredComponentType()) : type;
}
@Nullable
public static ExecutionAdapter getExecutionAdapter(Class<?> returnType) {
Assert.notNull(returnType, "Return type must not be null!");
return EXECUTION_ADAPTER.get(returnType);
}
public interface ThrowingSupplier {
Object get() throws Throwable;
}
public interface ExecutionAdapter {
Object apply(ThrowingSupplier supplier) throws Throwable;
}
@RequiredArgsConstructor
private static abstract class AbstractWrapperTypeConverter implements GenericConverter {
private final @NonNull ConversionService conversionService;
private final @NonNull Object nullValue;
private final @NonNull Iterable<Class<?>> wrapperTypes;
protected AbstractWrapperTypeConverter(ConversionService conversionService, Object nullValue) {
Assert.notNull(conversionService, "ConversionService must not be null!");
Assert.notNull(nullValue, "Null value must not be null!");
this.conversionService = conversionService;
this.nullValue = nullValue;
this.wrapperTypes = Collections.singleton(nullValue.getClass());
}
@Nonnull
@Override
public Set<ConvertiblePair> getConvertibleTypes() {
return Streamable.of(wrapperTypes)
.map(it -> new ConvertiblePair(NullableWrapper.class, it))
.stream().collect(StreamUtils.toUnmodifiableSet());
}
@Nullable
@Override
public final Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
if (source == null) {
return null;
}
NullableWrapper wrapper = (NullableWrapper) source;
Object value = wrapper.getValue();
return value == null ? nullValue : wrap(value);
}
protected abstract Object wrap(Object source);
}
private static class NullableWrapperToGuavaOptionalConverter extends AbstractWrapperTypeConverter {
public NullableWrapperToGuavaOptionalConverter(ConversionService conversionService) {
super(conversionService, Optional.absent(), Collections.singleton(Optional.class));
}
@Override
protected Object wrap(Object source) {
return Optional.of(source);
}
public static WrapperType getWrapperType() {
return WrapperType.singleValue(Optional.class);
}
}
private static class NullableWrapperToJdk8OptionalConverter extends AbstractWrapperTypeConverter {
public NullableWrapperToJdk8OptionalConverter(ConversionService conversionService) {
super(conversionService, java.util.Optional.empty());
}
@Override
protected Object wrap(Object source) {
return java.util.Optional.of(source);
}
public static WrapperType getWrapperType() {
return WrapperType.singleValue(java.util.Optional.class);
}
}
private static class NullableWrapperToFutureConverter extends AbstractWrapperTypeConverter {
public NullableWrapperToFutureConverter(ConversionService conversionService) {
super(conversionService, new AsyncResult<>(null), Arrays.asList(Future.class, ListenableFuture.class));
}
@Override
protected Object wrap(Object source) {
return new AsyncResult<>(source);
}
}
private static class NullableWrapperToCompletableFutureConverter extends AbstractWrapperTypeConverter {
public NullableWrapperToCompletableFutureConverter(ConversionService conversionService) {
super(conversionService, CompletableFuture.completedFuture(null));
}
@Override
protected Object wrap(Object source) {
return source instanceof CompletableFuture ? source : CompletableFuture.completedFuture(source);
}
public static WrapperType getWrapperType() {
return WrapperType.singleValue(CompletableFuture.class);
}
}
private static class NullableWrapperToScalaOptionConverter extends AbstractWrapperTypeConverter {
public NullableWrapperToScalaOptionConverter(ConversionService conversionService) {
super(conversionService, Option.empty(), Collections.singleton(Option.class));
}
@Override
protected Object wrap(Object source) {
return Option.apply(source);
}
public static WrapperType getWrapperType() {
return WrapperType.singleValue(Option.class);
}
}
private static class NullableWrapperToVavrOptionConverter extends AbstractWrapperTypeConverter {
public NullableWrapperToVavrOptionConverter(ConversionService conversionService) {
super(conversionService, io.vavr.control.Option.none(), Collections.singleton(io.vavr.control.Option.class));
}
public static WrapperType getWrapperType() {
return WrapperType.singleValue(io.vavr.control.Option.class);
}
@Override
protected Object wrap(Object source) {
return io.vavr.control.Option.of(source);
}
}
private enum GuavaOptionalUnwrapper implements Converter<Object, Object> {
INSTANCE;
@Nullable
@Override
public Object convert(Object source) {
return source instanceof Optional ? ((Optional<?>) source).orNull() : source;
}
}
private enum Jdk8OptionalUnwrapper implements Converter<Object, Object> {
INSTANCE;
@Nullable
@Override
public Object convert(Object source) {
return source instanceof java.util.Optional ? ((java.util.Optional<?>) source).orElse(null) : source;
}
}
private enum ScalOptionUnwrapper implements Converter<Object, Object> {
INSTANCE;
private final Function0<Object> alternative = new AbstractFunction0<Object>() {
@Nullable
@Override
public Option<Object> apply() {
return null;
}
};
@Nullable
@Override
public Object convert(Object source) {
return source instanceof Option ? ((Option<?>) source).getOrElse(alternative) : source;
}
}
private enum VavrOptionUnwrapper implements Converter<Object, Object> {
INSTANCE;
@Nullable
@Override
@SuppressWarnings("unchecked")
public Object convert(Object source) {
if (source instanceof io.vavr.control.Option) {
return ((io.vavr.control.Option<Object>) source).getOrElse(() -> null);
}
if (source instanceof io.vavr.collection.Traversable) {
return VavrCollections.ToJavaConverter.INSTANCE.convert(source);
}
return source;
}
}
@RequiredArgsConstructor
private static class IterableToStreamableConverter implements ConditionalGenericConverter {
private static final TypeDescriptor STREAMABLE = TypeDescriptor.valueOf(Streamable.class);
private final Map<TypeDescriptor, Boolean> TARGET_TYPE_CACHE = new ConcurrentHashMap<>();
private final ConversionService conversionService = DefaultConversionService.getSharedInstance();
@org.springframework.lang.NonNull
@Override
public Set<ConvertiblePair> getConvertibleTypes() {
return Collections.singleton(new ConvertiblePair(Iterable.class, Object.class));
}
@Override
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
if (sourceType.isAssignableTo(targetType)) {
return false;
}
if (!Iterable.class.isAssignableFrom(sourceType.getType())) {
return false;
}
if (Streamable.class.equals(targetType.getType())) {
return true;
}
return TARGET_TYPE_CACHE.computeIfAbsent(targetType, it -> {
return conversionService.canConvert(STREAMABLE, targetType);
});
}
@SuppressWarnings("unchecked")
@Nullable
@Override
public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
Streamable<Object> streamable = source == null
? Streamable.empty()
: Streamable.of(Iterable.class.cast(source));
return Streamable.class.equals(targetType.getType())
? streamable
: conversionService.convert(streamable, STREAMABLE, targetType);
}
}
@Value
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public static class WrapperType {
enum Cardinality {
NONE, SINGLE, MULTI;
}
Class<?> type;
@Getter(AccessLevel.NONE) Cardinality cardinality;
public static WrapperType singleValue(Class<?> type) {
return new WrapperType(type, Cardinality.SINGLE);
}
public static WrapperType multiValue(Class<?> type) {
return new WrapperType(type, Cardinality.MULTI);
}
public static WrapperType noValue(Class<?> type) {
return new WrapperType(type, Cardinality.NONE);
}
boolean isSingleValue() {
return cardinality.equals(Cardinality.SINGLE);
}
}
}