Skip to content

Commit deceb86

Browse files
committed
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.
1 parent b3ec06f commit deceb86

File tree

5 files changed

+381
-3
lines changed

5 files changed

+381
-3
lines changed

pom.xml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
<properties>
1919
<javaslang>2.0.6</javaslang>
2020
<vavr>0.10.4</vavr>
21+
<eclipse-collections>11.0.0</eclipse-collections>
2122
<scala>2.11.7</scala>
2223
<xmlbeam>1.4.23</xmlbeam>
2324

@@ -160,6 +161,22 @@
160161
<version>${vavr}</version>
161162
<optional>true</optional>
162163
</dependency>
164+
165+
<!-- Eclipse Collections -->
166+
167+
<dependency>
168+
<groupId>org.eclipse.collections</groupId>
169+
<artifactId>eclipse-collections-api</artifactId>
170+
<version>${eclipse-collections}</version>
171+
<optional>true</optional>
172+
</dependency>
173+
174+
<dependency>
175+
<groupId>org.eclipse.collections</groupId>
176+
<artifactId>eclipse-collections</artifactId>
177+
<version>${eclipse-collections}</version>
178+
<scope>test</scope>
179+
</dependency>
163180

164181
<!-- CDI -->
165182

src/main/java/org/springframework/data/util/CustomCollections.java

Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,24 @@
3333
import java.util.function.Supplier;
3434
import java.util.stream.Collectors;
3535

