Support for Eclipse Collections.
Using the newly introduced CustomCollectionsRegistrar SPI to provide support for Eclipse Collections (Imm|M)utable(List|Set|Bag|Map). Fixes #2618.
This commit is contained in:
17
pom.xml
17
pom.xml
@@ -18,6 +18,7 @@
|
||||
<properties>
|
||||
<javaslang>2.0.6</javaslang>
|
||||
<vavr>0.10.4</vavr>
|
||||
<eclipse-collections>11.0.0</eclipse-collections>
|
||||
<scala>2.11.7</scala>
|
||||
<xmlbeam>1.4.23</xmlbeam>
|
||||
|
||||
@@ -223,6 +224,22 @@
|
||||
<version>${vavr}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- Eclipse Collections -->
|
||||
|
||||
<dependency>
|
||||
<groupId>org.eclipse.collections</groupId>
|
||||
<artifactId>eclipse-collections-api</artifactId>
|
||||
<version>${eclipse-collections}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.eclipse.collections</groupId>
|
||||
<artifactId>eclipse-collections</artifactId>
|
||||
<version>${eclipse-collections}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>javax.el</groupId>
|
||||
|
||||
@@ -34,8 +34,24 @@ import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.collections.api.RichIterable;
|
||||
import org.eclipse.collections.api.bag.ImmutableBag;
|
||||
import org.eclipse.collections.api.bag.MutableBag;
|
||||
import org.eclipse.collections.api.factory.Bags;
|
||||
import org.eclipse.collections.api.factory.Lists;
|
||||
import org.eclipse.collections.api.factory.Maps;
|
||||
import org.eclipse.collections.api.factory.Sets;
|
||||
import org.eclipse.collections.api.list.ImmutableList;
|
||||
import org.eclipse.collections.api.list.MutableList;
|
||||
import org.eclipse.collections.api.map.ImmutableMap;
|
||||
import org.eclipse.collections.api.map.MapIterable;
|
||||
import org.eclipse.collections.api.map.MutableMap;
|
||||
import org.eclipse.collections.api.set.ImmutableSet;
|
||||
import org.eclipse.collections.api.set.MutableSet;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.core.convert.converter.ConditionalConverter;
|
||||
import org.springframework.core.convert.converter.ConditionalGenericConverter;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.core.convert.converter.ConverterRegistry;
|
||||
import org.springframework.core.io.support.SpringFactoriesLoader;
|
||||
import org.springframework.lang.NonNull;
|
||||
@@ -488,4 +504,232 @@ public class CustomCollections {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class EclipseCollections implements CustomCollectionRegistrar {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.util.CustomCollectionRegistrar#isAvailable()
|
||||
*/
|
||||
@Override
|
||||
public boolean isAvailable() {
|
||||
return ClassUtils.isPresent("org.eclipse.collections.api.list.ImmutableList",
|
||||
EclipseCollections.class.getClassLoader());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.util.CustomCollectionRegistrar#getCollectionTypes()
|
||||
*/
|
||||
@Override
|
||||
public Collection<Class<?>> getCollectionTypes() {
|
||||
return Arrays.asList(ImmutableList.class, ImmutableSet.class, ImmutableBag.class, //
|
||||
MutableList.class, MutableSet.class, MutableBag.class);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.util.CustomCollectionRegistrar#getMapTypes()
|
||||
*/
|
||||
@Override
|
||||
public Collection<Class<?>> getMapTypes() {
|
||||
return Arrays.asList(ImmutableMap.class, MutableMap.class);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.util.CustomCollectionRegistrar#getAllowedPaginationReturnTypes()
|
||||
*/
|
||||
@Override
|
||||
public Collection<Class<?>> getAllowedPaginationReturnTypes() {
|
||||
return Arrays.asList(ImmutableList.class, MutableList.class);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.util.CustomCollectionRegistrar#toJavaNativeCollection()
|
||||
*/
|
||||
@Override
|
||||
public Function<Object, Object> toJavaNativeCollection() {
|
||||
|
||||
return source -> source instanceof RichIterable
|
||||
? EclipseToJavaConverter.INSTANCE.convert(source)
|
||||
: source;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.util.CustomCollectionRegistrar#registerConvertersIn(org.springframework.core.convert.converter.ConverterRegistry)
|
||||
*/
|
||||
@Override
|
||||
public void registerConvertersIn(ConverterRegistry registry) {
|
||||
|
||||
registry.addConverter(EclipseToJavaConverter.INSTANCE);
|
||||
registry.addConverter(JavaToEclipseConverter.INSTANCE);
|
||||
}
|
||||
|
||||
enum EclipseToJavaConverter implements Converter<Object, Object>, ConditionalConverter {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
private static final TypeDescriptor RICH_ITERABLE_DESCRIPTOR = TypeDescriptor.valueOf(RichIterable.class);
|
||||
|
||||
/*
|
||||
* (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(RICH_ITERABLE_DESCRIPTOR)
|
||||
&& COLLECTIONS_AND_MAP.contains(targetType.getType());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
|
||||
*/
|
||||
@Nullable
|
||||
@Override
|
||||
public Object convert(@Nullable Object source) {
|
||||
|
||||
if (source instanceof ImmutableList) {
|
||||
return ((ImmutableList<?>) source).toList();
|
||||
}
|
||||
|
||||
if (source instanceof ImmutableBag) {
|
||||
return ((ImmutableBag<?>) source).toList();
|
||||
}
|
||||
|
||||
if (source instanceof ImmutableSet) {
|
||||
return ((ImmutableSet<?>) source).toSet();
|
||||
}
|
||||
|
||||
if (source instanceof ImmutableMap) {
|
||||
return ((ImmutableMap<?, ?>) source).toMap();
|
||||
}
|
||||
|
||||
return source;
|
||||
}
|
||||
}
|
||||
|
||||
enum JavaToEclipseConverter implements ConditionalGenericConverter {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
private static final Set<ConvertiblePair> CONVERTIBLE_PAIRS;
|
||||
|
||||
static {
|
||||
|
||||
Set<ConvertiblePair> pairs = new HashSet<>();
|
||||
pairs.add(new ConvertiblePair(Collection.class, RichIterable.class));
|
||||
|
||||
pairs.add(new ConvertiblePair(Set.class, MutableSet.class));
|
||||
pairs.add(new ConvertiblePair(Set.class, MutableList.class));
|
||||
pairs.add(new ConvertiblePair(Set.class, ImmutableSet.class));
|
||||
pairs.add(new ConvertiblePair(Set.class, ImmutableList.class));
|
||||
|
||||
pairs.add(new ConvertiblePair(List.class, MutableList.class));
|
||||
pairs.add(new ConvertiblePair(List.class, ImmutableList.class));
|
||||
|
||||
pairs.add(new ConvertiblePair(Map.class, RichIterable.class));
|
||||
pairs.add(new ConvertiblePair(Map.class, MutableMap.class));
|
||||
pairs.add(new ConvertiblePair(Map.class, ImmutableMap.class));
|
||||
|
||||
CONVERTIBLE_PAIRS = Collections.unmodifiableSet(pairs);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.core.convert.converter.GenericConverter#getConvertibleTypes()
|
||||
*/
|
||||
@NonNull
|
||||
@Override
|
||||
public 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() && MapIterable.class.isAssignableFrom(targetType.getType())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Prevent maps to be mapped to collections
|
||||
if (sourceType.isMap() //
|
||||
&& !(MapIterable.class.isAssignableFrom(targetType.getType())
|
||||
|| targetType.getType().equals(RichIterable.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) {
|
||||
|
||||
Class<?> targetType = targetDescriptor.getType();
|
||||
|
||||
if (ImmutableList.class.isAssignableFrom(targetType)) {
|
||||
return Lists.immutable.ofAll((Iterable<?>) source);
|
||||
}
|
||||
|
||||
if (ImmutableSet.class.isAssignableFrom(targetType)) {
|
||||
return Sets.immutable.ofAll((Iterable<?>) source);
|
||||
}
|
||||
|
||||
if (ImmutableBag.class.isAssignableFrom(targetType)) {
|
||||
return Bags.immutable.ofAll((Iterable<?>) source);
|
||||
}
|
||||
|
||||
if (ImmutableMap.class.isAssignableFrom(targetType)) {
|
||||
return Maps.immutable.ofAll((Map<?, ?>) source);
|
||||
}
|
||||
|
||||
if (MutableList.class.isAssignableFrom(targetType)) {
|
||||
return Lists.mutable.ofAll((Iterable<?>) source);
|
||||
}
|
||||
|
||||
if (MutableSet.class.isAssignableFrom(targetType)) {
|
||||
return Sets.mutable.ofAll((Iterable<?>) source);
|
||||
}
|
||||
|
||||
if (MutableBag.class.isAssignableFrom(targetType)) {
|
||||
return Bags.mutable.ofAll((Iterable<?>) source);
|
||||
}
|
||||
|
||||
if (MutableMap.class.isAssignableFrom(targetType)) {
|
||||
return Maps.mutable.ofMap((Map<?, ?>) source);
|
||||
}
|
||||
|
||||
// No dedicated type asked for, probably RichIterable.
|
||||
// Try to stay as close to the source value.
|
||||
|
||||
if (source instanceof List) {
|
||||
return Lists.mutable.ofAll((Iterable<?>) source);
|
||||
}
|
||||
|
||||
if (source instanceof Set) {
|
||||
return Sets.mutable.ofAll((Iterable<?>) source);
|
||||
}
|
||||
|
||||
if (source instanceof Map) {
|
||||
return Maps.mutable.ofMap((Map<?, ?>) source);
|
||||
}
|
||||
|
||||
return source;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
org.springframework.data.web.config.SpringDataJacksonModules=org.springframework.data.web.config.SpringDataJacksonConfiguration
|
||||
org.springframework.data.util.CustomCollectionRegistrar=org.springframework.data.util.CustomCollections.VavrCollections
|
||||
org.springframework.data.util.CustomCollectionRegistrar=org.springframework.data.util.CustomCollections.VavrCollections, \
|
||||
org.springframework.data.util.CustomCollections.EclipseCollections
|
||||
|
||||
@@ -19,6 +19,7 @@ import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import io.vavr.collection.Seq;
|
||||
import io.vavr.control.Option;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Method;
|
||||
@@ -27,9 +28,8 @@ import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.collections.api.list.ImmutableList;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Slice;
|
||||
@@ -249,6 +249,15 @@ class QueryMethodUnitTests {
|
||||
assertThat(returnedType.getDomainType()).isEqualTo(User.class);
|
||||
}
|
||||
|
||||
@Test // #1817
|
||||
void considersEclipseCollectionCollectionQuery() throws Exception {
|
||||
|
||||
Method method = SampleRepository.class.getMethod("returnsEclipseCollection");
|
||||
QueryMethod queryMethod = new QueryMethod(method, metadata, factory);
|
||||
|
||||
assertThat(queryMethod.isCollectionQuery()).isTrue();
|
||||
}
|
||||
|
||||
interface SampleRepository extends Repository<User, Serializable> {
|
||||
|
||||
String pagingMethodWithInvalidReturnType(Pageable pageable);
|
||||
@@ -295,6 +304,8 @@ class QueryMethodUnitTests {
|
||||
Future<Option<User>> returnsFutureOfOption();
|
||||
|
||||
Mono<Slice<User>> reactiveSlice();
|
||||
|
||||
ImmutableList<User> returnsEclipseCollection();
|
||||
}
|
||||
|
||||
class User {
|
||||
|
||||
@@ -78,6 +78,17 @@ class CustomCollectionsUnitTests {
|
||||
.verify();
|
||||
}
|
||||
|
||||
@TestFactory // #1817
|
||||
Stream<DynamicTest> registersEclipseCollections() {
|
||||
|
||||
return new CustomCollectionTester()
|
||||
.withCollections(ImmutableSet.class, ImmutableList.class, ImmutableBag.class, MutableSet.class,
|
||||
MutableList.class, MutableBag.class)
|
||||
.withMaps(ImmutableMap.class, MutableMap.class)
|
||||
.withMapImplementations(ImmutableUnifiedMap.class, UnifiedMap.class)
|
||||
.verify();
|
||||
}
|
||||
|
||||
@Test // DATACMNS-1065, #1817
|
||||
void conversListToVavr() {
|
||||
|
||||
@@ -125,6 +136,63 @@ class CustomCollectionsUnitTests {
|
||||
assertThat(result).isInstanceOf(io.vavr.collection.Map.class);
|
||||
}
|
||||
|
||||
@Test // #1817
|
||||
void conversListToEclipse() {
|
||||
|
||||
assertThat(conversionService.canConvert(List.class, RichIterable.class)).isTrue();
|
||||
assertThat(conversionService.canConvert(List.class, ImmutableList.class)).isTrue();
|
||||
assertThat(conversionService.canConvert(List.class, ImmutableSet.class)).isTrue();
|
||||
assertThat(conversionService.canConvert(List.class, ImmutableBag.class)).isTrue();
|
||||
assertThat(conversionService.canConvert(List.class, ImmutableMap.class)).isFalse();
|
||||
|
||||
List<Integer> integers = Arrays.asList(1, 2, 3);
|
||||
|
||||
assertThat(conversionService.convert(integers, RichIterable.class)).isInstanceOf(MutableList.class);
|
||||
assertThat(conversionService.convert(integers, MutableList.class)).isInstanceOf(MutableList.class);
|
||||
assertThat(conversionService.convert(integers, MutableSet.class)).isInstanceOf(MutableSet.class);
|
||||
assertThat(conversionService.convert(integers, MutableBag.class)).isInstanceOf(MutableBag.class);
|
||||
assertThat(conversionService.convert(integers, ImmutableList.class)).isInstanceOf(ImmutableList.class);
|
||||
assertThat(conversionService.convert(integers, ImmutableSet.class)).isInstanceOf(ImmutableSet.class);
|
||||
assertThat(conversionService.convert(integers, ImmutableBag.class)).isInstanceOf(ImmutableBag.class);
|
||||
}
|
||||
|
||||
@Test // #1817
|
||||
void convertsSetToEclipse() {
|
||||
|
||||
assertThat(conversionService.canConvert(Set.class, ImmutableSet.class)).isTrue();
|
||||
assertThat(conversionService.canConvert(Set.class, ImmutableBag.class)).isTrue();
|
||||
assertThat(conversionService.canConvert(Set.class, ImmutableList.class)).isTrue();
|
||||
assertThat(conversionService.canConvert(Set.class, ImmutableMap.class)).isFalse();
|
||||
|
||||
Set<Integer> integers = Collections.singleton(1);
|
||||
|
||||
assertThat(conversionService.convert(integers, RichIterable.class)).isInstanceOf(MutableSet.class);
|
||||
assertThat(conversionService.convert(integers, MutableList.class)).isInstanceOf(MutableList.class);
|
||||
assertThat(conversionService.convert(integers, MutableSet.class)).isInstanceOf(MutableSet.class);
|
||||
assertThat(conversionService.convert(integers, MutableBag.class)).isInstanceOf(MutableBag.class);
|
||||
assertThat(conversionService.convert(integers, ImmutableList.class)).isInstanceOf(ImmutableList.class);
|
||||
assertThat(conversionService.convert(integers, ImmutableSet.class)).isInstanceOf(ImmutableSet.class);
|
||||
assertThat(conversionService.convert(integers, ImmutableBag.class)).isInstanceOf(ImmutableBag.class);
|
||||
}
|
||||
|
||||
@Test // #1817
|
||||
void convertsMapToEclipse() {
|
||||
|
||||
assertThat(conversionService.canConvert(Map.class, RichIterable.class)).isTrue();
|
||||
assertThat(conversionService.canConvert(Map.class, MapIterable.class)).isTrue();
|
||||
assertThat(conversionService.canConvert(Map.class, SetIterable.class)).isFalse();
|
||||
assertThat(conversionService.canConvert(Map.class, ListIterable.class)).isFalse();
|
||||
|
||||
Map<String, String> map = Collections.singletonMap("key", "value");
|
||||
|
||||
assertThat(conversionService.convert(map, RichIterable.class)).isInstanceOf(MutableMap.class);
|
||||
assertThat(conversionService.convert(map, ImmutableMap.class)).isInstanceOf(ImmutableMap.class);
|
||||
|
||||
// Required as MutableMap implements both Iterable and Map and for the standard
|
||||
// Java compiler this creates an ambiguity
|
||||
assertThat((Map<?, ?>) conversionService.convert(map, MutableMap.class)).isInstanceOf(MutableMap.class);
|
||||
}
|
||||
|
||||
@Test // DATACMNS-1065, #1817
|
||||
void unwrapsVavrCollectionsToJavaOnes() {
|
||||
|
||||
@@ -133,6 +201,18 @@ class CustomCollectionsUnitTests {
|
||||
assertThat(unwrap(io.vavr.collection.LinkedHashMap.of("key", "value"))).isInstanceOf(Map.class);
|
||||
}
|
||||
|
||||
@Test // #1817
|
||||
void unwrapsEclipseCollectionsToJavaOnes() {
|
||||
|
||||
assertThat(unwrap(Lists.immutable.of(1, 2, 3))).isInstanceOf(List.class);
|
||||
assertThat(unwrap(Sets.immutable.of(1, 2, 3))).isInstanceOf(Set.class);
|
||||
assertThat(unwrap(Maps.immutable.of("key", "value"))).isInstanceOf(Map.class);
|
||||
|
||||
assertThat(unwrap(Lists.mutable.of(1, 2, 3))).isInstanceOf(List.class);
|
||||
assertThat(unwrap(Sets.mutable.of(1, 2, 3))).isInstanceOf(Set.class);
|
||||
assertThat(unwrap(Maps.mutable.of("key", "value"))).isInstanceOf(Map.class);
|
||||
}
|
||||
|
||||
@Test // #1817
|
||||
void rejectsInvalidMapType() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> CustomCollections.getMapBaseType(Object.class));
|
||||
@@ -143,7 +223,15 @@ class CustomCollectionsUnitTests {
|
||||
assertThat(CustomCollections.getPaginationReturnTypes()).contains(io.vavr.collection.Seq.class);
|
||||
}
|
||||
|
||||
@Test // DATAJPA-1258. #1817
|
||||
@TestFactory // #1817
|
||||
Stream<DynamicTest> eclipseSupportedPaginationReturnTypes() {
|
||||
|
||||
return DynamicTest.stream(Stream.of(ImmutableList.class, MutableList.class),
|
||||
it -> it.getSimpleName() + " is a pagination return type",
|
||||
it -> assertThat(CustomCollections.getPaginationReturnTypes()).contains(it));
|
||||
}
|
||||
|
||||
@Test // DATAJPA-1258, #1817
|
||||
void convertsJavaListsToVavrSet() {
|
||||
assertThat(conversionService.convert(Collections.singletonList("foo"), io.vavr.collection.Set.class)) //
|
||||
.isInstanceOf(io.vavr.collection.Set.class);
|
||||
|
||||
Reference in New Issue
Block a user