From 628dc8b9c85aed1f182fac9ca80f3a404caf00c8 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Mon, 13 Mar 2017 09:47:27 +0100 Subject: [PATCH] DATACMNS-867 - Introduced monadic methods on Streamable. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Streamable now exposes map(…), flatMap(…) and filter(…) to allow easy conversion into new Streamables. Introduced LazyStreamable to back those implementations. Simplified implementation of methods in PartTree to use those new methods. Changed Page and Slice map(…) method to take a Function over a Converter so that act as specialization of the newly introduced map(…) method. --- .../springframework/data/domain/Chunk.java | 5 +- .../org/springframework/data/domain/Page.java | 6 +- .../springframework/data/domain/PageImpl.java | 7 +- .../springframework/data/domain/Slice.java | 3 +- .../data/domain/SliceImpl.java | 5 +- .../repository/query/ResultProcessor.java | 2 +- .../repository/query/parser/PartTree.java | 7 +- .../data/util/LazyStreamable.java | 52 ++++++++++++++ .../springframework/data/util/Streamable.java | 68 ++++++++++++++++--- 9 files changed, 128 insertions(+), 27 deletions(-) create mode 100644 src/main/java/org/springframework/data/util/LazyStreamable.java diff --git a/src/main/java/org/springframework/data/domain/Chunk.java b/src/main/java/org/springframework/data/domain/Chunk.java index 6b69285f0..8e6c0c037 100644 --- a/src/main/java/org/springframework/data/domain/Chunk.java +++ b/src/main/java/org/springframework/data/domain/Chunk.java @@ -20,6 +20,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; +import java.util.function.Function; import java.util.stream.Collectors; import org.springframework.core.convert.converter.Converter; @@ -156,11 +157,11 @@ abstract class Chunk implements Slice, Serializable { * @param converter must not be {@literal null}. * @return */ - protected List getConvertedContent(Converter converter) { + protected List getConvertedContent(Function converter) { Assert.notNull(converter, "Converter must not be null!"); - return this.stream().map(converter::convert).collect(Collectors.toList()); + return this.stream().map(converter::apply).collect(Collectors.toList()); } /* diff --git a/src/main/java/org/springframework/data/domain/Page.java b/src/main/java/org/springframework/data/domain/Page.java index 0254c1706..9e5b9aabf 100644 --- a/src/main/java/org/springframework/data/domain/Page.java +++ b/src/main/java/org/springframework/data/domain/Page.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2015 the original author or authors. + * Copyright 2008-2017 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. @@ -15,6 +15,8 @@ */ package org.springframework.data.domain; +import java.util.function.Function; + import org.springframework.core.convert.converter.Converter; /** @@ -47,5 +49,5 @@ public interface Page extends Slice { * @return a new {@link Page} with the content of the current one mapped by the given {@link Converter}. * @since 1.10 */ - Page map(Converter converter); + Page map(Function converter); } diff --git a/src/main/java/org/springframework/data/domain/PageImpl.java b/src/main/java/org/springframework/data/domain/PageImpl.java index 812adfd76..58b7f08df 100644 --- a/src/main/java/org/springframework/data/domain/PageImpl.java +++ b/src/main/java/org/springframework/data/domain/PageImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2016 the original author or authors. + * Copyright 2008-2017 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. @@ -17,8 +17,7 @@ package org.springframework.data.domain; import java.util.List; import java.util.Optional; - -import org.springframework.core.convert.converter.Converter; +import java.util.function.Function; /** * Basic {@code Page} implementation. @@ -106,7 +105,7 @@ public class PageImpl extends Chunk implements Page { * @see org.springframework.data.domain.Slice#transform(org.springframework.core.convert.converter.Converter) */ @Override - public Page map(Converter converter) { + public Page map(Function converter) { return new PageImpl<>(getConvertedContent(converter), pageable, total); } diff --git a/src/main/java/org/springframework/data/domain/Slice.java b/src/main/java/org/springframework/data/domain/Slice.java index 04268e600..8ad73582e 100644 --- a/src/main/java/org/springframework/data/domain/Slice.java +++ b/src/main/java/org/springframework/data/domain/Slice.java @@ -16,6 +16,7 @@ package org.springframework.data.domain; import java.util.List; +import java.util.function.Function; import org.springframework.core.convert.converter.Converter; import org.springframework.data.util.Streamable; @@ -124,5 +125,5 @@ public interface Slice extends Streamable { * @return a new {@link Slice} with the content of the current one mapped by the given {@link Converter}. * @since 1.10 */ - Slice map(Converter converter); + Slice map(Function converter); } diff --git a/src/main/java/org/springframework/data/domain/SliceImpl.java b/src/main/java/org/springframework/data/domain/SliceImpl.java index a2f0c3e3b..875842d1e 100644 --- a/src/main/java/org/springframework/data/domain/SliceImpl.java +++ b/src/main/java/org/springframework/data/domain/SliceImpl.java @@ -16,8 +16,7 @@ package org.springframework.data.domain; import java.util.List; - -import org.springframework.core.convert.converter.Converter; +import java.util.function.Function; /** * Default implementation of {@link Slice}. @@ -70,7 +69,7 @@ public class SliceImpl extends Chunk { * @see org.springframework.data.domain.Slice#transform(org.springframework.core.convert.converter.Converter) */ @Override - public Slice map(Converter converter) { + public Slice map(Function converter) { return new SliceImpl<>(getConvertedContent(converter), pageable, hasNext); } diff --git a/src/main/java/org/springframework/data/repository/query/ResultProcessor.java b/src/main/java/org/springframework/data/repository/query/ResultProcessor.java index 1cc382141..200d7ed26 100644 --- a/src/main/java/org/springframework/data/repository/query/ResultProcessor.java +++ b/src/main/java/org/springframework/data/repository/query/ResultProcessor.java @@ -134,7 +134,7 @@ public class ResultProcessor { ChainingConverter converter = ChainingConverter.of(type.getReturnedType(), preparingConverter).and(this.converter); if (source instanceof Slice && method.isPageQuery() || method.isSliceQuery()) { - return (T) ((Slice) source).map(converter); + return (T) ((Slice) source).map(converter::convert); } if (source instanceof Collection && method.isCollectionQuery()) { diff --git a/src/main/java/org/springframework/data/repository/query/parser/PartTree.java b/src/main/java/org/springframework/data/repository/query/parser/PartTree.java index 6ddf4f988..f54e4ae8d 100644 --- a/src/main/java/org/springframework/data/repository/query/parser/PartTree.java +++ b/src/main/java/org/springframework/data/repository/query/parser/PartTree.java @@ -177,7 +177,7 @@ public class PartTree implements Streamable { * @return the iterable {@link Part}s */ public Streamable getParts() { - return Streamable.of(this.stream().flatMap(OrPart::stream).collect(Collectors.toList())); + return flatMap(OrPart::stream); } /** @@ -187,10 +187,7 @@ public class PartTree implements Streamable { * @return */ public Streamable getParts(Type type) { - - return Streamable.of(getParts().stream()// - .filter(part -> part.getType().equals(type))// - .collect(Collectors.toList())); + return getParts().filter(part -> part.getType().equals(type)); } /* diff --git a/src/main/java/org/springframework/data/util/LazyStreamable.java b/src/main/java/org/springframework/data/util/LazyStreamable.java new file mode 100644 index 000000000..812409631 --- /dev/null +++ b/src/main/java/org/springframework/data/util/LazyStreamable.java @@ -0,0 +1,52 @@ +/* + * Copyright 2017 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 + * + * http://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 lombok.Value; + +import java.util.Iterator; +import java.util.function.Supplier; +import java.util.stream.Stream; + +/** + * Lazy implementation of {@link Streamable} obtains a {@link Stream} from a given {@link Supplier}. + * + * @author Oliver Gierke + * @since 2.0 + */ +@Value(staticConstructor = "of") +class LazyStreamable implements Streamable { + + private final Supplier> stream; + + /* + * (non-Javadoc) + * @see java.lang.Iterable#iterator() + */ + @Override + public Iterator iterator() { + return stream().iterator(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.util.Streamable#stream() + */ + @Override + public Stream stream() { + return stream.get(); + } +} diff --git a/src/main/java/org/springframework/data/util/Streamable.java b/src/main/java/org/springframework/data/util/Streamable.java index 1ea612231..ae6d30f4c 100644 --- a/src/main/java/org/springframework/data/util/Streamable.java +++ b/src/main/java/org/springframework/data/util/Streamable.java @@ -17,6 +17,9 @@ package org.springframework.data.util; import java.util.Arrays; import java.util.Collections; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; import java.util.stream.Stream; import java.util.stream.StreamSupport; @@ -28,17 +31,9 @@ import org.springframework.util.Assert; * @author Oliver Gierke * @author Christoph Strobl */ +@FunctionalInterface public interface Streamable extends Iterable { - /** - * Creates a non-parallel {@link Stream} of the underlying {@link Iterable}. - * - * @return will never be {@literal null}. - */ - default Stream stream() { - return StreamSupport.stream(spliterator(), false); - } - /** * Returns an empty {@link Streamable}. * @@ -71,4 +66,59 @@ public interface Streamable extends Iterable { return iterable::iterator; } + + static Streamable of(Supplier> supplier) { + return LazyStreamable.of(supplier); + } + + /** + * Creates a non-parallel {@link Stream} of the underlying {@link Iterable}. + * + * @return will never be {@literal null}. + */ + default Stream stream() { + return StreamSupport.stream(spliterator(), false); + } + + /** + * Returns a new {@link Streamable} that will apply the given {@link Function} to the current one. + * + * @param mapper must not be {@literal null}. + * @return + * @see Stream#map(Function) + */ + default Streamable map(Function mapper) { + + Assert.notNull(mapper, "Mapping function must not be null!"); + + return Streamable.of(() -> stream().map(mapper)); + } + + /** + * Returns a new {@link Streamable} that will apply the given {@link Function} to the current one. + * + * @param mapper must not be {@literal null}. + * @return + * @see Stream#flatMap(Function) + */ + default Streamable flatMap(Function> mapper) { + + Assert.notNull(mapper, "Mapping function must not be null!"); + + return Streamable.of(() -> stream().flatMap(mapper)); + } + + /** + * Returns a new {@link Streamable} that will apply the given filter {@link Predicate} to the current one. + * + * @param predicate must not be {@literal null}. + * @return + * @see Stream#filter(Predicate) + */ + default Streamable filter(Predicate predicate) { + + Assert.notNull(predicate, "Filter predicate must not be null!"); + + return Streamable.of(() -> stream().filter(predicate)); + } }