36+
import org.eclipse.collections.api.RichIterable;
37+
import org.eclipse.collections.api.bag.ImmutableBag;
38+
import org.eclipse.collections.api.bag.MutableBag;
39+
import org.eclipse.collections.api.factory.Bags;
40+
import org.eclipse.collections.api.factory.Lists;
41+
import org.eclipse.collections.api.factory.Maps;
42+
import org.eclipse.collections.api.factory.Sets;
43+
import org.eclipse.collections.api.list.ImmutableList;
44+
import org.eclipse.collections.api.list.MutableList;
45+
import org.eclipse.collections.api.map.ImmutableMap;
46+
import org.eclipse.collections.api.map.MapIterable;
47+
import org.eclipse.collections.api.map.MutableMap;
48+
import org.eclipse.collections.api.set.ImmutableSet;
49+
import org.eclipse.collections.api.set.MutableSet;
3650
import org.springframework.core.convert.TypeDescriptor;
51+
import org.springframework.core.convert.converter.ConditionalConverter;
3752
import org.springframework.core.convert.converter.ConditionalGenericConverter;
53+
import org.springframework.core.convert.converter.Converter;
3854
import org.springframework.core.convert.converter.ConverterRegistry;
3955
import org.springframework.core.io.support.SpringFactoriesLoader;
4056
import org.springframework.lang.NonNull;
@@ -486,4 +502,232 @@ public Object convert(@Nullable Object source, TypeDescriptor sourceDescriptor,
486502
}
487503
}
488504
}
505+
506+
static class EclipseCollections implements CustomCollectionRegistrar {
507+
508+
/*
509+
* (non-Javadoc)
510+
* @see org.springframework.data.util.CustomCollectionRegistrar#isAvailable()
511+
*/
512+
@Override
513+
public boolean isAvailable() {
514+
return ClassUtils.isPresent("org.eclipse.collections.api.list.ImmutableList",
515+
EclipseCollections.class.getClassLoader());
516+
}
517+
518+
/*
519+
* (non-Javadoc)
520+
* @see org.springframework.data.util.CustomCollectionRegistrar#getCollectionTypes()
521+
*/
522+
@Override
523+
public Collection<Class<?>> getCollectionTypes() {
524+
return List.of(ImmutableList.class, ImmutableSet.class, ImmutableBag.class, MutableList.class, MutableSet.class,
525+
MutableBag.class);
526+
}
527+
528+
/*
529+
* (non-Javadoc)
530+
* @see org.springframework.data.util.CustomCollectionRegistrar#getMapTypes()
531+
*/
532+
@Override
533+
public Collection<Class<?>> getMapTypes() {
534+
return List.of(ImmutableMap.class, MutableMap.class);
535+
}
536+
537+
/*
538+
* (non-Javadoc)
539+
* @see org.springframework.data.util.CustomCollectionRegistrar#getAllowedPaginationReturnTypes()
540+
*/
541+
@Override
542+
public Collection<Class<?>> getAllowedPaginationReturnTypes() {
543+
return List.of(ImmutableList.class, MutableList.class);
544+
}
545+
546+
/*
547+
* (non-Javadoc)
548+
* @see org.springframework.data.util.CustomCollectionRegistrar#toJavaNativeCollection()
549+
*/
550+
@Override
551+
public Function<Object, Object> toJavaNativeCollection() {
552+
553+
return source -> source instanceof RichIterable
554+
? EclipseToJavaConverter.INSTANCE.convert(source)
555+
: source;
556+
}
557+
558+
/*
559+
* (non-Javadoc)
560+
* @see org.springframework.data.util.CustomCollectionRegistrar#registerConvertersIn(org.springframework.core.convert.converter.ConverterRegistry)
561+
*/
562+
@Override
563+
public void registerConvertersIn(ConverterRegistry registry) {
564+
565+
registry.addConverter(EclipseToJavaConverter.INSTANCE);
566+
registry.addConverter(JavaToEclipseConverter.INSTANCE);
567+
}
568+
569+
enum EclipseToJavaConverter implements Converter<Object, Object>, ConditionalConverter {
570+
571+
INSTANCE;
572+
573+
private static final TypeDescriptor RICH_ITERABLE_DESCRIPTOR = TypeDescriptor.valueOf(RichIterable.class);
574+
575+
/*
576+
* (non-Javadoc)
577+
* @see org.springframework.core.convert.converter.ConditionalConverter#matches(org.springframework.core.convert.TypeDescriptor, org.springframework.core.convert.TypeDescriptor)
578+
*/
579+
@Override
580+
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
581+
582+
return sourceType.isAssignableTo(RICH_ITERABLE_DESCRIPTOR)
583+
&& COLLECTIONS_AND_MAP.contains(targetType.getType());
584+
}
585+
586+
/*
587+
* (non-Javadoc)
588+
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
589+
*/
590+
@Nullable
591+
@Override
592+
public Object convert(@Nullable Object source) {
593+
594+
if (source instanceof ImmutableList) {
595+
return ((ImmutableList<?>) source).toList();
596+
}
597+
598+
if (source instanceof ImmutableBag) {
599+
return ((ImmutableBag<?>) source).toList();
600+
}
601+
602+
if (source instanceof ImmutableSet) {
603+
return ((ImmutableSet<?>) source).toSet();
604+
}
605+
606+
if (source instanceof ImmutableMap) {
607+
return ((ImmutableMap<?, ?>) source).toMap();
608+
}
609+
610+
return source;
611+
}
612+
}
613+
614+
enum JavaToEclipseConverter implements ConditionalGenericConverter {
615+
616+
INSTANCE;
617+
618+
private static final Set<ConvertiblePair> CONVERTIBLE_PAIRS;
619+
620+
static {
621+
622+
var pairs = new HashSet<ConvertiblePair>();
623+
pairs.add(new ConvertiblePair(Collection.class, RichIterable.class));
624+
625+
pairs.add(new ConvertiblePair(Set.class, MutableSet.class));
626+
pairs.add(new ConvertiblePair(Set.class, MutableList.class));
627+
pairs.add(new ConvertiblePair(Set.class, ImmutableSet.class));
628+
pairs.add(new ConvertiblePair(Set.class, ImmutableList.class));
629+
630+
pairs.add(new ConvertiblePair(List.class, MutableList.class));
631+
pairs.add(new ConvertiblePair(List.class, ImmutableList.class));
632+
633+
pairs.add(new ConvertiblePair(Map.class, RichIterable.class));
634+
pairs.add(new ConvertiblePair(Map.class, MutableMap.class));
635+
pairs.add(new ConvertiblePair(Map.class, ImmutableMap.class));
636+
637+
CONVERTIBLE_PAIRS = Collections.unmodifiableSet(pairs);
638+
}
639+
640+
/*
641+
* (non-Javadoc)
642+
* @see org.springframework.core.convert.converter.GenericConverter#getConvertibleTypes()
643+
*/
644+
@NonNull
645+
@Override
646+
public Set<ConvertiblePair> getConvertibleTypes() {
647+
return CONVERTIBLE_PAIRS;
648+
}
649+
650+
/*
651+
* (non-Javadoc)
652+
* @see org.springframework.core.convert.converter.ConditionalConverter#matches(org.springframework.core.convert.TypeDescriptor, org.springframework.core.convert.TypeDescriptor)
653+
*/
654+
@Override
655+
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
656+
657+
// Prevent collections to be mapped to maps
658+
if (sourceType.isCollection() && MapIterable.class.isAssignableFrom(targetType.getType())) {
659+
return false;
660+
}
661+
662+
// Prevent maps to be mapped to collections
663+
if (sourceType.isMap() //
664+
&& !(MapIterable.class.isAssignableFrom(targetType.getType())
665+
|| targetType.getType().equals(RichIterable.class))) {
666+
return false;
667+
}
668+
669+
return true;
670+
}
671+
672+
/*
673+
* (non-Javadoc)
674+
* @see org.springframework.core.convert.converter.GenericConverter#convert(java.lang.Object, org.springframework.core.convert.TypeDescriptor, org.springframework.core.convert.TypeDescriptor)
675+
*/
676+
@Nullable
677+
@Override
678+
public Object convert(@Nullable Object source, TypeDescriptor sourceDescriptor, TypeDescriptor targetDescriptor) {
679+
680+
var targetType = targetDescriptor.getType();
681+
682+
if (ImmutableList.class.isAssignableFrom(targetType)) {
683+
return Lists.immutable.ofAll((Iterable<?>) source);
684+
}
685+
686+
if (ImmutableSet.class.isAssignableFrom(targetType)) {
687+
return Sets.immutable.ofAll((Iterable<?>) source);
688+
}
689+
690+
if (ImmutableBag.class.isAssignableFrom(targetType)) {
691+
return Bags.immutable.ofAll((Iterable<?>) source);
692+
}
693+
694+
if (ImmutableMap.class.isAssignableFrom(targetType)) {
695+
return Maps.immutable.ofAll((Map<?, ?>) source);
696+
}
697+
698+
if (MutableList.class.isAssignableFrom(targetType)) {
699+
return Lists.mutable.ofAll((Iterable<?>) source);
700+
}
701+
702+
if (MutableSet.class.isAssignableFrom(targetType)) {
703+
return Sets.mutable.ofAll((Iterable<?>) source);
704+
}
705+
706+
if (MutableBag.class.isAssignableFrom(targetType)) {
707+
return Bags.mutable.ofAll((Iterable<?>) source);
708+
}
709+
710+
if (MutableMap.class.isAssignableFrom(targetType)) {
711+
return Maps.mutable.ofMap((Map<?, ?>) source);
712+
}
713+
714+
// No dedicated type asked for, probably RichIterable.
715+
// Try to stay as close to the source value.
716+
717+
if (source instanceof List) {
718+
return Lists.mutable.ofAll((Iterable<?>) source);
719+
}
720+
721+
if (source instanceof Set) {
722+
return Sets.mutable.ofAll((Iterable<?>) source);
723+
}
724+
725+
if (source instanceof Map) {
726+
return Maps.mutable.ofMap((Map<?, ?>) source);
727+
}
728+
729+
return source;
730+
}
731+
}
732+
}
489733
}
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
org.springframework.data.web.config.SpringDataJacksonModules=org.springframework.data.web.config.SpringDataJacksonConfiguration
2-
org.springframework.data.util.CustomCollectionRegistrar=org.springframework.data.util.CustomCollections.VavrCollections
2+
org.springframework.data.util.CustomCollectionRegistrar=org.springframework.data.util.CustomCollections.VavrCollections, \
3+
org.springframework.data.util.CustomCollections.EclipseCollections

