Improve custom collection support.
Custom collection support is now centralized in ….util.CustomCollections. It exposes API to detect whether a type is a map or collection, identifies the map base type etc. Support for different collection implementations is externalized via the CustomCollectionRegistrar SPI that allows to define implementations via spring.factories. The current support for Vavr collections has been moved into an implementation of that, VavrCollections. Unit tests for custom collection handling and conversion previously living in QueryExecutionConverterUnitTests have been moved into CustomCollectionsUnitTests. Fixes #2619.
This commit is contained in:
@@ -16,16 +16,7 @@
|
||||
package org.springframework.data.convert;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
@@ -33,7 +24,6 @@ import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.core.GenericTypeResolver;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
@@ -45,9 +35,9 @@ import org.springframework.core.convert.support.GenericConversionService;
|
||||
import org.springframework.data.convert.ConverterBuilder.ConverterAware;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.mapping.model.SimpleTypeHolder;
|
||||
import org.springframework.data.util.CustomCollections;
|
||||
import org.springframework.data.util.Predicates;
|
||||
import org.springframework.data.util.Streamable;
|
||||
import org.springframework.data.util.VavrCollectionConverters;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -214,7 +204,7 @@ public class CustomConversions {
|
||||
Assert.notNull(conversionService, "ConversionService must not be null");
|
||||
|
||||
converters.forEach(it -> registerConverterIn(it, conversionService));
|
||||
VavrCollectionConverters.getConvertersToRegister().forEach(it -> registerConverterIn(it, conversionService));
|
||||
CustomCollections.registerConvertersIn(conversionService);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -876,7 +866,7 @@ public class CustomConversions {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
public boolean equals(@Nullable Object o) {
|
||||
|
||||
if (this == o) {
|
||||
return true;
|
||||
|
||||
@@ -26,6 +26,7 @@ import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
@@ -38,12 +39,13 @@ 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.CustomCollections;
|
||||
import org.springframework.data.util.NullableWrapper;
|
||||
import org.springframework.data.util.NullableWrapperConverters;
|
||||
import org.springframework.data.util.StreamUtils;
|
||||
import org.springframework.data.util.Streamable;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.data.util.VavrCollectionConverters;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.scheduling.annotation.AsyncResult;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -80,7 +82,7 @@ public abstract class QueryExecutionConverters {
|
||||
|
||||
private static final Set<WrapperType> WRAPPER_TYPES = new HashSet<>();
|
||||
private static final Set<WrapperType> UNWRAPPER_TYPES = new HashSet<WrapperType>();
|
||||
private static final Set<Converter<Object, Object>> UNWRAPPERS = new HashSet<>();
|
||||
private static final Set<Function<Object, Object>> UNWRAPPERS = new HashSet<>();
|
||||
private static final Set<Class<?>> ALLOWED_PAGEABLE_TYPES = new HashSet<>();
|
||||
private static final Map<Class<?>, ExecutionAdapter> EXECUTION_ADAPTER = new HashMap<>();
|
||||
private static final Map<Class<?>, Boolean> supportsCache = new ConcurrentReferenceHashMap<>();
|
||||
@@ -98,16 +100,19 @@ public abstract class QueryExecutionConverters {
|
||||
|
||||
WRAPPER_TYPES.add(NullableWrapperToCompletableFutureConverter.getWrapperType());
|
||||
|
||||
if (VAVR_PRESENT) {
|
||||
UNWRAPPERS.addAll(CustomCollections.getUnwrappers());
|
||||
|
||||
WRAPPER_TYPES.add(VavrTraversableUnwrapper.INSTANCE.getWrapperType());
|
||||
UNWRAPPERS.add(VavrTraversableUnwrapper.INSTANCE);
|
||||
CustomCollections.getCustomTypes().stream()
|
||||
.map(WrapperType::multiValue)
|
||||
.forEach(WRAPPER_TYPES::add);
|
||||
|
||||
CustomCollections.getPaginationReturnTypes().forEach(ALLOWED_PAGEABLE_TYPES::add);
|
||||
|
||||
if (VAVR_PRESENT) {
|
||||
|
||||
// Try support
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,10 +200,7 @@ public abstract class QueryExecutionConverters {
|
||||
conversionService.removeConvertible(Collection.class, Object.class);
|
||||
|
||||
NullableWrapperConverters.registerConvertersIn(conversionService);
|
||||
|
||||
if (VAVR_PRESENT) {
|
||||
conversionService.addConverter(VavrCollectionConverters.FromJavaConverter.INSTANCE);
|
||||
}
|
||||
CustomCollections.registerConvertersIn(conversionService);
|
||||
|
||||
conversionService.addConverter(new NullableWrapperToCompletableFutureConverter());
|
||||
conversionService.addConverter(new NullableWrapperToFutureConverter());
|
||||
@@ -220,9 +222,9 @@ public abstract class QueryExecutionConverters {
|
||||
return source;
|
||||
}
|
||||
|
||||
for (Converter<Object, Object> converter : UNWRAPPERS) {
|
||||
for (Function<Object, Object> converter : UNWRAPPERS) {
|
||||
|
||||
Object result = converter.convert(source);
|
||||
Object result = converter.apply(source);
|
||||
|
||||
if (result != source) {
|
||||
return result;
|
||||
@@ -382,36 +384,6 @@ public abstract class QueryExecutionConverters {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converter to unwrap Vavr {@link io.vavr.collection.Traversable} instances.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @since 2.0
|
||||
*/
|
||||
private enum VavrTraversableUnwrapper implements Converter<Object, Object> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
private static final TypeDescriptor OBJECT_DESCRIPTOR = TypeDescriptor.valueOf(Object.class);
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
@SuppressWarnings("null")
|
||||
public Object convert(Object source) {
|
||||
|
||||
if (source instanceof io.vavr.collection.Traversable) {
|
||||
return VavrCollectionConverters.ToJavaConverter.INSTANCE //
|
||||
.convert(source, TypeDescriptor.forObject(source), OBJECT_DESCRIPTOR);
|
||||
}
|
||||
|
||||
return source;
|
||||
}
|
||||
|
||||
public WrapperType getWrapperType() {
|
||||
return WrapperType.multiValue(io.vavr.collection.Traversable.class);
|
||||
}
|
||||
}
|
||||
|
||||
private static class IterableToStreamableConverter implements ConditionalGenericConverter {
|
||||
|
||||
private static final TypeDescriptor STREAMABLE = TypeDescriptor.valueOf(Streamable.class);
|
||||
@@ -477,7 +449,7 @@ public abstract class QueryExecutionConverters {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
public boolean equals(@Nullable Object o) {
|
||||
|
||||
if (this == o) {
|
||||
return true;
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright 2022 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
|
||||
*
|
||||
* https://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.data.util;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.core.convert.converter.ConverterRegistry;
|
||||
|
||||
/**
|
||||
* An SPI to register custom collection types. Implementations need to be registered via
|
||||
* {@code META-INF/spring.factories}.
|
||||
*
|
||||
* @author Oliver Drotbohm
|
||||
* @since 2.7
|
||||
*/
|
||||
public interface CustomCollectionRegistrar {
|
||||
|
||||
/**
|
||||
* Returns whether the registrar is available, meaning whether it can be used at runtime. Primary use is for
|
||||
* implementations that need to perform a classpath check to prevent the actual methods loading classes that might not
|
||||
* be available.
|
||||
*
|
||||
* @return whether the registrar is available
|
||||
*/
|
||||
default boolean isAvailable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all types that are supposed to be considered maps. Primary requirement is key and value generics expressed
|
||||
* in the first and second generics parameter of the type. Also, the types should be transformable into their
|
||||
* Java-native equivalent using {@link #toJavaNativeCollection()}.
|
||||
*
|
||||
* @return will never be {@literal null}.
|
||||
* @see #toJavaNativeCollection()
|
||||
*/
|
||||
Collection<Class<?>> getMapTypes();
|
||||
|
||||
/**
|
||||
* Returns all types that are supposed to be considered collections. Primary requirement is that their component types
|
||||
* are expressed as first generics parameter. Also, the types should be transformable into their Java-native
|
||||
* equivalent using {@link #toJavaNativeCollection()}.
|
||||
*
|
||||
* @return will never be {@literal null}.
|
||||
* @see #toJavaNativeCollection()
|
||||
*/
|
||||
Collection<Class<?>> getCollectionTypes();
|
||||
|
||||
/**
|
||||
* Return all types that are considered valid return types for methods using pagination. These are usually collections
|
||||
* with a stable order, like {@link List} but no {@link Set}s, as pagination usually involves sorting.
|
||||
*
|
||||
* @return will never be {@literal null}.
|
||||
*/
|
||||
default Collection<Class<?>> getAllowedPaginationReturnTypes() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register all converters to convert instances of the types returned by {@link #getCollectionTypes()} and
|
||||
* {@link #getMapTypes()} from an to their Java-native counterparts.
|
||||
*
|
||||
* @param registry will never be {@literal null}.
|
||||
*/
|
||||
void registerConvertersIn(ConverterRegistry registry);
|
||||
|
||||
/**
|
||||
* Returns a {@link Function} to convert instances of their Java-native counterpart.
|
||||
*
|
||||
* @return must not be {@literal null}.
|
||||
*/
|
||||
Function<Object, Object> toJavaNativeCollection();
|
||||
}
|
||||
@@ -0,0 +1,489 @@
|
||||
/*
|
||||
* Copyright 2022 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
|
||||
*
|
||||
* https://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.data.util;
|
||||
|
||||
import io.vavr.collection.HashMap;
|
||||
import io.vavr.collection.LinkedHashMap;
|
||||
import io.vavr.collection.LinkedHashSet;
|
||||
import io.vavr.collection.Seq;
|
||||
import io.vavr.collection.Traversable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiPredicate;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.core.convert.converter.ConditionalGenericConverter;
|
||||
import org.springframework.core.convert.converter.ConverterRegistry;
|
||||
import org.springframework.core.io.support.SpringFactoriesLoader;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* Central API to expose information about custom collections present for Spring Data. Exposes custom collection and map
|
||||
* types and registers converters to convert them from and to Java-native collections.
|
||||
*
|
||||
* @author Oliver Drotbohm
|
||||
* @since 2.7
|
||||
* @soundtrack Black Sea Dahu - White Creatures (White Creatures)
|
||||
*/
|
||||
public class CustomCollections {
|
||||
|
||||
private static final Set<Class<?>> CUSTOM_TYPES, CUSTOM_MAP_TYPES, CUSTOM_COLLECTION_TYPES, PAGINATION_RETURN_TYPES;
|
||||
private static final Set<Class<?>> COLLECTIONS_AND_MAP = Set.of(Collection.class, List.class, Set.class, Map.class);
|
||||
private static final SearchableTypes MAP_TYPES, COLLECTION_TYPES;
|
||||
private static final Collection<CustomCollectionRegistrar> REGISTRARS;
|
||||
|
||||
static {
|
||||
|
||||
CUSTOM_TYPES = new HashSet<>();
|
||||
PAGINATION_RETURN_TYPES = new HashSet<>();
|
||||
CUSTOM_MAP_TYPES = new HashSet<>();
|
||||
CUSTOM_COLLECTION_TYPES = new HashSet<>();
|
||||
|
||||
REGISTRARS = SpringFactoriesLoader
|
||||
.loadFactories(CustomCollectionRegistrar.class, CustomCollections.class.getClassLoader())
|
||||
.stream()
|
||||
.filter(CustomCollectionRegistrar::isAvailable)
|
||||
.toList();
|
||||
|
||||
REGISTRARS.forEach(it -> {
|
||||
|
||||
it.getCollectionTypes().forEach(CustomCollections::registerCollectionType);
|
||||
it.getMapTypes().forEach(CustomCollections::registerMapType);
|
||||
it.getAllowedPaginationReturnTypes().forEach(PAGINATION_RETURN_TYPES::add);
|
||||
});
|
||||
|
||||
MAP_TYPES = new SearchableTypes(CUSTOM_MAP_TYPES, Map.class);
|
||||
COLLECTION_TYPES = new SearchableTypes(CUSTOM_COLLECTION_TYPES, Collection.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all custom collection and map types.
|
||||
*
|
||||
* @return will never be {@literal null}.
|
||||
*/
|
||||
public static Set<Class<?>> getCustomTypes() {
|
||||
return CUSTOM_TYPES;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all types that are allowed pagination return types.
|
||||
*
|
||||
* @return will never be {@literal null}.
|
||||
*/
|
||||
public static Set<Class<?>> getPaginationReturnTypes() {
|
||||
return PAGINATION_RETURN_TYPES;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given type is a map base type.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @return will never be {@literal null}.
|
||||
*/
|
||||
public static boolean isMapBaseType(Class<?> type) {
|
||||
|
||||
Assert.notNull(type, "Type must not be null!");
|
||||
|
||||
return MAP_TYPES.has(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the map base type for the given type, i.e. the one that's considered the logical map interface ({@link Map}
|
||||
* for {@link HashMap} etc.).
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @return will never be {@literal null}.
|
||||
* @throws IllegalArgumentException in case we do not find a map base type for the given one.
|
||||
*/
|
||||
public static Class<?> getMapBaseType(Class<?> type) throws IllegalArgumentException {
|
||||
return MAP_TYPES.getSuperType(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given type is considered a {@link Map} type.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @return will never be {@literal null}.
|
||||
*/
|
||||
public static boolean isMap(Class<?> type) {
|
||||
return MAP_TYPES.hasSuperTypeFor(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given type is considered a {@link Collection} type.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @return will never be {@literal null}.
|
||||
*/
|
||||
public static boolean isCollection(Class<?> type) {
|
||||
return COLLECTION_TYPES.hasSuperTypeFor(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all unwrapper functions that transform the custom collections into Java-native ones.
|
||||
*
|
||||
* @return will never be {@literal null}.
|
||||
*/
|
||||
public static Set<Function<Object, Object>> getUnwrappers() {
|
||||
|
||||
return REGISTRARS.stream()
|
||||
.map(CustomCollectionRegistrar::toJavaNativeCollection)
|
||||
.collect(Collectors.toUnmodifiableSet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers all converters to transform Java-native collections into custom ones and back in the given
|
||||
* {@link ConverterRegistry}.
|
||||
*
|
||||
* @param registry must not be {@literal null}.
|
||||
*/
|
||||
public static void registerConvertersIn(ConverterRegistry registry) {
|
||||
|
||||
Assert.notNull(registry, "ConverterRegistry must not be null!");
|
||||
|
||||
// Remove general collection to anything conversion as that would also convert collections to maps
|
||||
registry.removeConvertible(Collection.class, Object.class);
|
||||
|
||||
REGISTRARS.forEach(it -> it.registerConvertersIn(registry));
|
||||
}
|
||||
|
||||
private static void registerCollectionType(Class<?> type) {
|
||||
|
||||
CUSTOM_TYPES.add(type);
|
||||
CUSTOM_COLLECTION_TYPES.add(type);
|
||||
}
|
||||
|
||||
private static void registerMapType(Class<?> type) {
|
||||
|
||||
CUSTOM_TYPES.add(type);
|
||||
CUSTOM_MAP_TYPES.add(type);
|
||||
}
|
||||
|
||||
private static class SearchableTypes {
|
||||
|
||||
private static final BiPredicate<Class<?>, Class<?>> EQUALS = (left, right) -> left.equals(right);
|
||||
private static final BiPredicate<Class<?>, Class<?>> IS_ASSIGNABLE = (left, right) -> left.isAssignableFrom(right);
|
||||
private static final Function<Class<?>, Boolean> IS_NOT_NULL = it -> it != null;
|
||||
|
||||
private final Collection<Class<?>> types;
|
||||
|
||||
public SearchableTypes(Set<Class<?>> types, Class<?>... additional) {
|
||||
|
||||
var all = new ArrayList<>(List.of(additional));
|
||||
all.addAll(types);
|
||||
|
||||
this.types = all;
|
||||
}
|
||||
|
||||
public boolean hasSuperTypeFor(Class<?> type) {
|
||||
|
||||
Assert.notNull(type, "Type must not be null!");
|
||||
|
||||
return isOneOf(type, IS_ASSIGNABLE, IS_NOT_NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the current's raw type is one of the given ones.
|
||||
*
|
||||
* @param candidates must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public boolean has(Class<?> type) {
|
||||
|
||||
Assert.notNull(type, "Type must not be null!");
|
||||
|
||||
return isOneOf(type, EQUALS, IS_NOT_NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the super type of the given one from the set of types.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @return will never be {@literal null}.
|
||||
* @throws IllegalArgumentException in case no base type of the given one can be found.
|
||||
*/
|
||||
public Class<?> getSuperType(Class<?> type) {
|
||||
|
||||
Assert.notNull(type, "Type must not be null!");
|
||||
|
||||
Supplier<String> message = () -> String.format("Type %s not contained in candidates %s!", type, types);
|
||||
|
||||
return isOneOf(type, (l, r) -> l.isAssignableFrom(r), rejectNull(message));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given type matches one of the given candidates given the matcher with the
|
||||
*
|
||||
* @param <T>
|
||||
* @param type the type to match against the current candidates.
|
||||
* @param matcher how to match the candidates against the given type.
|
||||
* @param resultMapper a {@link Function} to map the potentially given type to the actual result.
|
||||
* @return will never be {@literal null}.
|
||||
*/
|
||||
private <T> T isOneOf(Class<?> type, BiPredicate<Class<?>, Class<?>> matcher, Function<Class<?>, T> resultMapper) {
|
||||
|
||||
for (var candidate : types) {
|
||||
if (matcher.test(candidate, type)) {
|
||||
return resultMapper.apply(candidate);
|
||||
}
|
||||
}
|
||||
|
||||
return resultMapper.apply(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a function that rejects the source {@link Class} resolving the given message if the former is
|
||||
* {@literal null}.
|
||||
*
|
||||
* @param message must not be {@literal null}.
|
||||
* @return will never be {@literal null}.
|
||||
*/
|
||||
private static Function<Class<?>, Class<?>> rejectNull(Supplier<String> message) {
|
||||
|
||||
Assert.notNull(message, "Message must not be null!");
|
||||
|
||||
return candidate -> {
|
||||
|
||||
if (candidate == null) {
|
||||
throw new IllegalArgumentException(message.get());
|
||||
}
|
||||
|
||||
return candidate;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
static class VavrCollections implements CustomCollectionRegistrar {
|
||||
|
||||
private static final TypeDescriptor OBJECT_DESCRIPTOR = TypeDescriptor.valueOf(Object.class);
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.util.CustomCollectionRegistrar#isAvailable()
|
||||
*/
|
||||
@Override
|
||||
public boolean isAvailable() {
|
||||
return ClassUtils.isPresent("io.vavr.control.Option", VavrCollections.class.getClassLoader());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.util.CustomCollectionRegistrar#getMapTypes()
|
||||
*/
|
||||
@Override
|
||||
public Collection<Class<?>> getMapTypes() {
|
||||
return Set.of(io.vavr.collection.Map.class);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.util.CustomCollectionRegistrar#getCollectionTypes()
|
||||
*/
|
||||
@Override
|
||||
public Collection<Class<?>> getCollectionTypes() {
|
||||
return List.of(Seq.class, io.vavr.collection.Set.class);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.util.CustomCollectionRegistrar#getAllowedPaginationReturnTypes()
|
||||
*/
|
||||
@Override
|
||||
public Collection<Class<?>> getAllowedPaginationReturnTypes() {
|
||||
return Set.of(Seq.class);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.util.CustomCollectionRegistrar#registerConvertersIn(org.springframework.core.convert.converter.ConverterRegistry)
|
||||
*/
|
||||
@Override
|
||||
public void registerConvertersIn(ConverterRegistry registry) {
|
||||
|
||||
registry.addConverter(JavaToVavrCollectionConverter.INSTANCE);
|
||||
registry.addConverter(VavrToJavaCollectionConverter.INSTANCE);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.util.CustomCollectionRegistrar#toJavaNativeCollection()
|
||||
*/
|
||||
@Override
|
||||
public Function<Object, Object> toJavaNativeCollection() {
|
||||
|
||||
return source -> source instanceof io.vavr.collection.Traversable
|
||||
? VavrToJavaCollectionConverter.INSTANCE.convert(source, TypeDescriptor.forObject(source), OBJECT_DESCRIPTOR)
|
||||
: source;
|
||||
}
|
||||
|
||||
private enum VavrToJavaCollectionConverter implements ConditionalGenericConverter {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
private static final TypeDescriptor TRAVERSAL_TYPE = TypeDescriptor.valueOf(Traversable.class);
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.core.convert.converter.GenericConverter#getConvertibleTypes()
|
||||
*/
|
||||
@NonNull
|
||||
@Override
|
||||
public Set<ConvertiblePair> getConvertibleTypes() {
|
||||
|
||||
return COLLECTIONS_AND_MAP.stream()
|
||||
.map(it -> new ConvertiblePair(Traversable.class, it))
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.core.convert.converter.ConditionalConverter#matches(org.springframework.core.convert.TypeDescriptor, org.springframework.core.convert.TypeDescriptor)
|
||||
*/
|
||||
@Override
|
||||
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
|
||||
|
||||
return sourceType.isAssignableTo(TRAVERSAL_TYPE)
|
||||
&& COLLECTIONS_AND_MAP.contains(targetType.getType());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.core.convert.converter.GenericConverter#convert(java.lang.Object, org.springframework.core.convert.TypeDescriptor, org.springframework.core.convert.TypeDescriptor)
|
||||
*/
|
||||
@Nullable
|
||||
@Override
|
||||
public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
|
||||
|
||||
if (source == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (source instanceof io.vavr.collection.Seq) {
|
||||
return ((io.vavr.collection.Seq<?>) source).asJava();
|
||||
}
|
||||
|
||||
if (source instanceof io.vavr.collection.Map) {
|
||||
return ((io.vavr.collection.Map<?, ?>) source).toJavaMap();
|
||||
}
|
||||
|
||||
if (source instanceof io.vavr.collection.Set) {
|
||||
return ((io.vavr.collection.Set<?>) source).toJavaSet();
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Unsupported Vavr collection " + source.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
private enum JavaToVavrCollectionConverter implements ConditionalGenericConverter {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
private static final Set<ConvertiblePair> CONVERTIBLE_PAIRS;
|
||||
|
||||
static {
|
||||
|
||||
Set<ConvertiblePair> pairs = new HashSet<>();
|
||||
pairs.add(new ConvertiblePair(Collection.class, io.vavr.collection.Traversable.class));
|
||||
pairs.add(new ConvertiblePair(Map.class, io.vavr.collection.Traversable.class));
|
||||
|
||||
CONVERTIBLE_PAIRS = Collections.unmodifiableSet(pairs);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.core.convert.converter.GenericConverter#getConvertibleTypes()
|
||||
*/
|
||||
@NonNull
|
||||
@Override
|
||||
public java.util.Set<ConvertiblePair> getConvertibleTypes() {
|
||||
return CONVERTIBLE_PAIRS;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.core.convert.converter.ConditionalConverter#matches(org.springframework.core.convert.TypeDescriptor, org.springframework.core.convert.TypeDescriptor)
|
||||
*/
|
||||
@Override
|
||||
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
|
||||
|
||||
// Prevent collections to be mapped to maps
|
||||
if (sourceType.isCollection() && io.vavr.collection.Map.class.isAssignableFrom(targetType.getType())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Prevent maps to be mapped to collections
|
||||
if (sourceType.isMap() && !(io.vavr.collection.Map.class.isAssignableFrom(targetType.getType())
|
||||
|| targetType.getType().equals(Traversable.class))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.core.convert.converter.GenericConverter#convert(java.lang.Object, org.springframework.core.convert.TypeDescriptor, org.springframework.core.convert.TypeDescriptor)
|
||||
*/
|
||||
@Nullable
|
||||
@Override
|
||||
public Object convert(@Nullable Object source, TypeDescriptor sourceDescriptor, TypeDescriptor targetDescriptor) {
|
||||
|
||||
var targetType = targetDescriptor.getType();
|
||||
|
||||
if (io.vavr.collection.Seq.class.isAssignableFrom(targetType)) {
|
||||
return io.vavr.collection.List.ofAll((Iterable<?>) source);
|
||||
}
|
||||
|
||||
if (io.vavr.collection.Set.class.isAssignableFrom(targetType)) {
|
||||
return LinkedHashSet.ofAll((Iterable<?>) source);
|
||||
}
|
||||
|
||||
if (io.vavr.collection.Map.class.isAssignableFrom(targetType)) {
|
||||
return LinkedHashMap.ofAll((Map<?, ?>) source);
|
||||
}
|
||||
|
||||
// No dedicated type asked for, probably Traversable.
|
||||
// Try to stay as close to the source value.
|
||||
|
||||
if (source instanceof List) {
|
||||
return io.vavr.collection.List.ofAll((Iterable<?>) source);
|
||||
}
|
||||
|
||||
if (source instanceof Set) {
|
||||
return LinkedHashSet.ofAll((Iterable<?>) source);
|
||||
}
|
||||
|
||||
if (source instanceof Map) {
|
||||
return LinkedHashMap.ofAll((Map<?, ?>) source);
|
||||
}
|
||||
|
||||
return source;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,6 @@ import java.lang.reflect.Type;
|
||||
import java.lang.reflect.TypeVariable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
@@ -122,7 +121,8 @@ class ParameterizedTypeInformation<T> extends ParentTypeAwareTypeInformation<T>
|
||||
: target.getSuperTypeInformation(rawType);
|
||||
|
||||
List<TypeInformation<?>> myParameters = getTypeArguments();
|
||||
List<TypeInformation<?>> typeParameters = otherTypeInformation == null ? Collections.emptyList()
|
||||
List<TypeInformation<?>> typeParameters = otherTypeInformation == null //
|
||||
? java.util.Collections.emptyList() //
|
||||
: otherTypeInformation.getTypeArguments();
|
||||
|
||||
if (myParameters.size() != typeParameters.size()) {
|
||||
@@ -142,8 +142,10 @@ class ParameterizedTypeInformation<T> extends ParentTypeAwareTypeInformation<T>
|
||||
@Nullable
|
||||
protected TypeInformation<?> doGetComponentType() {
|
||||
|
||||
return isMap() && !isMapBaseType()
|
||||
? getRequiredSuperTypeInformation(getMapBaseType()).getComponentType()
|
||||
Class<?> type = getType();
|
||||
|
||||
return isMap() && !CustomCollections.isMapBaseType(type)
|
||||
? getRequiredSuperTypeInformation(CustomCollections.getMapBaseType(type)).getComponentType()
|
||||
: createInfo(this.type.getActualTypeArguments()[0]);
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,12 @@ import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.lang.reflect.TypeVariable;
|
||||
import java.lang.reflect.WildcardType;
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -35,7 +40,6 @@ import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
@@ -50,36 +54,6 @@ import org.springframework.util.ReflectionUtils;
|
||||
*/
|
||||
class TypeDiscoverer<S> implements TypeInformation<S> {
|
||||
|
||||
protected static final Class<?>[] MAP_TYPES;
|
||||
private static final Class<?>[] COLLECTION_TYPES;
|
||||
|
||||
static {
|
||||
|
||||
ClassLoader classLoader = TypeDiscoverer.class.getClassLoader();
|
||||
|
||||
Set<Class<?>> mapTypes = new HashSet<>();
|
||||
mapTypes.add(Map.class);
|
||||
|
||||
try {
|
||||
mapTypes.add(ClassUtils.forName("io.vavr.collection.Map", classLoader));
|
||||
} catch (ClassNotFoundException o_O) {}
|
||||
|
||||
MAP_TYPES = mapTypes.toArray(new Class[0]);
|
||||
|
||||
Set<Class<?>> collectionTypes = new HashSet<>();
|
||||
collectionTypes.add(Collection.class);
|
||||
|
||||
try {
|
||||
collectionTypes.add(ClassUtils.forName("io.vavr.collection.Seq", classLoader));
|
||||
} catch (ClassNotFoundException o_O) {}
|
||||
|
||||
try {
|
||||
collectionTypes.add(ClassUtils.forName("io.vavr.collection.Set", classLoader));
|
||||
} catch (ClassNotFoundException o_O) {}
|
||||
|
||||
COLLECTION_TYPES = collectionTypes.toArray(new Class[0]);
|
||||
}
|
||||
|
||||
private final Type type;
|
||||
private final Map<TypeVariable<?>, Type> typeVariableMap;
|
||||
private final Map<String, Optional<TypeInformation<?>>> fieldTypes = new ConcurrentHashMap<>();
|
||||
@@ -320,16 +294,7 @@ class TypeDiscoverer<S> implements TypeInformation<S> {
|
||||
}
|
||||
|
||||
public boolean isMap() {
|
||||
|
||||
Class<S> type = getType();
|
||||
|
||||
for (Class<?> mapType : MAP_TYPES) {
|
||||
if (mapType.isAssignableFrom(type)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return CustomCollections.isMap(getType());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -339,7 +304,9 @@ class TypeDiscoverer<S> implements TypeInformation<S> {
|
||||
|
||||
@Nullable
|
||||
protected TypeInformation<?> doGetMapValueType() {
|
||||
return isMap() ? getTypeArgument(getMapBaseType(), 1)
|
||||
|
||||
return isMap() //
|
||||
? getTypeArgument(CustomCollections.getMapBaseType(getType()), 1)
|
||||
: getTypeArguments().stream().skip(1).findFirst().orElse(null);
|
||||
}
|
||||
|
||||
@@ -350,7 +317,7 @@ class TypeDiscoverer<S> implements TypeInformation<S> {
|
||||
return rawType.isArray() //
|
||||
|| Iterable.class.equals(rawType) //
|
||||
|| Streamable.class.isAssignableFrom(rawType) //
|
||||
|| isCollection();
|
||||
|| CustomCollections.isCollection(rawType);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -368,7 +335,7 @@ class TypeDiscoverer<S> implements TypeInformation<S> {
|
||||
}
|
||||
|
||||
if (isMap()) {
|
||||
return getTypeArgument(getMapBaseType(), 0);
|
||||
return getTypeArgument(CustomCollections.getMapBaseType(rawType), 0);
|
||||
}
|
||||
|
||||
if (Iterable.class.isAssignableFrom(rawType)) {
|
||||
@@ -441,7 +408,7 @@ class TypeDiscoverer<S> implements TypeInformation<S> {
|
||||
}
|
||||
|
||||
public List<TypeInformation<?>> getTypeArguments() {
|
||||
return Collections.emptyList();
|
||||
return java.util.Collections.emptyList();
|
||||
}
|
||||
|
||||
public boolean isAssignableFrom(TypeInformation<?> target) {
|
||||
@@ -480,14 +447,6 @@ class TypeDiscoverer<S> implements TypeInformation<S> {
|
||||
: null;
|
||||
}
|
||||
|
||||
protected boolean isMapBaseType() {
|
||||
return isOneOf(MAP_TYPES);
|
||||
}
|
||||
|
||||
protected Class<?> getMapBaseType() {
|
||||
return getSuperTypeWithin(MAP_TYPES);
|
||||
}
|
||||
|
||||
protected ResolvableType toResolvableType() {
|
||||
return ResolvableType.forType(type);
|
||||
}
|
||||
@@ -525,66 +484,6 @@ class TypeDiscoverer<S> implements TypeInformation<S> {
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the current type is considered a collection.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private boolean isCollection() {
|
||||
|
||||
Class<S> type = getType();
|
||||
|
||||
for (Class<?> collectionType : COLLECTION_TYPES) {
|
||||
if (collectionType.isAssignableFrom(type)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the current's raw type is one of the given ones.
|
||||
*
|
||||
* @param candidates must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
private boolean isOneOf(Class<?>[] candidates) {
|
||||
|
||||
Assert.notNull(candidates, "Candidates must not be null!");
|
||||
|
||||
Class<S> type = getType();
|
||||
|
||||
for (Class<?> candidate : candidates) {
|
||||
if (candidate.equals(type)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the super type of the current raw type from the given candidates.
|
||||
*
|
||||
* @param candidates must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
private Class<?> getSuperTypeWithin(Class<?>[] candidates) {
|
||||
|
||||
Assert.notNull(candidates, "Candidates must not be null!");
|
||||
|
||||
Class<S> type = getType();
|
||||
|
||||
for (Class<?> candidate : candidates) {
|
||||
if (candidate.isAssignableFrom(type)) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException(String.format("Type %s not contained in candidates %s!", type, candidates));
|
||||
}
|
||||
|
||||
private boolean isNullableWrapper() {
|
||||
return NullableWrapperConverters.supports(getType());
|
||||
}
|
||||
|
||||
@@ -1,181 +0,0 @@
|
||||
/*
|
||||
* Copyright 2022-2022 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
|
||||
*
|
||||
* https://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.data.util;
|
||||
|
||||
import io.vavr.collection.LinkedHashMap;
|
||||
import io.vavr.collection.LinkedHashSet;
|
||||
import io.vavr.collection.Traversable;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.core.convert.converter.ConditionalGenericConverter;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* Converter implementations to map from and to Vavr collections.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Christoph Strobl
|
||||
* @since 2.7
|
||||
*/
|
||||
public class VavrCollectionConverters {
|
||||
|
||||
private static final boolean VAVR_PRESENT = ClassUtils.isPresent("io.vavr.control.Option",
|
||||
NullableWrapperConverters.class.getClassLoader());
|
||||
|
||||
public static Collection<Object> getConvertersToRegister() {
|
||||
|
||||
if (!VAVR_PRESENT) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
return Arrays.asList(ToJavaConverter.INSTANCE, FromJavaConverter.INSTANCE);
|
||||
}
|
||||
|
||||
public enum ToJavaConverter implements ConditionalGenericConverter {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
private static final TypeDescriptor TRAVERSAL_TYPE = TypeDescriptor.valueOf(Traversable.class);
|
||||
private static final Set<Class<?>> COLLECTIONS_AND_MAP = new HashSet<>(
|
||||
Arrays.asList(Collection.class, List.class, Set.class, Map.class));
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Set<ConvertiblePair> getConvertibleTypes() {
|
||||
|
||||
return COLLECTIONS_AND_MAP.stream()
|
||||
.map(it -> new ConvertiblePair(Traversable.class, it))
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
|
||||
|
||||
return sourceType.isAssignableTo(TRAVERSAL_TYPE)
|
||||
&& COLLECTIONS_AND_MAP.contains(targetType.getType());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
|
||||
|
||||
if (source == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (source instanceof io.vavr.collection.Seq) {
|
||||
return ((io.vavr.collection.Seq<?>) source).asJava();
|
||||
}
|
||||
|
||||
if (source instanceof io.vavr.collection.Map) {
|
||||
return ((io.vavr.collection.Map<?, ?>) source).toJavaMap();
|
||||
}
|
||||
|
||||
if (source instanceof io.vavr.collection.Set) {
|
||||
return ((io.vavr.collection.Set<?>) source).toJavaSet();
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Unsupported Vavr collection " + source.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
public enum FromJavaConverter implements ConditionalGenericConverter {
|
||||
|
||||
INSTANCE {
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public java.util.Set<ConvertiblePair> getConvertibleTypes() {
|
||||
return CONVERTIBLE_PAIRS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
|
||||
|
||||
// Prevent collections to be mapped to maps
|
||||
if (sourceType.isCollection() && io.vavr.collection.Map.class.isAssignableFrom(targetType.getType())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Prevent maps to be mapped to collections
|
||||
if (sourceType.isMap() && !(io.vavr.collection.Map.class.isAssignableFrom(targetType.getType())
|
||||
|| targetType.getType().equals(Traversable.class))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Object convert(@Nullable Object source, TypeDescriptor sourceDescriptor, TypeDescriptor targetDescriptor) {
|
||||
|
||||
Class<?> targetType = targetDescriptor.getType();
|
||||
|
||||
if (io.vavr.collection.Seq.class.isAssignableFrom(targetType)) {
|
||||
return io.vavr.collection.List.ofAll((Iterable<?>) source);
|
||||
}
|
||||
|
||||
if (io.vavr.collection.Set.class.isAssignableFrom(targetType)) {
|
||||
return LinkedHashSet.ofAll((Iterable<?>) source);
|
||||
}
|
||||
|
||||
if (io.vavr.collection.Map.class.isAssignableFrom(targetType)) {
|
||||
return LinkedHashMap.ofAll((Map<?, ?>) source);
|
||||
}
|
||||
|
||||
// No dedicated type asked for, probably Traversable.
|
||||
// Try to stay as close to the source value.
|
||||
|
||||
if (source instanceof List) {
|
||||
return io.vavr.collection.List.ofAll((Iterable<?>) source);
|
||||
}
|
||||
|
||||
if (source instanceof Set) {
|
||||
return LinkedHashSet.ofAll((Iterable<?>) source);
|
||||
}
|
||||
|
||||
if (source instanceof Map) {
|
||||
return LinkedHashMap.ofAll((Map<?, ?>) source);
|
||||
}
|
||||
|
||||
return source;
|
||||
}
|
||||
};
|
||||
|
||||
private static final Set<ConvertiblePair> CONVERTIBLE_PAIRS;
|
||||
|
||||
static {
|
||||
|
||||
Set<ConvertiblePair> pairs = new HashSet<>();
|
||||
pairs.add(new ConvertiblePair(Collection.class, io.vavr.collection.Traversable.class));
|
||||
pairs.add(new ConvertiblePair(Map.class, io.vavr.collection.Traversable.class));
|
||||
|
||||
CONVERTIBLE_PAIRS = Collections.unmodifiableSet(pairs);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user