src/test/java/org/springframework/data/repository/query/QueryMethodUnitTests.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@
2727
import java.util.concurrent.Future;
2828
import java.util.stream.Stream;
2929

30+
import org.eclipse.collections.api.list.ImmutableList;
3031
import org.junit.jupiter.api.Test;
31-
3232
import org.springframework.data.domain.Page;
3333
import org.springframework.data.domain.Pageable;
3434
import org.springframework.data.domain.Slice;
@@ -248,6 +248,15 @@ void detectsReactiveSliceQuery() throws Exception {
248248
assertThat(returnedType.getDomainType()).isEqualTo(User.class);
249249
}
250250

251+
@Test // #1817
252+
void considersEclipseCollectionCollectionQuery() throws Exception {
253+
254+
var method = SampleRepository.class.getMethod("returnsEclipseCollection");
255+
var queryMethod = new QueryMethod(method, metadata, factory);
256+
257+
assertThat(queryMethod.isCollectionQuery()).isTrue();
258+
}
259+
251260
interface SampleRepository extends Repository<User, Serializable> {
252261

253262
String pagingMethodWithInvalidReturnType(Pageable pageable);
@@ -294,6 +303,8 @@ interface SampleRepository extends Repository<User, Serializable> {
294303
Future<Option<User>> returnsFutureOfOption();
295304

296305
Mono<Slice<User>> reactiveSlice();
306+
307+
ImmutableList<User> returnsEclipseCollection();
297308
}
298309

299310
class User {

0 commit comments

Comments
 (0)