From d754e63f0681674e3a4af941c90b692ec41cc2e0 Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Sun, 11 Oct 2015 15:35:46 +0600 Subject: [PATCH 01/42] MoreCollectors: groupingBy documentation --- .../javax/util/streamex/MoreCollectors.java | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/src/main/java/javax/util/streamex/MoreCollectors.java b/src/main/java/javax/util/streamex/MoreCollectors.java index 72b4e41f..68699392 100644 --- a/src/main/java/javax/util/streamex/MoreCollectors.java +++ b/src/main/java/javax/util/streamex/MoreCollectors.java @@ -884,6 +884,7 @@ class Container { * a {@code Collector} implementing the downstream reduction * @return a {@code Collector} implementing the cascaded group-by operation * @see Collectors#groupingBy(Function, Collector) + * @see #groupingBy(Function, Set, Supplier, Collector) * @since 0.3.7 */ public static , A, D> Collector> groupingByEnum(Class enumClass, @@ -891,11 +892,110 @@ class Container { return groupingBy(classifier, EnumSet.allOf(enumClass), () -> new EnumMap<>(enumClass), downstream); } + /** + * Returns a {@code Collector} implementing a cascaded "group by" operation + * on input elements of type {@code T}, grouping elements according to a + * classification function, and then performing a reduction operation on the + * values associated with a given key using the specified downstream + * {@code Collector}. + * + *

There are no guarantees on the type, mutability, + * serializability, or thread-safety of the {@code Map} returned. + * + *

+ * The main difference of this collector from + * {@link Collectors#groupingBy(Function, Supplier, Collector)} is that it + * accepts additional domain parameter which is the {@code Set} of all + * possible map keys. If the mapper function produces the key out of domain, + * an {@code IllegalStateException} will occur. If the mapper function does + * not produce some of domain keys at all, they are also added to the + * result. These keys are mapped to the default collector value which is + * equivalent to collecting an empty stream with the same collector. + * + *

+ * This method returns a short-circuiting + * collector if the downstream collector is short-circuiting. The + * collection might stop when for every possible key from the domain the + * downstream collection is known to be finished. + * + * @param + * the type of the input elements + * @param + * the type of the keys + * @param + * the intermediate accumulation type of the downstream collector + * @param + * the result type of the downstream reduction + * @param classifier + * a classifier function mapping input elements to keys + * @param domain + * a domain of all possible key values + * @param downstream + * a {@code Collector} implementing the downstream reduction + * @return a {@code Collector} implementing the cascaded group-by operation + * with given domain + * + * @see #groupingBy(Function, Set, Supplier, Collector) + * @see #groupingByEnum(Class, Function, Collector) + * @since 0.4.0 + */ public static Collector> groupingBy(Function classifier, Set domain, Collector downstream) { return groupingBy(classifier, domain, HashMap::new, downstream); } + /** + * Returns a {@code Collector} implementing a cascaded "group by" operation + * on input elements of type {@code T}, grouping elements according to a + * classification function, and then performing a reduction operation on the + * values associated with a given key using the specified downstream + * {@code Collector}. The {@code Map} produced by the Collector is created + * with the supplied factory function. + * + *

+ * The main difference of this collector from + * {@link Collectors#groupingBy(Function, Supplier, Collector)} is that it + * accepts additional domain parameter which is the {@code Set} of all + * possible map keys. If the mapper function produces the key out of domain, + * an {@code IllegalStateException} will occur. If the mapper function does + * not produce some of domain keys at all, they are also added to the + * result. These keys are mapped to the default collector value which is + * equivalent to collecting an empty stream with the same collector. + * + *

+ * This method returns a short-circuiting + * collector if the downstream collector is short-circuiting. The + * collection might stop when for every possible key from the domain the + * downstream collection is known to be finished. + * + * @param + * the type of the input elements + * @param + * the type of the keys + * @param + * the intermediate accumulation type of the downstream collector + * @param + * the result type of the downstream reduction + * @param + * the type of the resulting {@code Map} + * @param classifier + * a classifier function mapping input elements to keys + * @param domain + * a domain of all possible key values + * @param downstream + * a {@code Collector} implementing the downstream reduction + * @param mapFactory + * a function which, when called, produces a new empty + * {@code Map} of the desired type + * @return a {@code Collector} implementing the cascaded group-by operation + * with given domain + * + * @see #groupingBy(Function, Set, Collector) + * @see #groupingByEnum(Class, Function, Collector) + * @since 0.4.0 + */ public static > Collector groupingBy( Function classifier, Set domain, Supplier mapFactory, Collector downstream) { From 887b4e3fa05acd634d553d88a30d63a01464a8f1 Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Sun, 11 Oct 2015 15:36:38 +0600 Subject: [PATCH 02/42] Support of short-circuit collectors with custom pool feature --- .../javax/util/streamex/AbstractStreamEx.java | 6 +- .../javax/util/streamex/StreamFactory.java | 4 +- .../javax/util/streamex/CustomPoolTest.java | 68 ++++++++++++------- 3 files changed, 52 insertions(+), 26 deletions(-) diff --git a/src/main/java/javax/util/streamex/AbstractStreamEx.java b/src/main/java/javax/util/streamex/AbstractStreamEx.java index 65b1436b..20a2e943 100644 --- a/src/main/java/javax/util/streamex/AbstractStreamEx.java +++ b/src/main/java/javax/util/streamex/AbstractStreamEx.java @@ -86,6 +86,10 @@ final > void addToMap(M map, K key, V val) { + oldVal + "' and '" + val + "')"); } } + + R rawCollect(Collector collector) { + return stream.collect(collector); + } abstract S supply(Stream stream); @@ -303,7 +307,7 @@ public R collect(Collector collector) { strategy().newStreamEx(StreamSupport.stream(spltr, true)).reduce(combiner).get()); } } - return stream.collect(collector); + return rawCollect(collector); } @Override diff --git a/src/main/java/javax/util/streamex/StreamFactory.java b/src/main/java/javax/util/streamex/StreamFactory.java index 1bf7d830..b19f9042 100644 --- a/src/main/java/javax/util/streamex/StreamFactory.java +++ b/src/main/java/javax/util/streamex/StreamFactory.java @@ -146,7 +146,7 @@ public R collect(Supplier supplier, BiConsumer> ac } @Override - public R collect(Collector, A, R> collector) { + R rawCollect(Collector, A, R> collector) { return strategy.terminate(collector, stream::collect); } @@ -231,7 +231,7 @@ public R collect(Supplier supplier, BiConsumer accumulator, } @Override - public R collect(Collector collector) { + R rawCollect(Collector collector) { return strategy.terminate(collector, stream::collect); } diff --git a/src/test/java/javax/util/streamex/CustomPoolTest.java b/src/test/java/javax/util/streamex/CustomPoolTest.java index 2dea4a28..201b3d82 100644 --- a/src/test/java/javax/util/streamex/CustomPoolTest.java +++ b/src/test/java/javax/util/streamex/CustomPoolTest.java @@ -22,9 +22,12 @@ import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Map.Entry; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.ForkJoinWorkerThread; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; import org.junit.Test; @@ -95,10 +98,8 @@ public void testStreamEx() { assertEquals(7, (int) StreamEx.of("aa", "bbb", "cccc").parallel(pool).peek(this::checkThread).filter(x -> x.length() > 2) .reduce(0, (x, s) -> x + s.length(), Integer::sum)); - assertEquals( - "aabbbcccc", - StreamEx.of("aa", "bbb", "cccc").parallel(pool) - .peek(this::checkThread).foldLeft("", String::concat)); + assertEquals("aabbbcccc", + StreamEx.of("aa", "bbb", "cccc").parallel(pool).peek(this::checkThread).foldLeft("", String::concat)); } @Test @@ -120,24 +121,29 @@ public void testEntryStream() { .count()); List res = new ArrayList<>(); EntryStream.of("a", 1, "b", 2, "c", 3).parallel(pool).peek(this::checkThread).filterValues(v -> v > 1) - .forEachOrdered(entry -> res.add(entry.getValue())); + .forEachOrdered(entry -> res.add(entry.getValue())); assertEquals(Arrays.asList(2, 3), res); - assertEquals( - 6, - (int) EntryStream - .of("a", 1, "b", 2, "c", 3) - .parallel(pool) - .peek(this::checkThread) - .reduce(0, (sum, e) -> sum + e.getValue(), Integer::sum)); - assertEquals(Arrays.asList(1, 2, 3), EntryStream.of("a", 1, "b", 2, "c", 3).parallel(pool).peek(this::checkThread) - .collect(ArrayList::new, (List list, Entry e) -> list.add(e.getValue()), List::addAll)); + assertEquals(2L, (long) EntryStream.of("a", 1, "b", 2, "c", 3).parallel(pool).peek(this::checkThread) + .filterValues(v -> v > 1).collect(Collectors.counting())); + assertEquals( + 6, + (int) EntryStream.of("a", 1, "b", 2, "c", 3).parallel(pool).peek(this::checkThread) + .reduce(0, (sum, e) -> sum + e.getValue(), Integer::sum)); + assertEquals( + Arrays.asList(1, 2, 3), + EntryStream + .of("a", 1, "b", 2, "c", 3) + .parallel(pool) + .peek(this::checkThread) + .collect(ArrayList::new, (List list, Entry e) -> list.add(e.getValue()), + List::addAll)); @SuppressWarnings("unchecked") Entry[] array = EntryStream.of("a", 1, "b", 2, "c", 3).parallel(pool).peek(this::checkThread) .filterValues(v -> v > 1).toArray(Entry[]::new); assertEquals(2, array.length); assertEquals(new SimpleEntry<>("b", 2), array[0]); assertEquals(new SimpleEntry<>("c", 3), array[1]); - + assertEquals( new SimpleEntry<>("abc", 6), EntryStream.of("a", 1, "b", 2, "c", 3).parallel(pool).peek(this::checkThread) @@ -176,9 +182,9 @@ public void testIntStreamEx() { assertEquals(2, IntStreamEx.of(1, 2, 3).parallel(pool).peek(this::checkThread).findFirst(x -> x % 2 == 0) .getAsInt()); List res = new ArrayList<>(); - IntStreamEx.of(1, 5, 10, Integer.MAX_VALUE).parallel(pool).peek(this::checkThread).map(x -> x*2) - .forEachOrdered(res::add); - assertEquals(Arrays.asList(2, 10, 20, Integer.MAX_VALUE*2), res); + IntStreamEx.of(1, 5, 10, Integer.MAX_VALUE).parallel(pool).peek(this::checkThread).map(x -> x * 2) + .forEachOrdered(res::add); + assertEquals(Arrays.asList(2, 10, 20, Integer.MAX_VALUE * 2), res); } @Test @@ -206,9 +212,9 @@ public void testLongStreamEx() { assertEquals(2, LongStreamEx.of(1, 2, 3).parallel(pool).peek(this::checkThread).findFirst(x -> x % 2 == 0) .getAsLong()); List res = new ArrayList<>(); - LongStreamEx.of(1, 5, 10, Integer.MAX_VALUE).parallel(pool).peek(this::checkThread).map(x -> x*2) - .forEachOrdered(res::add); - assertEquals(Arrays.asList(2L, 10L, 20L, Integer.MAX_VALUE*2L), res); + LongStreamEx.of(1, 5, 10, Integer.MAX_VALUE).parallel(pool).peek(this::checkThread).map(x -> x * 2) + .forEachOrdered(res::add); + assertEquals(Arrays.asList(2L, 10L, 20L, Integer.MAX_VALUE * 2L), res); } @Test @@ -239,8 +245,8 @@ public void testDoubleStreamEx() { assertEquals(2.0, DoubleStreamEx.of(1, 2, 3).parallel(pool).peek(this::checkThread).findFirst(x -> x % 2 == 0) .getAsDouble(), 0.0); List res = new ArrayList<>(); - DoubleStreamEx.of(1.0, 2.0, 3.5, 4.5).parallel(pool).peek(this::checkThread).map(x -> x*2) - .forEachOrdered(res::add); + DoubleStreamEx.of(1.0, 2.0, 3.5, 4.5).parallel(pool).peek(this::checkThread).map(x -> x * 2) + .forEachOrdered(res::add); assertEquals(Arrays.asList(2.0, 4.0, 7.0, 9.0), res); } @@ -257,4 +263,20 @@ public void testPairMap() { bits.clear(p.getKey()); }); } + + @Test + public void testShortCircuit() { + AtomicInteger counter = new AtomicInteger(0); + assertEquals( + Optional.empty(), + IntStreamEx.range(0, 10000).boxed().parallel(pool).peek(this::checkThread) + .peek(t -> counter.incrementAndGet()).collect(MoreCollectors.onlyOne())); + assertTrue(counter.get() < 10000); + counter.set(0); + assertEquals( + Optional.empty(), + IntStreamEx.range(0, 10000).boxed().mapToEntry(x -> x).parallel(pool).peek(this::checkThread) + .peek(t -> counter.incrementAndGet()).collect(MoreCollectors.onlyOne())); + assertTrue(counter.get() < 10000); + } } From 04f95e50d3dece4ad74f1428966b014518a774e2 Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Sun, 11 Oct 2015 15:50:24 +0600 Subject: [PATCH 03/42] grouping* now preserve order in parallel case, but not guarantee to return the concurrent map --- CHANGES.md | 3 +- .../java/javax/util/streamex/EntryStream.java | 6 +- .../java/javax/util/streamex/StreamEx.java | 26 ++------ .../javax/util/streamex/EntryStreamTest.java | 63 ++++++++++--------- .../javax/util/streamex/StreamExTest.java | 56 +++++------------ 5 files changed, 62 insertions(+), 92 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index cc4b33e7..771b2806 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -17,7 +17,8 @@ * Added `IntStreamEx/LongStreamEx.range/rangeClosed` methods with additional step parameter. * Added `IntStreamEx/LongStreamEx/DoubleStreamEx.foldLeft` methods. * Methods `StreamEx/EntryStream.toMap/toSortedMap/toCustomMap` without merge function now produce better exception message in the case of duplicate keys. -* Methods `StreamEx/EntryStream.toMap/toSortedMap/toCustomMap` accepting merge function do not return ConcurrentMap for parallel streams now (this caused incorrect merging for non-commutative merger functions). +* Methods `StreamEx/EntryStream.toMap/toSortedMap/toCustomMap` accepting merge function are not guaranteed to return ConcurrentMap for parallel streams now. They however guarantee now the correct merging order for non-commutative merger functions. +* Methods `StreamEx/EntryStream.grouping*` are not guaranteed to return the ConcurrentMap for parallel streams now. They however guarantee now the correct order of downstream collection. * Methods `StreamEx.ofEntries` are declared as deprecated and may be removed in future releases! * Deprecated methods `EntryStream.mapEntryKeys`/`mapEntryValues` are removed! * Updated documentation diff --git a/src/main/java/javax/util/streamex/EntryStream.java b/src/main/java/javax/util/streamex/EntryStream.java index dee62f72..a721f859 100644 --- a/src/main/java/javax/util/streamex/EntryStream.java +++ b/src/main/java/javax/util/streamex/EntryStream.java @@ -39,6 +39,7 @@ import java.util.function.Predicate; import java.util.function.Supplier; import java.util.stream.Collector; +import java.util.stream.Collector.Characteristics; import java.util.stream.Collectors; import java.util.stream.Stream; import java.util.stream.StreamSupport; @@ -1015,7 +1016,7 @@ public >> M grouping(Supplier mapSupplier) { public Map grouping(Collector downstream) { Function, K> keyMapper = Entry::getKey; Collector, ?, D> mapping = Collectors.mapping(Entry::getValue, downstream); - if (stream.isParallel()) { + if (stream.isParallel() && downstream.characteristics().contains(Characteristics.UNORDERED)) { return collect(Collectors.groupingByConcurrent(keyMapper, mapping)); } return collect(Collectors.groupingBy(keyMapper, mapping)); @@ -1025,7 +1026,8 @@ public Map grouping(Collector downstream) { public > M grouping(Supplier mapSupplier, Collector downstream) { Function, K> keyMapper = Entry::getKey; Collector, ?, D> mapping = Collectors.mapping(Entry::getValue, downstream); - if (stream.isParallel() && mapSupplier.get() instanceof ConcurrentMap) { + if (stream.isParallel() && downstream.characteristics().contains(Characteristics.UNORDERED) + && mapSupplier.get() instanceof ConcurrentMap) { return (M) collect(Collectors.groupingByConcurrent(keyMapper, (Supplier>) mapSupplier, mapping)); } diff --git a/src/main/java/javax/util/streamex/StreamEx.java b/src/main/java/javax/util/streamex/StreamEx.java index 41e553ef..b1ee2b45 100644 --- a/src/main/java/javax/util/streamex/StreamEx.java +++ b/src/main/java/javax/util/streamex/StreamEx.java @@ -54,6 +54,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import java.util.stream.StreamSupport; +import java.util.stream.Collector.Characteristics; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; @@ -293,10 +294,6 @@ public EntryStream cross(Function - * For parallel stream concurrent collector is used and ConcurrentMap is - * returned. - * - *

* This is a terminal * operation. * @@ -325,10 +322,6 @@ public Map> groupingBy(Function classifie * {@code Map} objects returned. * *

- * For parallel stream concurrent collector is used and ConcurrentMap is - * returned. - * - *

* This is a terminal * operation. * @@ -348,7 +341,7 @@ public Map> groupingBy(Function classifie */ public Map groupingBy(Function classifier, Collector downstream) { - if (stream.isParallel()) + if (stream.isParallel() && downstream.characteristics().contains(Characteristics.UNORDERED)) return collect(Collectors.groupingByConcurrent(classifier, downstream)); return collect(Collectors.groupingBy(classifier, downstream)); } @@ -363,10 +356,6 @@ public Map groupingBy(Function classifier, * The {@code Map} will be created using the provided factory function. * *

- * If the stream is parallel and map factory produces a - * {@link ConcurrentMap} then concurrent collector is used. - * - *

* This is a terminal * operation. * @@ -392,7 +381,8 @@ public Map groupingBy(Function classifier, @SuppressWarnings("unchecked") public > M groupingBy(Function classifier, Supplier mapFactory, Collector downstream) { - if (stream.isParallel() && mapFactory.get() instanceof ConcurrentMap) + if (stream.isParallel() && downstream.characteristics().contains(Characteristics.UNORDERED) + && mapFactory.get() instanceof ConcurrentMap) return (M) collect(Collectors.groupingByConcurrent(classifier, (Supplier>) mapFactory, downstream)); return collect(Collectors.groupingBy(classifier, mapFactory, downstream)); @@ -409,10 +399,6 @@ public > M groupingBy(Function * {@code Map} objects returned. * *

- * For parallel stream concurrent collector is used and ConcurrentMap is - * returned. - * - *

* This is a terminal * operation. * @@ -448,10 +434,6 @@ public > Map groupingTo(Function - * If the stream is parallel and map factory produces a - * {@link ConcurrentMap} then concurrent collector is used. - * - *

* This is a terminal * operation. * diff --git a/src/test/java/javax/util/streamex/EntryStreamTest.java b/src/test/java/javax/util/streamex/EntryStreamTest.java index dba242ba..172e4ea0 100644 --- a/src/test/java/javax/util/streamex/EntryStreamTest.java +++ b/src/test/java/javax/util/streamex/EntryStreamTest.java @@ -24,11 +24,11 @@ import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Random; import java.util.Map.Entry; -import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; import java.util.concurrent.ConcurrentMap; @@ -231,7 +231,8 @@ public void testToMap() { TreeMap result = EntryStream.of(createMap()).toCustomMap(TreeMap::new); assertEquals(createMap(), result); - Supplier> s = () -> StreamEx.of("aaa", "bb", "dd").mapToEntry(String::length, Function.identity()); + Supplier> s = () -> StreamEx.of("aaa", "bb", "dd").mapToEntry(String::length, + Function.identity()); Map expected = new HashMap<>(); expected.put(3, "aaa"); expected.put(2, "bbdd"); @@ -311,10 +312,14 @@ public void testGrouping() { Map> expected = new LinkedHashMap<>(); expected.put("a", Arrays.asList(1, 2)); expected.put("b", Arrays.asList(3, 4)); - Map> result = EntryStream.of(data).mapKeys(k -> k.substring(0, 1)).grouping(); + Supplier> s = () -> EntryStream.of(data).mapKeys(k -> k.substring(0, 1)); + Map> result = s.get().grouping(); assertEquals(expected, result); - TreeMap> resultTree = EntryStream.of(data).mapKeys(k -> k.substring(0, 1)) - .grouping(TreeMap::new); + TreeMap> resultTree = s.get().grouping(TreeMap::new); + assertEquals(expected, resultTree); + result = s.get().parallel().grouping(); + assertEquals(expected, result); + resultTree = s.get().parallel().grouping(TreeMap::new); assertEquals(expected, resultTree); } @@ -324,29 +329,26 @@ public void testGroupingTo() { data.put("ab", 1); data.put("ac", 2); data.put("ba", 3); - data.put("bc", 3); - Map> expected = new LinkedHashMap<>(); - expected.put("a", new HashSet<>(Arrays.asList(1, 2))); - expected.put("b", Collections.singleton(3)); - Map> result = EntryStream.of(data).mapKeys(k -> k.substring(0, 1)) - .groupingTo(HashSet::new); + data.put("bc", 4); + Map> expected = new LinkedHashMap<>(); + expected.put("a", Arrays.asList(1, 2)); + expected.put("b", Arrays.asList(3, 4)); + Supplier> s = () -> EntryStream.of(data).mapKeys(k -> k.substring(0, 1)); + Map> result = s.get().groupingTo(LinkedList::new); assertEquals(expected, result); - assertFalse(result instanceof ConcurrentMap); - result = EntryStream.of(data).mapKeys(k -> k.substring(0, 1)).parallel().groupingTo(HashSet::new); + assertTrue(result.get("a") instanceof LinkedList); + result = s.get().parallel().groupingTo(LinkedList::new); assertEquals(expected, result); - assertTrue(result instanceof ConcurrentMap); - SortedMap> resultTree = EntryStream.of(data).mapKeys(k -> k.substring(0, 1)) - .groupingTo(TreeMap::new, HashSet::new); + assertTrue(result.get("a") instanceof LinkedList); + SortedMap> resultTree = s.get().groupingTo(TreeMap::new, LinkedList::new); + assertTrue(result.get("a") instanceof LinkedList); assertEquals(expected, resultTree); - assertFalse(resultTree instanceof ConcurrentMap); - resultTree = EntryStream.of(data).mapKeys(k -> k.substring(0, 1)).parallel() - .groupingTo(TreeMap::new, HashSet::new); + resultTree = s.get().parallel().groupingTo(TreeMap::new, LinkedList::new); + assertTrue(result.get("a") instanceof LinkedList); assertEquals(expected, resultTree); - assertFalse(resultTree instanceof ConcurrentMap); - resultTree = EntryStream.of(data).mapKeys(k -> k.substring(0, 1)).parallel() - .groupingTo(ConcurrentSkipListMap::new, HashSet::new); + resultTree = s.get().parallel().groupingTo(ConcurrentSkipListMap::new, LinkedList::new); + assertTrue(result.get("a") instanceof LinkedList); assertEquals(expected, resultTree); - assertTrue(resultTree instanceof ConcurrentMap); } @Test @@ -442,12 +444,17 @@ private Map createMap() { @Test public void testOfPairs() { Random r = new Random(1); - Point[] pts = StreamEx.generate(() -> new Point(r.nextDouble(), r.nextDouble())).limit(100).toArray(Point[]::new); - double expected = StreamEx.of(pts).cross(pts).mapKeyValue(Point::distance).mapToDouble(Double::doubleValue).max().getAsDouble(); - assertEquals(expected, EntryStream.ofPairs(pts).mapKeyValue(Point::distance).mapToDouble(Double::doubleValue).max().getAsDouble(), 0.0); - assertEquals(expected, EntryStream.ofPairs(pts).parallel().mapKeyValue(Point::distance).mapToDouble(Double::doubleValue).max().getAsDouble(), 0.0); + Point[] pts = StreamEx.generate(() -> new Point(r.nextDouble(), r.nextDouble())).limit(100) + .toArray(Point[]::new); + double expected = StreamEx.of(pts).cross(pts).mapKeyValue(Point::distance).mapToDouble(Double::doubleValue) + .max().getAsDouble(); + assertEquals(expected, EntryStream.ofPairs(pts).mapKeyValue(Point::distance).mapToDouble(Double::doubleValue) + .max().getAsDouble(), 0.0); + assertEquals(expected, + EntryStream.ofPairs(pts).parallel().mapKeyValue(Point::distance).mapToDouble(Double::doubleValue).max() + .getAsDouble(), 0.0); } - + @Test public void testDistinctKeysValues() { Supplier> s = () -> EntryStream.of(1, "a", 1, "b", 2, "b").append(2, "c", 1, "c", diff --git a/src/test/java/javax/util/streamex/StreamExTest.java b/src/test/java/javax/util/streamex/StreamExTest.java index 5a44eaab..a6fef05b 100644 --- a/src/test/java/javax/util/streamex/StreamExTest.java +++ b/src/test/java/javax/util/streamex/StreamExTest.java @@ -279,50 +279,28 @@ public void testToSortedMap() { public void testGroupingBy() { Map> expected = new HashMap<>(); expected.put(1, Arrays.asList("a")); - expected.put(2, Arrays.asList("bb", "bb")); + expected.put(2, Arrays.asList("bb", "dd")); expected.put(3, Arrays.asList("ccc")); - Map> seqMap = StreamEx.of("a", "bb", "bb", "ccc").groupingBy(String::length); - Map> parallelMap = StreamEx.of("a", "bb", "bb", "ccc").parallel() - .groupingBy(String::length); - Map> mapLinkedList = StreamEx.of("a", "bb", "bb", "ccc").parallel() - .groupingTo(String::length, LinkedList::new); - assertEquals(expected, seqMap); - assertEquals(expected, parallelMap); - assertEquals(expected, mapLinkedList); - assertFalse(seqMap instanceof ConcurrentMap); - assertTrue(parallelMap instanceof ConcurrentMap); - assertTrue(mapLinkedList instanceof ConcurrentMap); - assertTrue(mapLinkedList.get(1) instanceof LinkedList); Map> expectedMapSet = new HashMap<>(); expectedMapSet.put(1, new HashSet<>(Arrays.asList("a"))); - expectedMapSet.put(2, new HashSet<>(Arrays.asList("bb", "bb"))); + expectedMapSet.put(2, new HashSet<>(Arrays.asList("bb", "dd"))); expectedMapSet.put(3, new HashSet<>(Arrays.asList("ccc"))); - Map> seqMapSet = StreamEx.of("a", "bb", "bb", "ccc").groupingBy(String::length, - Collectors.toSet()); - Map> parallelMapSet = StreamEx.of("a", "bb", "bb", "ccc").parallel() - .groupingBy(String::length, Collectors.toSet()); - assertEquals(expectedMapSet, seqMapSet); - assertEquals(expectedMapSet, parallelMapSet); - assertFalse(seqMapSet instanceof ConcurrentMap); - assertTrue(parallelMapSet instanceof ConcurrentMap); - - seqMapSet = StreamEx.of("a", "bb", "bb", "ccc").groupingBy(String::length, HashMap::new, Collectors.toSet()); - assertEquals(expectedMapSet, seqMapSet); - assertFalse(seqMapSet instanceof ConcurrentMap); - seqMapSet = StreamEx.of("a", "bb", "bb", "ccc").parallel() - .groupingBy(String::length, HashMap::new, Collectors.toSet()); - assertEquals(expectedMapSet, seqMapSet); - assertFalse(seqMapSet instanceof ConcurrentMap); - parallelMapSet = StreamEx.of("a", "bb", "bb", "ccc").parallel() - .groupingBy(String::length, ConcurrentHashMap::new, Collectors.toSet()); - assertEquals(expectedMapSet, parallelMapSet); - assertTrue(parallelMapSet instanceof ConcurrentMap); - parallelMapSet = StreamEx.of("a", "bb", "bb", "ccc").parallel() - .groupingTo(String::length, ConcurrentHashMap::new, TreeSet::new); - assertEquals(expectedMapSet, parallelMapSet); - assertTrue(parallelMapSet instanceof ConcurrentMap); - assertTrue(parallelMapSet.get(1) instanceof TreeSet); + + for(StreamExSupplier supplier : streamEx(() -> StreamEx.of("a", "bb", "dd", "ccc"))) { + assertEquals(supplier.toString(), expected, supplier.get().groupingBy(String::length)); + Map> map = supplier.get().groupingTo(String::length, LinkedList::new); + assertEquals(supplier.toString(), expected, map); + assertTrue(map.get(1) instanceof LinkedList); + assertEquals(supplier.toString(), expectedMapSet, supplier.get().groupingBy(String::length, Collectors.toSet())); + assertEquals(supplier.toString(), expectedMapSet, + supplier.get().groupingBy(String::length, HashMap::new, Collectors.toSet())); + ConcurrentHashMap> chm = supplier.get().groupingBy(String::length, + ConcurrentHashMap::new, Collectors.toSet()); + assertEquals(supplier.toString(), expectedMapSet, chm); + chm = supplier.get().groupingTo(String::length, ConcurrentHashMap::new, TreeSet::new); + assertTrue(chm.get(1) instanceof TreeSet); + } } @Test From 9c3a1f55761664a62bd722b54aaaa0de152e1a72 Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Sun, 11 Oct 2015 16:05:29 +0600 Subject: [PATCH 04/42] intSum, longSum: unordered --- src/main/java/javax/util/streamex/StreamExInternals.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/javax/util/streamex/StreamExInternals.java b/src/main/java/javax/util/streamex/StreamExInternals.java index 5b489084..0ef29b93 100644 --- a/src/main/java/javax/util/streamex/StreamExInternals.java +++ b/src/main/java/javax/util/streamex/StreamExInternals.java @@ -496,12 +496,12 @@ CancellableCollector asCancellable(BiConsumer accumulator, Pr static PartialCollector intSum() { return new PartialCollector<>(() -> new int[1], (box1, box2) -> box1[0] += box2[0], UNBOX_INT, - NO_CHARACTERISTICS); + UNORDERED_CHARACTERISTICS); } static PartialCollector longSum() { return new PartialCollector<>(() -> new long[1], (box1, box2) -> box1[0] += box2[0], UNBOX_LONG, - NO_CHARACTERISTICS); + UNORDERED_CHARACTERISTICS); } static PartialCollector, boolean[]> booleanArray() { From 3fe326f018ac8b4accb54949de39af0445dd7da4 Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Sun, 11 Oct 2015 16:06:02 +0600 Subject: [PATCH 05/42] More tests --- .../javax/util/streamex/EntryStreamTest.java | 22 +++++++++++++++++++ .../javax/util/streamex/StreamExTest.java | 6 +++++ 2 files changed, 28 insertions(+) diff --git a/src/test/java/javax/util/streamex/EntryStreamTest.java b/src/test/java/javax/util/streamex/EntryStreamTest.java index 172e4ea0..c2c9b802 100644 --- a/src/test/java/javax/util/streamex/EntryStreamTest.java +++ b/src/test/java/javax/util/streamex/EntryStreamTest.java @@ -40,6 +40,8 @@ import javax.util.streamex.StreamExTest.Point; +import static javax.util.streamex.TestHelpers.*; + import org.junit.Test; public class EntryStreamTest { @@ -72,6 +74,16 @@ public void testCreate() { assertEquals(Collections.singletonMap("aaa", 3), EntryStream.of(Collections.singletonMap("aaa", 3).entrySet().spliterator()).toMap()); } + + @Test + public void testSequential() { + EntryStream stream = EntryStream.of(createMap()); + assertFalse(stream.isParallel()); + stream = stream.parallel(); + assertTrue(stream.isParallel()); + stream = stream.sequential(); + assertFalse(stream.isParallel()); + } @Test public void testZip() { @@ -230,6 +242,8 @@ public void testPrepend() { public void testToMap() { TreeMap result = EntryStream.of(createMap()).toCustomMap(TreeMap::new); assertEquals(createMap(), result); + result = EntryStream.of(createMap()).parallel().toCustomMap(TreeMap::new); + assertEquals(createMap(), result); Supplier> s = () -> StreamEx.of("aaa", "bb", "dd").mapToEntry(String::length, Function.identity()); @@ -321,6 +335,14 @@ public void testGrouping() { assertEquals(expected, result); resultTree = s.get().parallel().grouping(TreeMap::new); assertEquals(expected, resultTree); + + for (StreamExSupplier supplier : streamEx(() -> IntStreamEx.range(1000).boxed())) { + assertEquals(supplier.toString(), EntryStream.of(0, 500, 1, 500).toMap(), + supplier.get().mapToEntry(i -> i / 500, i -> i).grouping(MoreCollectors.countingInt())); + ConcurrentSkipListMap map = supplier.get().mapToEntry(i -> i / 500, i -> i) + .grouping(ConcurrentSkipListMap::new, MoreCollectors.countingInt()); + assertEquals(supplier.toString(), EntryStream.of(0, 500, 1, 500).toMap(), map); + } } @Test diff --git a/src/test/java/javax/util/streamex/StreamExTest.java b/src/test/java/javax/util/streamex/StreamExTest.java index a6fef05b..0516c197 100644 --- a/src/test/java/javax/util/streamex/StreamExTest.java +++ b/src/test/java/javax/util/streamex/StreamExTest.java @@ -367,6 +367,12 @@ public void testAppend() { List list = Arrays.asList(1, 2, 3, 4); assertEquals(Arrays.asList(1.0, 2, 3L, 1, 2, 3, 4), StreamEx.of(1.0, 2, 3L).append(list).toList()); + + StreamEx s = StreamEx.of(1, 2, 3); + assertSame(s, s.append()); + assertSame(s, s.append(Collections.emptyList())); + assertSame(s, s.prepend()); + assertSame(s, s.prepend(Collections.emptyList())); } @Test From bebeede00c7ec9c7749fd61520cfc62c5d384811 Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Sun, 11 Oct 2015 16:39:40 +0600 Subject: [PATCH 06/42] MoreCollectors: documentation update --- .../javax/util/streamex/MoreCollectors.java | 53 ++++++++++++++----- 1 file changed, 39 insertions(+), 14 deletions(-) diff --git a/src/main/java/javax/util/streamex/MoreCollectors.java b/src/main/java/javax/util/streamex/MoreCollectors.java index 68699392..63d169f7 100644 --- a/src/main/java/javax/util/streamex/MoreCollectors.java +++ b/src/main/java/javax/util/streamex/MoreCollectors.java @@ -506,9 +506,9 @@ private MoreCollectors() { * * @param * the type of the input elements - * @return a collector which returns an {@link Optional} which describes the - * only element of the stream. For empty stream or stream containing - * more than one element an empty {@code Optional} is returned. + * @return a collector which returns an {@link Optional} describing the only + * element of the stream. For empty stream or stream containing more + * than one element an empty {@code Optional} is returned. * @since 0.4.0 */ public static Collector> onlyOne() { @@ -571,6 +571,10 @@ private MoreCollectors() { * collector. * *

+ * There are no guarantees on the type, mutability, serializability, or + * thread-safety of the {@code List} returned. + * + *

* The operation performed by the returned collector is equivalent to * {@code stream.limit(n).collect(Collectors.toList())}. This collector is * mostly useful as a downstream collector. @@ -599,6 +603,10 @@ private MoreCollectors() { * the last stream elements into the {@link List}. * *

+ * There are no guarantees on the type, mutability, serializability, or + * thread-safety of the {@code List} returned. + * + *

* When supplied {@code n} is less or equal to zero, this method returns a * short-circuiting * collector which ignores the input and produces an empty list. @@ -638,6 +646,10 @@ private MoreCollectors() { * {@code n} is much less than the stream size. * *

+ * There are no guarantees on the type, mutability, serializability, or + * thread-safety of the {@code List} returned. + * + *

* When supplied {@code n} is less or equal to zero, this method returns a * short-circuiting * collector which ignores the input and produces an empty list. @@ -687,6 +699,10 @@ else if (comparator.compare(queue.peek(), t) < 0) { * {@code n} is much less than the stream size. * *

+ * There are no guarantees on the type, mutability, serializability, or + * thread-safety of the {@code List} returned. + * + *

* When supplied {@code n} is less or equal to zero, this method returns a * short-circuiting * collector which ignores the input and produces an empty list. @@ -715,6 +731,10 @@ else if (comparator.compare(queue.peek(), t) < 0) { * is much less than the stream size. * *

+ * There are no guarantees on the type, mutability, serializability, or + * thread-safety of the {@code List} returned. + * + *

* When supplied {@code n} is less or equal to zero, this method returns a * short-circuiting * collector which ignores the input and produces an empty list. @@ -745,6 +765,10 @@ else if (comparator.compare(queue.peek(), t) < 0) { * less than the stream size. * *

+ * There are no guarantees on the type, mutability, serializability, or + * thread-safety of the {@code List} returned. + * + *

* When supplied {@code n} is less or equal to zero, this method returns a * short-circuiting * collector which ignores the input and produces an empty list. @@ -899,18 +923,19 @@ class Container { * values associated with a given key using the specified downstream * {@code Collector}. * - *

There are no guarantees on the type, mutability, - * serializability, or thread-safety of the {@code Map} returned. + *

+ * There are no guarantees on the type, mutability, serializability, or + * thread-safety of the {@code Map} returned. * *

* The main difference of this collector from - * {@link Collectors#groupingBy(Function, Supplier, Collector)} is that it - * accepts additional domain parameter which is the {@code Set} of all - * possible map keys. If the mapper function produces the key out of domain, - * an {@code IllegalStateException} will occur. If the mapper function does - * not produce some of domain keys at all, they are also added to the - * result. These keys are mapped to the default collector value which is - * equivalent to collecting an empty stream with the same collector. + * {@link Collectors#groupingBy(Function, Collector)} is that it accepts + * additional domain parameter which is the {@code Set} of all possible map + * keys. If the mapper function produces the key out of domain, an + * {@code IllegalStateException} will occur. If the mapper function does not + * produce some of domain keys at all, they are also added to the result. + * These keys are mapped to the default collector value which is equivalent + * to collecting an empty stream with the same collector. * *

* This method returns a Collector collectingAndThen(Collector Collector collectingAndThen(Collector Date: Sun, 11 Oct 2015 16:47:36 +0600 Subject: [PATCH 07/42] Release 0.4.0 --- README.md | 4 ++-- pom.xml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f2b0f6a5..b3e62572 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# StreamEx +# StreamEx 0.4.0 Enhancing Java 8 Streams. This library defines four classes: `StreamEx`, `IntStreamEx`, `LongStreamEx`, `DoubleStreamEx` @@ -115,7 +115,7 @@ To use from maven add this snippet to the pom.xml `dependencies` section: io.github.amaembo streamex - 0.3.8 + 0.4.0 ``` diff --git a/pom.xml b/pom.xml index 8ed0af63..7f03877b 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ io.github.amaembo streamex - 0.4.0-SNAPSHOT + 0.4.0 jar StreamEx From 4b28ac95b0bf763ef9587649e552ea044269f295 Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Sun, 11 Oct 2015 16:48:53 +0600 Subject: [PATCH 08/42] pom.xml: 0.4.1 development started --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 7f03877b..2a0167fc 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ io.github.amaembo streamex - 0.4.0 + 0.4.1-SNAPSHOT jar StreamEx From b9c3fab0766e01cd82b853f3a96d9f95ad0a6fbc Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Sun, 11 Oct 2015 17:08:31 +0600 Subject: [PATCH 09/42] StreamEx.ofEntries minor javadoc fix --- src/main/java/javax/util/streamex/StreamEx.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/javax/util/streamex/StreamEx.java b/src/main/java/javax/util/streamex/StreamEx.java index b1ee2b45..0f9e5a7a 100644 --- a/src/main/java/javax/util/streamex/StreamEx.java +++ b/src/main/java/javax/util/streamex/StreamEx.java @@ -1648,7 +1648,7 @@ public static StreamEx ofValues(Map map, Predicate keyFilter) * @return an ordered {@code StreamEx} of entries in given zip file * @throws IllegalStateException * if the zip file has been closed - * @deprecated Use {@code StreamEx.of(file.stream())} + * @deprecated Use {@code StreamEx.of(file.entries())} */ @Deprecated public static StreamEx ofEntries(ZipFile file) { @@ -1665,7 +1665,7 @@ public static StreamEx ofEntries(ZipFile file) { * @return an ordered {@code StreamEx} of entries in given jar file * @throws IllegalStateException * if the jar file has been closed - * @deprecated Use {@code StreamEx.of(file.stream())} + * @deprecated Use {@code StreamEx.of(file.entries())} */ @Deprecated public static StreamEx ofEntries(JarFile file) { From e630b746597ca773d5a299807a583b8797f83fb4 Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Mon, 12 Oct 2015 10:18:45 +0600 Subject: [PATCH 10/42] OrderedCancellableSpliterator2 with combiner (tests passed) --- .../javax/util/streamex/AbstractStreamEx.java | 39 +++- .../OrderedCancellableSpliterator2.java | 180 ++++++++++++++++++ .../OrderedCancellableSpliteratorTest.java | 52 +++++ 3 files changed, 270 insertions(+), 1 deletion(-) create mode 100644 src/main/java/javax/util/streamex/OrderedCancellableSpliterator2.java create mode 100644 src/test/java/javax/util/streamex/OrderedCancellableSpliteratorTest.java diff --git a/src/main/java/javax/util/streamex/AbstractStreamEx.java b/src/main/java/javax/util/streamex/AbstractStreamEx.java index 20a2e943..11850e8d 100644 --- a/src/main/java/javax/util/streamex/AbstractStreamEx.java +++ b/src/main/java/javax/util/streamex/AbstractStreamEx.java @@ -300,6 +300,43 @@ public R collect(Collector collector) { if (!spliterator.hasCharacteristics(Spliterator.ORDERED) || c.characteristics().contains(Characteristics.UNORDERED)) { spltr = new UnorderedCancellableSpliterator<>(spliterator, c.supplier(), acc, combiner, finished); + } else { + spltr = new OrderedCancellableSpliterator2<>(spliterator, c.supplier(), acc, combiner, finished); + } + return c.finisher().apply(strategy().newStreamEx(StreamSupport.stream(spltr, true)).findFirst().get()); + } + return rawCollect(collector); + } + + public R collectOld(Collector collector) { + if (collector instanceof CancellableCollector) { + CancellableCollector c = (CancellableCollector) collector; + BiConsumer acc = c.accumulator(); + Predicate finished = c.finished(); + BinaryOperator combiner = c.combiner(); + Spliterator spliterator = stream.spliterator(); + if (!isParallel()) { + A a = c.supplier().get(); + if (!finished.test(a)) { + try { + // forEachRemaining can be much faster + // and take much less memory than tryAdvance for certain + // spliterators + spliterator.forEachRemaining(e -> { + acc.accept(a, e); + if (finished.test(a)) + throw new CancelException(); + }); + } catch (CancelException ex) { + // ignore + } + } + return c.finisher().apply(a); + } + Spliterator spltr; + if (!spliterator.hasCharacteristics(Spliterator.ORDERED) + || c.characteristics().contains(Characteristics.UNORDERED)) { + spltr = new UnorderedCancellableSpliterator<>(spliterator, c.supplier(), acc, combiner, finished); return c.finisher().apply(strategy().newStreamEx(StreamSupport.stream(spltr, true)).findAny().get()); } else { spltr = new OrderedCancellableSpliterator<>(spliterator, c.supplier(), acc, finished); @@ -309,7 +346,7 @@ public R collect(Collector collector) { } return rawCollect(collector); } - + @Override public Optional min(Comparator comparator) { return reduce(BinaryOperator.minBy(comparator)); diff --git a/src/main/java/javax/util/streamex/OrderedCancellableSpliterator2.java b/src/main/java/javax/util/streamex/OrderedCancellableSpliterator2.java new file mode 100644 index 00000000..ae71a03a --- /dev/null +++ b/src/main/java/javax/util/streamex/OrderedCancellableSpliterator2.java @@ -0,0 +1,180 @@ +/* + * Copyright 2015 Tagir Valeev + * + * 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 javax.util.streamex; + +import java.util.ArrayDeque; +import java.util.Spliterator; +import java.util.function.BiConsumer; +import java.util.function.BinaryOperator; +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.function.Supplier; + +import static javax.util.streamex.StreamExInternals.*; + +/** + * @author Tagir Valeev + */ +/* package */final class OrderedCancellableSpliterator2 implements Spliterator, Cloneable { + private Spliterator source; + private final Object lock = new Object(); + private final BiConsumer accumulator; + private final Predicate cancelPredicate; + private final BinaryOperator combiner; + private final Supplier supplier; + private volatile boolean localCancelled; + private OrderedCancellableSpliterator2 prefix; + private OrderedCancellableSpliterator2 suffix; + private A payload; + + OrderedCancellableSpliterator2(Spliterator source, Supplier supplier, BiConsumer accumulator, + BinaryOperator combiner, Predicate cancelPredicate) { + this.source = source; + this.supplier = supplier; + this.accumulator = accumulator; + this.combiner = combiner; + this.cancelPredicate = cancelPredicate; + } + + @Override + public boolean tryAdvance(Consumer action) { + Spliterator source = this.source; + if (source == null || localCancelled) { + this.source = null; + return false; + } + A acc = supplier.get(); + try { + source.forEachRemaining(t -> { + accumulator.accept(acc, t); + if (cancelPredicate.test(acc)) { + cancelSuffix(); + throw new CancelException(); + } + if (localCancelled) { + throw new CancelException(); + } + }); + } + catch(CancelException ex) { + if(localCancelled) { + return false; + } + localCancelled = true; + } + this.source = null; + A result = acc; + while(true) { + ArrayDeque res = new ArrayDeque<>(); + res.offer(result); + synchronized (lock) { + OrderedCancellableSpliterator2 s = prefix; + while(s != null) { + if(s.payload == null) break; + res.offerFirst(s.payload); + s = s.prefix; + } + prefix = s; + if(s != null) { + s.suffix = this; + } + s = suffix; + while(s != null) { + if(s.payload == null) break; + res.offerLast(s.payload); + s = s.suffix; + } + suffix = s; + if(s != null) { + s.prefix = this; + } + if(res.size() == 1) { + if(prefix == null && suffix == null) { + action.accept(result); + return true; + } + this.payload = result; + break; + } + } + result = res.pollFirst(); + while(!res.isEmpty()) { + result = combiner.apply(result, res.pollFirst()); + if(cancelPredicate.test(result)) { + cancelSuffix(); + localCancelled = true; + } + } + } + return false; + } + + private void cancelSuffix() { + if(this.suffix == null) + return; + synchronized (lock) { + OrderedCancellableSpliterator2 suffix = this.suffix; + while (suffix != null && !suffix.localCancelled) { + suffix.prefix = null; + suffix.localCancelled = true; + suffix = suffix.suffix; + } + this.suffix = null; + } + } + + @Override + public void forEachRemaining(Consumer action) { + tryAdvance(action); + } + + @Override + public Spliterator trySplit() { + if (source == null || localCancelled) { + source = null; + return null; + } + Spliterator prefix = source.trySplit(); + if (prefix == null) { + return null; + } + try { + synchronized(lock) { + @SuppressWarnings("unchecked") + OrderedCancellableSpliterator2 result = (OrderedCancellableSpliterator2) this.clone(); + result.source = prefix; + this.prefix = result; + result.suffix = this; + OrderedCancellableSpliterator2 prefixPrefix = result.prefix; + if (prefixPrefix != null) + prefixPrefix.suffix = result; + return result; + } + } catch (CloneNotSupportedException e) { + throw new InternalError(); + } + } + + @Override + public long estimateSize() { + return source == null ? 0 : source.estimateSize(); + } + + @Override + public int characteristics() { + return source == null ? SIZED : ORDERED; + } +} diff --git a/src/test/java/javax/util/streamex/OrderedCancellableSpliteratorTest.java b/src/test/java/javax/util/streamex/OrderedCancellableSpliteratorTest.java new file mode 100644 index 00000000..650241ab --- /dev/null +++ b/src/test/java/javax/util/streamex/OrderedCancellableSpliteratorTest.java @@ -0,0 +1,52 @@ +/* + * Copyright 2015 Tagir Valeev + * + * 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 javax.util.streamex; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.function.BiConsumer; +import java.util.function.BinaryOperator; +import java.util.function.Predicate; +import java.util.function.Supplier; + +import static javax.util.streamex.TestHelpers.*; + +import org.junit.Test; + +/** + * @author Tagir Valeev + */ +public class OrderedCancellableSpliteratorTest { + @Test + public void testSpliterator() { + int limit = 10; + Supplier> s = ArrayList::new; + BiConsumer, Integer> a = (acc, t) -> { + if(acc.size() < limit) acc.add(t); + }; + BinaryOperator> c = (a1, a2) -> { + a2.forEach(t -> a.accept(a1, t)); + return a1; + }; + Predicate> p = acc -> acc.size() >= limit; + List input = IntStreamEx.range(30).boxed().toList(); + List expected = IntStreamEx.range(limit).boxed().toList(); + checkSpliterator("head-short-circuit", Collections.singletonList(expected), + () -> new OrderedCancellableSpliterator2<>( + input.spliterator(), s, a, c, p)); + } +} From 272d9ca2953f1079b473fe3f4b2d911d94af491f Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Mon, 12 Oct 2015 10:50:45 +0600 Subject: [PATCH 11/42] MoreCollectorsTest:testFirstLast/testHeadTail: more tests with unlimited parallel streams --- src/test/java/javax/util/streamex/MoreCollectorsTest.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/test/java/javax/util/streamex/MoreCollectorsTest.java b/src/test/java/javax/util/streamex/MoreCollectorsTest.java index 2f17c298..ecb390d6 100644 --- a/src/test/java/javax/util/streamex/MoreCollectorsTest.java +++ b/src/test/java/javax/util/streamex/MoreCollectorsTest.java @@ -170,10 +170,7 @@ public void testFirstLast() { checkShortCircuitCollector("first", Optional.of(0), 1, s, MoreCollectors.first()); checkShortCircuitCollector("firstLong", Optional.of(0), 1, () -> Stream.of(1).flatMap(x -> IntStream.range(0, 1000000000).boxed()), MoreCollectors.first(), true); - // TODO: such test is failed now. Must be supported when - // OrderedCancellableSpliterator will be rewritten - // checkShortCircuitCollector("first", Optional.of(1), 1, () -> - // Stream.iterate(1, x -> x + 1), MoreCollectors.first()); + checkShortCircuitCollector("first", Optional.of(1), 1, () -> Stream.iterate(1, x -> x + 1), MoreCollectors.first(), true); assertEquals(1, (int) StreamEx.iterate(1, x -> x + 1).parallel().collect(MoreCollectors.first()).get()); checkCollector("last", Optional.of(999), s, MoreCollectors.last()); @@ -208,6 +205,9 @@ public void testHeadTail() { checkShortCircuitCollector("head(999)", ints.subList(0, 999), 999, ints::stream, MoreCollectors.head(999)); checkShortCircuitCollector("head(1000)", ints, 1000, ints::stream, MoreCollectors.head(1000)); checkShortCircuitCollector("head(MAX)", ints, 1000, ints::stream, MoreCollectors.head(Integer.MAX_VALUE)); + + checkShortCircuitCollector("head(10000)", IntStreamEx.rangeClosed(1, 10000).boxed().toList(), 10000, + () -> Stream.iterate(1, x -> x + 1), MoreCollectors.head(10000), true); } @Test From d2a7061fa9188563d3b603d3d178cb15fed4e127 Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Mon, 12 Oct 2015 14:23:09 +0600 Subject: [PATCH 12/42] MoreCollectorsTest: testHeadTail: unstable test added (fails with ConcurrentModificationException sometimes) --- src/test/java/javax/util/streamex/MoreCollectorsTest.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/test/java/javax/util/streamex/MoreCollectorsTest.java b/src/test/java/javax/util/streamex/MoreCollectorsTest.java index ecb390d6..0760fd36 100644 --- a/src/test/java/javax/util/streamex/MoreCollectorsTest.java +++ b/src/test/java/javax/util/streamex/MoreCollectorsTest.java @@ -180,9 +180,11 @@ public void testFirstLast() { @Test public void testHeadTailParallel() { - for (int i = 0; i < 100; i++) { + for (int i = 0; i < 1000; i++) { assertEquals("#" + i, Arrays.asList(0, 1), IntStreamEx.range(1000).boxed().parallel().collect(MoreCollectors.head(2))); + assertEquals("#" + i, IntStreamEx.range(0, 2000, 2).boxed().toList(), IntStreamEx.range(10000).boxed() + .parallel().filter(x -> x % 2 == 0).collect(MoreCollectors.head(1000))); } assertEquals(Arrays.asList(0, 1), StreamEx.iterate(0, x -> x + 1).parallel().collect(MoreCollectors.head(2))); } From 5d37219933d569055ec43f20720a37e3a9274aae Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Mon, 12 Oct 2015 18:31:13 +0600 Subject: [PATCH 13/42] OrderedCancellableSpliterator2: race is likely to be fixed --- .../OrderedCancellableSpliterator2.java | 45 ++++++++++--------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/src/main/java/javax/util/streamex/OrderedCancellableSpliterator2.java b/src/main/java/javax/util/streamex/OrderedCancellableSpliterator2.java index ae71a03a..453a5512 100644 --- a/src/main/java/javax/util/streamex/OrderedCancellableSpliterator2.java +++ b/src/main/java/javax/util/streamex/OrderedCancellableSpliterator2.java @@ -39,7 +39,7 @@ private OrderedCancellableSpliterator2 prefix; private OrderedCancellableSpliterator2 suffix; private A payload; - + OrderedCancellableSpliterator2(Spliterator source, Supplier supplier, BiConsumer accumulator, BinaryOperator combiner, Predicate cancelPredicate) { this.source = source; @@ -68,41 +68,47 @@ public boolean tryAdvance(Consumer action) { throw new CancelException(); } }); - } - catch(CancelException ex) { - if(localCancelled) { + } catch (CancelException ex) { + if (localCancelled) { return false; } - localCancelled = true; } this.source = null; A result = acc; - while(true) { + while (true) { + if (prefix == null && suffix == null) { + action.accept(result); + return true; + } ArrayDeque res = new ArrayDeque<>(); res.offer(result); synchronized (lock) { + if (localCancelled) + return false; OrderedCancellableSpliterator2 s = prefix; - while(s != null) { - if(s.payload == null) break; + while (s != null) { + if (s.payload == null) + break; res.offerFirst(s.payload); s = s.prefix; } prefix = s; - if(s != null) { + if (s != null) { s.suffix = this; } s = suffix; - while(s != null) { - if(s.payload == null) break; + while (s != null) { + if (s.payload == null) + break; res.offerLast(s.payload); s = s.suffix; } suffix = s; - if(s != null) { + if (s != null) { s.prefix = this; } - if(res.size() == 1) { - if(prefix == null && suffix == null) { + if (res.size() == 1) { + if (prefix == null && suffix == null) { action.accept(result); return true; } @@ -111,11 +117,10 @@ public boolean tryAdvance(Consumer action) { } } result = res.pollFirst(); - while(!res.isEmpty()) { - result = combiner.apply(result, res.pollFirst()); - if(cancelPredicate.test(result)) { + while (!res.isEmpty()) { + result = combiner.apply(result, res.pollFirst()); + if (cancelPredicate.test(result)) { cancelSuffix(); - localCancelled = true; } } } @@ -123,7 +128,7 @@ public boolean tryAdvance(Consumer action) { } private void cancelSuffix() { - if(this.suffix == null) + if (this.suffix == null) return; synchronized (lock) { OrderedCancellableSpliterator2 suffix = this.suffix; @@ -152,7 +157,7 @@ public Spliterator trySplit() { return null; } try { - synchronized(lock) { + synchronized (lock) { @SuppressWarnings("unchecked") OrderedCancellableSpliterator2 result = (OrderedCancellableSpliterator2) this.clone(); result.source = prefix; From 7cf21b88fd23c92f399c8840dda0f46e65ac2566 Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Tue, 13 Oct 2015 11:38:23 +0600 Subject: [PATCH 14/42] StreamEx.cross(mapper) now works if mapper returns null; minor documentation update --- CHANGES.md | 5 +++ .../java/javax/util/streamex/StreamEx.java | 36 +++++++++++++++++-- .../javax/util/streamex/StreamExTest.java | 1 + 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 771b2806..a1fa6f4b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,10 @@ # StreamEx changes +### 0.4.1 + +* Fixed: `StreamEx.cross(mapper)` now correctly handles the case when mapper returns null instead of empty stream. +* Updated documentation. + ### 0.4.0 * Introduced the concept of short-circuiting collectors. diff --git a/src/main/java/javax/util/streamex/StreamEx.java b/src/main/java/javax/util/streamex/StreamEx.java index 0f9e5a7a..d7b68e7c 100644 --- a/src/main/java/javax/util/streamex/StreamEx.java +++ b/src/main/java/javax/util/streamex/StreamEx.java @@ -195,6 +195,27 @@ public EntryStream mapToEntry(Function keyM stream.map(e -> new SimpleImmutableEntry<>(keyMapper.apply(e), valueMapper.apply(e)))); } + /** + * Creates a new {@code EntryStream} populated from entries of maps produced + * by supplied mapper function which is applied to the every element of this + * stream. + * + *

+ * This is an intermediate + * operation. + * + * @param + * the type of {@code Map} keys. + * @param + * the type of {@code Map} values. + * @param mapper + * a non-interfering, stateless function to apply to each element + * which produces a {@link Map} of the entries corresponding to + * the single element of the current stream. The mapper function + * may return null or empty {@code Map} if no mapping should + * correspond to some element. + * @return the new {@code EntryStream} + */ public EntryStream flatMapToEntry(Function> mapper) { return strategy().newEntryStream(stream.flatMap(e -> { Map s = mapper.apply(e); @@ -221,6 +242,8 @@ public EntryStream flatMapToEntry(Function EntryStream cross(V... other) { * @param other * the collection to perform a cross product with * @return the new {@code EntryStream} + * @throws NullPointerException + * if other is null * @since 0.2.3 */ public EntryStream cross(Collection other) { @@ -263,7 +288,10 @@ public EntryStream cross(Collection other) { /** * Creates a new {@code EntryStream} whose keys are elements of current - * stream and corresponding values are supplied by given function. + * stream and corresponding values are supplied by given function. Each + * mapped stream is {@link java.util.stream.BaseStream#close() closed} after + * its contents have been placed into this stream. (If a mapped stream is + * {@code null} an empty stream is used, instead.) * *

* This is an intermediate @@ -279,8 +307,10 @@ public EntryStream cross(Collection other) { * @since 0.2.3 */ public EntryStream cross(Function> mapper) { - return strategy().newEntryStream( - stream.flatMap(a -> mapper.apply(a).map(b -> new SimpleImmutableEntry<>(a, b)))); + return strategy().newEntryStream(stream.flatMap(a -> { + Stream s = mapper.apply(a); + return s == null ? null : s.map(b -> new SimpleImmutableEntry<>(a, b)); + })); } /** diff --git a/src/test/java/javax/util/streamex/StreamExTest.java b/src/test/java/javax/util/streamex/StreamExTest.java index 0516c197..9c71c3e6 100644 --- a/src/test/java/javax/util/streamex/StreamExTest.java +++ b/src/test/java/javax/util/streamex/StreamExTest.java @@ -890,6 +890,7 @@ public void testCross() { .mapKeyValue((input, output) -> input + "->" + output).joining(", ")); assertEquals("", StreamEx.of(inputs).cross(Collections.emptyList()).join("->").joining(", ")); assertEquals("i-i, j-j, k-k", StreamEx.of(inputs).cross(Stream::of).join("-").joining(", ")); + assertEquals("j-j, k-k", StreamEx.of(inputs).cross(x -> x.equals("i") ? null : Stream.of(x)).join("-").joining(", ")); } @Test From f13676d390d2195a33014f90173073bc771325dc Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Thu, 22 Oct 2015 18:54:27 +0600 Subject: [PATCH 15/42] OrderedCancellableSpliterator3: skiplist-based (first try) --- .../OrderedCancellableSpliterator3.java | 250 ++++++++++++++++++ .../OrderedCancellableSpliteratorTest.java | 19 ++ 2 files changed, 269 insertions(+) create mode 100644 src/main/java/javax/util/streamex/OrderedCancellableSpliterator3.java diff --git a/src/main/java/javax/util/streamex/OrderedCancellableSpliterator3.java b/src/main/java/javax/util/streamex/OrderedCancellableSpliterator3.java new file mode 100644 index 00000000..c86cc40d --- /dev/null +++ b/src/main/java/javax/util/streamex/OrderedCancellableSpliterator3.java @@ -0,0 +1,250 @@ +/* + * Copyright 2015 Tagir Valeev + * + * 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 javax.util.streamex; + +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.util.Map.Entry; +import java.util.Spliterator; +import java.util.concurrent.ConcurrentSkipListMap; +import java.util.function.BiConsumer; +import java.util.function.BinaryOperator; +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.function.Supplier; + +import static javax.util.streamex.StreamExInternals.*; + +/** + * @author Tagir Valeev + */ +/* package */final class OrderedCancellableSpliterator3 implements Spliterator, Cloneable { + private Spliterator source; + private final ConcurrentSkipListMap map = new ConcurrentSkipListMap<>(); + private final BiConsumer accumulator; + private final Predicate cancelPredicate; + private final BinaryOperator combiner; + private final Supplier supplier; + private Key key = Key.ROOT; + private volatile boolean localCancelled; + private OrderedCancellableSpliterator3 prefix; + private volatile OrderedCancellableSpliterator3 suffix; + + private static class Key implements Comparable { + final int length; + final long bits1, bits2, bits3; + + static final int MAX_LENGTH = Long.SIZE*3; + static final Key ROOT = new Key(null, false); + + private Key(Key parent, boolean left) { + long b1 = 0, b2 = 0, b3 = 0; + int l = 0; + if(parent != null) { + l = parent.length+1; + b1 = parent.bits1; + b2 = parent.bits2; + b3 = parent.bits3; + if(!left) { + if(l < Long.SIZE) { + b1 |= (1 << (Long.SIZE-l)); + } else if(l < 2*Long.SIZE) { + b2 |= (1 << (2*Long.SIZE-l)); + } else { + b3 |= (1 << (MAX_LENGTH-l)); + } + } + } + this.length = l; + this.bits1 = b1; + this.bits2 = b2; + this.bits3 = b3; + } + + Key left() { + return new Key(this, true); + } + + Key right() { + return new Key(this, false); + } + + @Override + public int compareTo(Key o) { + int res = Long.compareUnsigned(bits1, o.bits1); + if(res == 0) + res = Long.compareUnsigned(bits2, o.bits2); + if(res == 0) + res = Long.compareUnsigned(bits3, o.bits3); + if(res == 0) + res = Integer.compare(o.length, length); + return res; + } + + @Override + public String toString() { + return new BigInteger(ByteBuffer.allocate(25).put((byte) 0).putLong(bits1).putLong(bits2).putLong(bits3).array()).toString(16); + } + + } + + OrderedCancellableSpliterator3(Spliterator source, Supplier supplier, BiConsumer accumulator, + BinaryOperator combiner, Predicate cancelPredicate) { + this.source = source; + this.supplier = supplier; + this.accumulator = accumulator; + this.combiner = combiner; + this.cancelPredicate = cancelPredicate; + this.map.put(key, none()); + } + + @Override + public boolean tryAdvance(Consumer action) { + Spliterator source = this.source; + if (source == null || localCancelled) { + this.source = null; + return false; + } + A acc = supplier.get(); + try { + source.forEachRemaining(t -> { + accumulator.accept(acc, t); + if (cancelPredicate.test(acc)) { + cancelSuffix(); + throw new CancelException(); + } + if (localCancelled) { + throw new CancelException(); + } + }); + } catch (CancelException ex) { + if (localCancelled) { + return false; + } + } + this.source = null; + A result = acc; + boolean changed = true; + Entry lowerEntry, higherEntry; + do { + changed = false; + while(true) { + if(localCancelled) + return false; + lowerEntry = map.lowerEntry(key); + if(lowerEntry == null || lowerEntry.getValue() == NONE) + break; + A prev = map.remove(lowerEntry.getKey()); + if(prev == null) // other party removed this key during suffix traversal + break; + result = combiner.apply(prev, result); + if(cancelPredicate.test(result)) { + cancelSuffix(); + } + changed = true; + } + while(true) { + if(localCancelled) + return false; + higherEntry = map.higherEntry(key); + if(higherEntry == null || higherEntry.getValue() == NONE) + break; + A next = map.remove(higherEntry.getKey()); + if(next == null) // other party removed this key during prefix traversal + break; + result = combiner.apply(result, next); + if(cancelPredicate.test(result)) { + cancelSuffix(); + } + changed = true; + } + } while(changed); + if(lowerEntry == null && (higherEntry == null || suffix == null)) { + action.accept(result); + return true; + } + A old = map.put(key, result); + assert old == NONE; + return false; + } + + private void cancelSuffix() { + if (this.suffix == null) + return; + OrderedCancellableSpliterator3 suffix = this.suffix; + while (suffix != null && !suffix.localCancelled) { + suffix.localCancelled = true; + suffix = suffix.suffix; + } + this.suffix = null; + } + + @Override + public void forEachRemaining(Consumer action) { + tryAdvance(action); + } + + @Override + public Spliterator trySplit() { + if (localCancelled) { + source = null; + return null; + } + if(key.length == Key.MAX_LENGTH || source == null) { + return null; + } + Spliterator prefix = source.trySplit(); + if (prefix == null) { + return null; + } + try { + Key left = key.left(); + Key right = key.right(); + @SuppressWarnings("unchecked") + OrderedCancellableSpliterator3 result = (OrderedCancellableSpliterator3) this.clone(); + result.source = prefix; + this.prefix = result; + result.suffix = this; + OrderedCancellableSpliterator3 prefixPrefix = result.prefix; + if (prefixPrefix != null) + prefixPrefix.suffix = result; + if (this.localCancelled || result.localCancelled) { + // we can end up here due to the race with suffix updates in + // tryAdvance + this.localCancelled = result.localCancelled = true; + return null; + } + map.put(left, none()); + map.put(right, none()); + map.remove(this.key); + this.key = right; + result.key = left; + return result; + } catch (CloneNotSupportedException e) { + throw new InternalError(); + } + } + + @Override + public long estimateSize() { + return source == null ? 0 : source.estimateSize(); + } + + @Override + public int characteristics() { + return source == null ? SIZED : ORDERED; + } +} diff --git a/src/test/java/javax/util/streamex/OrderedCancellableSpliteratorTest.java b/src/test/java/javax/util/streamex/OrderedCancellableSpliteratorTest.java index 650241ab..98c68a46 100644 --- a/src/test/java/javax/util/streamex/OrderedCancellableSpliteratorTest.java +++ b/src/test/java/javax/util/streamex/OrderedCancellableSpliteratorTest.java @@ -49,4 +49,23 @@ public void testSpliterator() { () -> new OrderedCancellableSpliterator2<>( input.spliterator(), s, a, c, p)); } + + @Test + public void testSpliteratorMap() { + int limit = 10; + Supplier> s = ArrayList::new; + BiConsumer, Integer> a = (acc, t) -> { + if(acc.size() < limit) acc.add(t); + }; + BinaryOperator> c = (a1, a2) -> { + a2.forEach(t -> a.accept(a1, t)); + return a1; + }; + Predicate> p = acc -> acc.size() >= limit; + List input = IntStreamEx.range(30).boxed().toList(); + List expected = IntStreamEx.range(limit).boxed().toList(); + checkSpliterator("head-short-circuit", Collections.singletonList(expected), + () -> new OrderedCancellableSpliterator3<>( + input.spliterator(), s, a, c, p)); + } } From 34fd300f665fc1489e6af27b1e2fa15370c041ca Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Thu, 22 Oct 2015 19:12:09 +0600 Subject: [PATCH 16/42] OrderedCancellableSpliterator3: some bugs fixed, tests, debug output (not working yet) --- .../javax/util/streamex/AbstractStreamEx.java | 2 +- .../OrderedCancellableSpliterator3.java | 17 +++++++++++------ .../java/javax/util/streamex/StreamExTest.java | 9 +++++++++ 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/main/java/javax/util/streamex/AbstractStreamEx.java b/src/main/java/javax/util/streamex/AbstractStreamEx.java index 11850e8d..f6998748 100644 --- a/src/main/java/javax/util/streamex/AbstractStreamEx.java +++ b/src/main/java/javax/util/streamex/AbstractStreamEx.java @@ -301,7 +301,7 @@ public R collect(Collector collector) { || c.characteristics().contains(Characteristics.UNORDERED)) { spltr = new UnorderedCancellableSpliterator<>(spliterator, c.supplier(), acc, combiner, finished); } else { - spltr = new OrderedCancellableSpliterator2<>(spliterator, c.supplier(), acc, combiner, finished); + spltr = new OrderedCancellableSpliterator3<>(spliterator, c.supplier(), acc, combiner, finished); } return c.finisher().apply(strategy().newStreamEx(StreamSupport.stream(spltr, true)).findFirst().get()); } diff --git a/src/main/java/javax/util/streamex/OrderedCancellableSpliterator3.java b/src/main/java/javax/util/streamex/OrderedCancellableSpliterator3.java index c86cc40d..a7b491f7 100644 --- a/src/main/java/javax/util/streamex/OrderedCancellableSpliterator3.java +++ b/src/main/java/javax/util/streamex/OrderedCancellableSpliterator3.java @@ -15,8 +15,6 @@ */ package javax.util.streamex; -import java.math.BigInteger; -import java.nio.ByteBuffer; import java.util.Map.Entry; import java.util.Spliterator; import java.util.concurrent.ConcurrentSkipListMap; @@ -60,11 +58,11 @@ private Key(Key parent, boolean left) { b3 = parent.bits3; if(!left) { if(l < Long.SIZE) { - b1 |= (1 << (Long.SIZE-l)); + b1 |= (1L << (Long.SIZE-l)); } else if(l < 2*Long.SIZE) { - b2 |= (1 << (2*Long.SIZE-l)); + b2 |= (1L << (2*Long.SIZE-l)); } else { - b3 |= (1 << (MAX_LENGTH-l)); + b3 |= (1L << (MAX_LENGTH-l)); } } } @@ -96,7 +94,7 @@ public int compareTo(Key o) { @Override public String toString() { - return new BigInteger(ByteBuffer.allocate(25).put((byte) 0).putLong(bits1).putLong(bits2).putLong(bits3).array()).toString(16); + return String.format("%64s%64s%64s", Long.toBinaryString(bits1), Long.toBinaryString(bits2), Long.toBinaryString(bits3)).substring(0, length).replace(' ', '0'); } } @@ -118,6 +116,7 @@ public boolean tryAdvance(Consumer action) { this.source = null; return false; } + System.out.println(key+": start"); A acc = supplier.get(); try { source.forEachRemaining(t -> { @@ -131,6 +130,7 @@ public boolean tryAdvance(Consumer action) { } }); } catch (CancelException ex) { + System.out.println(key+": cancelled"); if (localCancelled) { return false; } @@ -139,6 +139,7 @@ public boolean tryAdvance(Consumer action) { A result = acc; boolean changed = true; Entry lowerEntry, higherEntry; + System.out.println(key+": combining"); do { changed = false; while(true) { @@ -150,6 +151,7 @@ public boolean tryAdvance(Consumer action) { A prev = map.remove(lowerEntry.getKey()); if(prev == null) // other party removed this key during suffix traversal break; + System.out.println(key+": combine with prefix "+lowerEntry.getKey()); result = combiner.apply(prev, result); if(cancelPredicate.test(result)) { cancelSuffix(); @@ -165,6 +167,7 @@ public boolean tryAdvance(Consumer action) { A next = map.remove(higherEntry.getKey()); if(next == null) // other party removed this key during prefix traversal break; + System.out.println(key+": combine with suffix "+higherEntry.getKey()); result = combiner.apply(result, next); if(cancelPredicate.test(result)) { cancelSuffix(); @@ -173,9 +176,11 @@ public boolean tryAdvance(Consumer action) { } } while(changed); if(lowerEntry == null && (higherEntry == null || suffix == null)) { + System.out.println(key+": consume!"); action.accept(result); return true; } + System.out.println(key+": offer"); A old = map.put(key, result); assert old == NONE; return false; diff --git a/src/test/java/javax/util/streamex/StreamExTest.java b/src/test/java/javax/util/streamex/StreamExTest.java index 0516c197..ef0338d7 100644 --- a/src/test/java/javax/util/streamex/StreamExTest.java +++ b/src/test/java/javax/util/streamex/StreamExTest.java @@ -1451,4 +1451,13 @@ public void testIndexOf() { assertFalse(supplier.toString(), supplier.get().indexOf(""::equals).isPresent()); } } + + @Test + public void testIndexOfSimple() { + List input = IntStreamEx.range(10).boxed().toList(); + //input.addAll(input); + for (int i = 0; i < 100; i++) { + assertEquals(9, StreamEx.of(input).parallel().indexOf(9).getAsLong()); + } + } } From 398ac14d8128f78ad4be96e427690cf8bd58b70f Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Fri, 23 Oct 2015 08:27:57 +0600 Subject: [PATCH 17/42] OrderedCancellableSpliterator3: publication race fixed (all tests pass now) --- .../OrderedCancellableSpliterator3.java | 58 ++++++++++--------- .../javax/util/streamex/StreamExTest.java | 1 - 2 files changed, 31 insertions(+), 28 deletions(-) diff --git a/src/main/java/javax/util/streamex/OrderedCancellableSpliterator3.java b/src/main/java/javax/util/streamex/OrderedCancellableSpliterator3.java index a7b491f7..8356adf3 100644 --- a/src/main/java/javax/util/streamex/OrderedCancellableSpliterator3.java +++ b/src/main/java/javax/util/streamex/OrderedCancellableSpliterator3.java @@ -116,7 +116,6 @@ public boolean tryAdvance(Consumer action) { this.source = null; return false; } - System.out.println(key+": start"); A acc = supplier.get(); try { source.forEachRemaining(t -> { @@ -130,60 +129,65 @@ public boolean tryAdvance(Consumer action) { } }); } catch (CancelException ex) { - System.out.println(key+": cancelled"); if (localCancelled) { return false; } } this.source = null; A result = acc; - boolean changed = true; - Entry lowerEntry, higherEntry; - System.out.println(key+": combining"); - do { - changed = false; + Entry lowerEntry, higherEntry = null; + while(true) { while(true) { if(localCancelled) return false; lowerEntry = map.lowerEntry(key); if(lowerEntry == null || lowerEntry.getValue() == NONE) break; - A prev = map.remove(lowerEntry.getKey()); - if(prev == null) // other party removed this key during suffix traversal + if(!map.remove(lowerEntry.getKey(), lowerEntry.getValue())) break; - System.out.println(key+": combine with prefix "+lowerEntry.getKey()); - result = combiner.apply(prev, result); + result = combiner.apply(lowerEntry.getValue(), result); if(cancelPredicate.test(result)) { cancelSuffix(); } - changed = true; } - while(true) { + while(suffix != null) { if(localCancelled) return false; higherEntry = map.higherEntry(key); if(higherEntry == null || higherEntry.getValue() == NONE) break; - A next = map.remove(higherEntry.getKey()); - if(next == null) // other party removed this key during prefix traversal + if(!map.remove(higherEntry.getKey(), higherEntry.getValue())) break; - System.out.println(key+": combine with suffix "+higherEntry.getKey()); - result = combiner.apply(result, next); + result = combiner.apply(result, higherEntry.getValue()); if(cancelPredicate.test(result)) { cancelSuffix(); } - changed = true; } - } while(changed); - if(lowerEntry == null && (higherEntry == null || suffix == null)) { - System.out.println(key+": consume!"); - action.accept(result); - return true; + if(lowerEntry == null && (higherEntry == null || suffix == null)) { + action.accept(result); + return true; + } + map.put(key, result); + if(lowerEntry != null) { + lowerEntry = map.lowerEntry(key); + if(lowerEntry != null && lowerEntry.getValue() != NONE) { + if(!map.replace(key, result, none())) { + return false; + } + continue; + } + } + if(higherEntry != null && suffix != null) { + higherEntry = map.higherEntry(key); + if(higherEntry != null && higherEntry.getValue() != NONE) { + if(!map.replace(key, result, none())) { + return false; + } + continue; + } + } + return false; } - System.out.println(key+": offer"); - A old = map.put(key, result); - assert old == NONE; - return false; } private void cancelSuffix() { diff --git a/src/test/java/javax/util/streamex/StreamExTest.java b/src/test/java/javax/util/streamex/StreamExTest.java index ef0338d7..82e52235 100644 --- a/src/test/java/javax/util/streamex/StreamExTest.java +++ b/src/test/java/javax/util/streamex/StreamExTest.java @@ -1455,7 +1455,6 @@ public void testIndexOf() { @Test public void testIndexOfSimple() { List input = IntStreamEx.range(10).boxed().toList(); - //input.addAll(input); for (int i = 0; i < 100; i++) { assertEquals(9, StreamEx.of(input).parallel().indexOf(9).getAsLong()); } From e5cac0f7af10e02d3f69179b7a0d0af2ef5bb0cf Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Fri, 23 Oct 2015 17:20:35 +0600 Subject: [PATCH 18/42] OrderedCancellableSpliterator3: likely fixed (with debug info and additional test) --- .../OrderedCancellableSpliterator3.java | 45 +++++++++++++++---- .../util/streamex/MoreCollectorsTest.java | 8 ++++ 2 files changed, 44 insertions(+), 9 deletions(-) diff --git a/src/main/java/javax/util/streamex/OrderedCancellableSpliterator3.java b/src/main/java/javax/util/streamex/OrderedCancellableSpliterator3.java index 8356adf3..e8936279 100644 --- a/src/main/java/javax/util/streamex/OrderedCancellableSpliterator3.java +++ b/src/main/java/javax/util/streamex/OrderedCancellableSpliterator3.java @@ -15,9 +15,12 @@ */ package javax.util.streamex; +import java.util.Collection; import java.util.Map.Entry; import java.util.Spliterator; +import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentSkipListMap; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; import java.util.function.BiConsumer; import java.util.function.BinaryOperator; import java.util.function.Consumer; @@ -30,6 +33,9 @@ * @author Tagir Valeev */ /* package */final class OrderedCancellableSpliterator3 implements Spliterator, Cloneable { + @SuppressWarnings("rawtypes") + private static final AtomicReferenceFieldUpdater updater + = AtomicReferenceFieldUpdater.newUpdater(OrderedCancellableSpliterator3.class, OrderedCancellableSpliterator3.class, "suffix"); private Spliterator source; private final ConcurrentSkipListMap map = new ConcurrentSkipListMap<>(); private final BiConsumer accumulator; @@ -96,8 +102,9 @@ public int compareTo(Key o) { public String toString() { return String.format("%64s%64s%64s", Long.toBinaryString(bits1), Long.toBinaryString(bits2), Long.toBinaryString(bits3)).substring(0, length).replace(' ', '0'); } - } + + static Collection log = new ConcurrentLinkedQueue<>(); OrderedCancellableSpliterator3(Spliterator source, Supplier supplier, BiConsumer accumulator, BinaryOperator combiner, Predicate cancelPredicate) { @@ -116,6 +123,7 @@ public boolean tryAdvance(Consumer action) { this.source = null; return false; } + //log.add(key+": start"); A acc = supplier.get(); try { source.forEachRemaining(t -> { @@ -129,60 +137,76 @@ public boolean tryAdvance(Consumer action) { } }); } catch (CancelException ex) { + //log.add(key+"/"+acc+": cancelled"); if (localCancelled) { return false; } } this.source = null; A result = acc; + //log.add(key+"/"+acc+": combining start"); Entry lowerEntry, higherEntry = null; while(true) { while(true) { - if(localCancelled) - return false; lowerEntry = map.lowerEntry(key); if(lowerEntry == null || lowerEntry.getValue() == NONE) break; if(!map.remove(lowerEntry.getKey(), lowerEntry.getValue())) - break; + continue; + //log.add(key+"/"+result+" with prefix "+lowerEntry); result = combiner.apply(lowerEntry.getValue(), result); if(cancelPredicate.test(result)) { + //log.add(key+"/"+result+": cancelsuffix1"); cancelSuffix(); } } while(suffix != null) { - if(localCancelled) - return false; higherEntry = map.higherEntry(key); if(higherEntry == null || higherEntry.getValue() == NONE) break; if(!map.remove(higherEntry.getKey(), higherEntry.getValue())) - break; + continue; + //log.add(key+"/"+result+" with suffix "+higherEntry); result = combiner.apply(result, higherEntry.getValue()); if(cancelPredicate.test(result)) { + //log.add(key+"/"+result+": cancelsuffix2"); cancelSuffix(); } } if(lowerEntry == null && (higherEntry == null || suffix == null)) { + //log.add(key+"/"+result+": accept!!!"); + //log.add(map.toString()); action.accept(result); return true; } +// if(lowerEntry == null) +// { +// log.add(key+"/"+result+": offer: "+suffix); +// //log.add(map.keySet().toString()); +// } + //log.add(key+"/"+result+": offer"); map.put(key, result); if(lowerEntry != null) { lowerEntry = map.lowerEntry(key); if(lowerEntry != null && lowerEntry.getValue() != NONE) { + //log.add(key+": race on lower "+lowerEntry.getKey()); if(!map.replace(key, result, none())) { + //log.add(key+": other party took the responsibility; exiting"); return false; } + //log.add(key+"/"+result+": continue"); continue; } } if(higherEntry != null && suffix != null) { higherEntry = map.higherEntry(key); if(higherEntry != null && higherEntry.getValue() != NONE) { + //log.add(key+": race on higher "+higherEntry.getKey()); if(!map.replace(key, result, none())) { + //log.add(key+": other party took the responsibility; exiting"); return false; } + //log.add(key+"/"+result+": continue"); continue; } } @@ -191,12 +215,15 @@ public boolean tryAdvance(Consumer action) { } private void cancelSuffix() { + //log.add(key+": cancelling suffix"); if (this.suffix == null) return; OrderedCancellableSpliterator3 suffix = this.suffix; while (suffix != null && !suffix.localCancelled) { suffix.localCancelled = true; - suffix = suffix.suffix; + OrderedCancellableSpliterator3 next = suffix.suffix; + suffix.suffix = null; + suffix = next; } this.suffix = null; } @@ -229,7 +256,7 @@ public Spliterator trySplit() { result.suffix = this; OrderedCancellableSpliterator3 prefixPrefix = result.prefix; if (prefixPrefix != null) - prefixPrefix.suffix = result; + updater.compareAndSet(prefixPrefix, this, result); if (this.localCancelled || result.localCancelled) { // we can end up here due to the race with suffix updates in // tryAdvance diff --git a/src/test/java/javax/util/streamex/MoreCollectorsTest.java b/src/test/java/javax/util/streamex/MoreCollectorsTest.java index 0760fd36..54b8e56d 100644 --- a/src/test/java/javax/util/streamex/MoreCollectorsTest.java +++ b/src/test/java/javax/util/streamex/MoreCollectorsTest.java @@ -189,6 +189,14 @@ public void testHeadTailParallel() { assertEquals(Arrays.asList(0, 1), StreamEx.iterate(0, x -> x + 1).parallel().collect(MoreCollectors.head(2))); } + @Test + public void testHeadTailParallel2() { + for (int i = 0; i < 1000000; i++) { + assertEquals("#" + i, IntStreamEx.range(0, 20, 2).boxed().toList(), IntStreamEx.range(100).boxed() + .parallel().filter(x -> x % 2 == 0).collect(MoreCollectors.head(10))); + } + } + @Test public void testHeadTail() { List ints = IntStreamEx.range(1000).boxed().toList(); From 082812c1e4adc173d0c51e9755b846785dbc1b96 Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Fri, 23 Oct 2015 17:33:54 +0600 Subject: [PATCH 19/42] OrderedCancellableSpliterator3: Debug code removed; test rectified; AbstractStreamEx.collect2 (temporary method for benchmarking) --- .../javax/util/streamex/AbstractStreamEx.java | 37 +++++++++++++++++++ .../OrderedCancellableSpliterator3.java | 26 ------------- .../util/streamex/MoreCollectorsTest.java | 6 +-- 3 files changed, 40 insertions(+), 29 deletions(-) diff --git a/src/main/java/javax/util/streamex/AbstractStreamEx.java b/src/main/java/javax/util/streamex/AbstractStreamEx.java index f6998748..ae1a72ca 100644 --- a/src/main/java/javax/util/streamex/AbstractStreamEx.java +++ b/src/main/java/javax/util/streamex/AbstractStreamEx.java @@ -308,6 +308,43 @@ public R collect(Collector collector) { return rawCollect(collector); } + public R collect2(Collector collector) { + if (collector instanceof CancellableCollector) { + CancellableCollector c = (CancellableCollector) collector; + BiConsumer acc = c.accumulator(); + Predicate finished = c.finished(); + BinaryOperator combiner = c.combiner(); + Spliterator spliterator = stream.spliterator(); + if (!isParallel()) { + A a = c.supplier().get(); + if (!finished.test(a)) { + try { + // forEachRemaining can be much faster + // and take much less memory than tryAdvance for certain + // spliterators + spliterator.forEachRemaining(e -> { + acc.accept(a, e); + if (finished.test(a)) + throw new CancelException(); + }); + } catch (CancelException ex) { + // ignore + } + } + return c.finisher().apply(a); + } + Spliterator spltr; + if (!spliterator.hasCharacteristics(Spliterator.ORDERED) + || c.characteristics().contains(Characteristics.UNORDERED)) { + spltr = new UnorderedCancellableSpliterator<>(spliterator, c.supplier(), acc, combiner, finished); + } else { + spltr = new OrderedCancellableSpliterator2<>(spliterator, c.supplier(), acc, combiner, finished); + } + return c.finisher().apply(strategy().newStreamEx(StreamSupport.stream(spltr, true)).findFirst().get()); + } + return rawCollect(collector); + } + public R collectOld(Collector collector) { if (collector instanceof CancellableCollector) { CancellableCollector c = (CancellableCollector) collector; diff --git a/src/main/java/javax/util/streamex/OrderedCancellableSpliterator3.java b/src/main/java/javax/util/streamex/OrderedCancellableSpliterator3.java index e8936279..88a9d97b 100644 --- a/src/main/java/javax/util/streamex/OrderedCancellableSpliterator3.java +++ b/src/main/java/javax/util/streamex/OrderedCancellableSpliterator3.java @@ -15,10 +15,8 @@ */ package javax.util.streamex; -import java.util.Collection; import java.util.Map.Entry; import java.util.Spliterator; -import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentSkipListMap; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; import java.util.function.BiConsumer; @@ -104,8 +102,6 @@ public String toString() { } } - static Collection log = new ConcurrentLinkedQueue<>(); - OrderedCancellableSpliterator3(Spliterator source, Supplier supplier, BiConsumer accumulator, BinaryOperator combiner, Predicate cancelPredicate) { this.source = source; @@ -123,7 +119,6 @@ public boolean tryAdvance(Consumer action) { this.source = null; return false; } - //log.add(key+": start"); A acc = supplier.get(); try { source.forEachRemaining(t -> { @@ -137,14 +132,12 @@ public boolean tryAdvance(Consumer action) { } }); } catch (CancelException ex) { - //log.add(key+"/"+acc+": cancelled"); if (localCancelled) { return false; } } this.source = null; A result = acc; - //log.add(key+"/"+acc+": combining start"); Entry lowerEntry, higherEntry = null; while(true) { while(true) { @@ -153,10 +146,8 @@ public boolean tryAdvance(Consumer action) { break; if(!map.remove(lowerEntry.getKey(), lowerEntry.getValue())) continue; - //log.add(key+"/"+result+" with prefix "+lowerEntry); result = combiner.apply(lowerEntry.getValue(), result); if(cancelPredicate.test(result)) { - //log.add(key+"/"+result+": cancelsuffix1"); cancelSuffix(); } } @@ -166,47 +157,31 @@ public boolean tryAdvance(Consumer action) { break; if(!map.remove(higherEntry.getKey(), higherEntry.getValue())) continue; - //log.add(key+"/"+result+" with suffix "+higherEntry); result = combiner.apply(result, higherEntry.getValue()); if(cancelPredicate.test(result)) { - //log.add(key+"/"+result+": cancelsuffix2"); cancelSuffix(); } } if(lowerEntry == null && (higherEntry == null || suffix == null)) { - //log.add(key+"/"+result+": accept!!!"); - //log.add(map.toString()); action.accept(result); return true; } -// if(lowerEntry == null) -// { -// log.add(key+"/"+result+": offer: "+suffix); -// //log.add(map.keySet().toString()); -// } - //log.add(key+"/"+result+": offer"); map.put(key, result); if(lowerEntry != null) { lowerEntry = map.lowerEntry(key); if(lowerEntry != null && lowerEntry.getValue() != NONE) { - //log.add(key+": race on lower "+lowerEntry.getKey()); if(!map.replace(key, result, none())) { - //log.add(key+": other party took the responsibility; exiting"); return false; } - //log.add(key+"/"+result+": continue"); continue; } } if(higherEntry != null && suffix != null) { higherEntry = map.higherEntry(key); if(higherEntry != null && higherEntry.getValue() != NONE) { - //log.add(key+": race on higher "+higherEntry.getKey()); if(!map.replace(key, result, none())) { - //log.add(key+": other party took the responsibility; exiting"); return false; } - //log.add(key+"/"+result+": continue"); continue; } } @@ -215,7 +190,6 @@ public boolean tryAdvance(Consumer action) { } private void cancelSuffix() { - //log.add(key+": cancelling suffix"); if (this.suffix == null) return; OrderedCancellableSpliterator3 suffix = this.suffix; diff --git a/src/test/java/javax/util/streamex/MoreCollectorsTest.java b/src/test/java/javax/util/streamex/MoreCollectorsTest.java index 54b8e56d..f4133141 100644 --- a/src/test/java/javax/util/streamex/MoreCollectorsTest.java +++ b/src/test/java/javax/util/streamex/MoreCollectorsTest.java @@ -179,7 +179,7 @@ public void testFirstLast() { } @Test - public void testHeadTailParallel() { + public void testHeadParallel() { for (int i = 0; i < 1000; i++) { assertEquals("#" + i, Arrays.asList(0, 1), IntStreamEx.range(1000).boxed().parallel().collect(MoreCollectors.head(2))); @@ -190,8 +190,8 @@ public void testHeadTailParallel() { } @Test - public void testHeadTailParallel2() { - for (int i = 0; i < 1000000; i++) { + public void testHeadParallel2() { + for (int i = 0; i < 10000; i++) { assertEquals("#" + i, IntStreamEx.range(0, 20, 2).boxed().toList(), IntStreamEx.range(100).boxed() .parallel().filter(x -> x % 2 == 0).collect(MoreCollectors.head(10))); } From fa4ef9486ec9f45646b89f73c354943cb710fe7c Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Sat, 24 Oct 2015 16:54:41 +0600 Subject: [PATCH 20/42] OrderedCancellableSpliterator3: preinitialization --- .../OrderedCancellableSpliterator3.java | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/main/java/javax/util/streamex/OrderedCancellableSpliterator3.java b/src/main/java/javax/util/streamex/OrderedCancellableSpliterator3.java index 88a9d97b..cbd7ba16 100644 --- a/src/main/java/javax/util/streamex/OrderedCancellableSpliterator3.java +++ b/src/main/java/javax/util/streamex/OrderedCancellableSpliterator3.java @@ -46,6 +46,7 @@ private volatile OrderedCancellableSpliterator3 suffix; private static class Key implements Comparable { + // TODO: support keys of arbitrary length final int length; final long bits1, bits2, bits3; @@ -119,7 +120,18 @@ public boolean tryAdvance(Consumer action) { this.source = null; return false; } - A acc = supplier.get(); + A result = null; + while(true) { + Entry lowerEntry = map.lowerEntry(key); + if(lowerEntry == null || lowerEntry.getValue() == NONE) + break; + if(!map.remove(lowerEntry.getKey(), lowerEntry.getValue())) + continue; + result = result == null ? lowerEntry.getValue() : combiner.apply(lowerEntry.getValue(), result); + } + if(result == null) + result = supplier.get(); + A acc = result; try { source.forEachRemaining(t -> { accumulator.accept(acc, t); @@ -132,12 +144,8 @@ public boolean tryAdvance(Consumer action) { } }); } catch (CancelException ex) { - if (localCancelled) { - return false; - } } this.source = null; - A result = acc; Entry lowerEntry, higherEntry = null; while(true) { while(true) { From ddd4e286922c15d62b6f674be0485a66d80f1b1b Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Sat, 24 Oct 2015 21:36:54 +0600 Subject: [PATCH 21/42] Revert "OrderedCancellableSpliterator3: preinitialization" This reverts commit fa4ef9486ec9f45646b89f73c354943cb710fe7c. --- .../OrderedCancellableSpliterator3.java | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/src/main/java/javax/util/streamex/OrderedCancellableSpliterator3.java b/src/main/java/javax/util/streamex/OrderedCancellableSpliterator3.java index cbd7ba16..88a9d97b 100644 --- a/src/main/java/javax/util/streamex/OrderedCancellableSpliterator3.java +++ b/src/main/java/javax/util/streamex/OrderedCancellableSpliterator3.java @@ -46,7 +46,6 @@ private volatile OrderedCancellableSpliterator3 suffix; private static class Key implements Comparable { - // TODO: support keys of arbitrary length final int length; final long bits1, bits2, bits3; @@ -120,18 +119,7 @@ public boolean tryAdvance(Consumer action) { this.source = null; return false; } - A result = null; - while(true) { - Entry lowerEntry = map.lowerEntry(key); - if(lowerEntry == null || lowerEntry.getValue() == NONE) - break; - if(!map.remove(lowerEntry.getKey(), lowerEntry.getValue())) - continue; - result = result == null ? lowerEntry.getValue() : combiner.apply(lowerEntry.getValue(), result); - } - if(result == null) - result = supplier.get(); - A acc = result; + A acc = supplier.get(); try { source.forEachRemaining(t -> { accumulator.accept(acc, t); @@ -144,8 +132,12 @@ public boolean tryAdvance(Consumer action) { } }); } catch (CancelException ex) { + if (localCancelled) { + return false; + } } this.source = null; + A result = acc; Entry lowerEntry, higherEntry = null; while(true) { while(true) { From 7d3ec6be4b8c5e22564c41d4dc2356831eb27cab Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Sun, 25 Oct 2015 13:06:54 +0600 Subject: [PATCH 22/42] OrderedCancellableSpliterator3: minor optimizations --- .../OrderedCancellableSpliterator3.java | 46 ++++++++++++++----- 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/src/main/java/javax/util/streamex/OrderedCancellableSpliterator3.java b/src/main/java/javax/util/streamex/OrderedCancellableSpliterator3.java index 88a9d97b..76f0a481 100644 --- a/src/main/java/javax/util/streamex/OrderedCancellableSpliterator3.java +++ b/src/main/java/javax/util/streamex/OrderedCancellableSpliterator3.java @@ -138,50 +138,72 @@ public boolean tryAdvance(Consumer action) { } this.source = null; A result = acc; - Entry lowerEntry, higherEntry = null; + Entry lowerEntry = null, higherEntry = null; + boolean prefixFinished = prefix == null; + boolean suffixFinished = false; while(true) { - while(true) { - lowerEntry = map.lowerEntry(key); - if(lowerEntry == null || lowerEntry.getValue() == NONE) + while(!prefixFinished) { + if(lowerEntry == null) + lowerEntry = map.lowerEntry(key); + if(lowerEntry == null) { + prefixFinished = true; + break; + } + if(lowerEntry.getValue() == NONE) { break; - if(!map.remove(lowerEntry.getKey(), lowerEntry.getValue())) + } + if(!map.remove(lowerEntry.getKey(), lowerEntry.getValue())) { + lowerEntry = null; continue; + } result = combiner.apply(lowerEntry.getValue(), result); if(cancelPredicate.test(result)) { cancelSuffix(); } + lowerEntry = null; } - while(suffix != null) { - higherEntry = map.higherEntry(key); - if(higherEntry == null || higherEntry.getValue() == NONE) + while(!suffixFinished && suffix != null) { + if(higherEntry == null) + higherEntry = map.higherEntry(key); + if(higherEntry == null) { + suffixFinished = true; + break; + } + if(higherEntry.getValue() == NONE) { break; - if(!map.remove(higherEntry.getKey(), higherEntry.getValue())) + } + if(!map.remove(higherEntry.getKey(), higherEntry.getValue())) { + higherEntry = null; continue; + } result = combiner.apply(result, higherEntry.getValue()); if(cancelPredicate.test(result)) { cancelSuffix(); } + higherEntry = null; } - if(lowerEntry == null && (higherEntry == null || suffix == null)) { + if(prefixFinished && (suffixFinished || suffix == null)) { action.accept(result); return true; } map.put(key, result); - if(lowerEntry != null) { + if(!prefixFinished) { lowerEntry = map.lowerEntry(key); if(lowerEntry != null && lowerEntry.getValue() != NONE) { if(!map.replace(key, result, none())) { return false; } + higherEntry = null; continue; } } - if(higherEntry != null && suffix != null) { + if(!suffixFinished && suffix != null) { higherEntry = map.higherEntry(key); if(higherEntry != null && higherEntry.getValue() != NONE) { if(!map.replace(key, result, none())) { return false; } + lowerEntry = null; continue; } } From b85ddd424ed9cc28f1c23ff359a6e9727c762659 Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Sun, 25 Oct 2015 14:08:01 +0600 Subject: [PATCH 23/42] StreamEx/EntryStream.toList()/toListAndThen()/foldRight()/scanRight() optimized (toList is about 1.5x faster for not sized and 2-4x faster for sized stream) --- CHANGES.md | 1 + .../javax/util/streamex/AbstractStreamEx.java | 5 +- .../util/streamex/StreamExInternals.java | 26 ++++++++++ .../javax/util/streamex/StreamFactory.java | 11 ++++ .../javax/util/streamex/CustomPoolTest.java | 12 +++++ .../javax/util/streamex/StreamExTest.java | 51 ++++++++++++------- 6 files changed, 85 insertions(+), 21 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index a1fa6f4b..0ada0c2d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,6 +3,7 @@ ### 0.4.1 * Fixed: `StreamEx.cross(mapper)` now correctly handles the case when mapper returns null instead of empty stream. +* Optimized: `StreamEx/EntryStream.toList()/toListAndThen()/foldRight()/scanRight()` now faster, especially for sized stream. * Updated documentation. ### 0.4.0 diff --git a/src/main/java/javax/util/streamex/AbstractStreamEx.java b/src/main/java/javax/util/streamex/AbstractStreamEx.java index 20a2e943..efce85f1 100644 --- a/src/main/java/javax/util/streamex/AbstractStreamEx.java +++ b/src/main/java/javax/util/streamex/AbstractStreamEx.java @@ -1025,8 +1025,9 @@ public S prepend(Stream other) { * @return a {@code List} containing the elements of this stream * @see Collectors#toList() */ + @SuppressWarnings("unchecked") public List toList() { - return collect(Collectors.toList()); + return new ArrayList((Collection)new ArrayCollection(toArray())); } /** @@ -1045,7 +1046,7 @@ public List toList() { * @since 0.2.3 */ public R toListAndThen(Function, R> finisher) { - return collect(Collectors.collectingAndThen(Collectors.toList(), finisher)); + return finisher.apply(toList()); } /** diff --git a/src/main/java/javax/util/streamex/StreamExInternals.java b/src/main/java/javax/util/streamex/StreamExInternals.java index 0ef29b93..cf11b013 100644 --- a/src/main/java/javax/util/streamex/StreamExInternals.java +++ b/src/main/java/javax/util/streamex/StreamExInternals.java @@ -23,6 +23,7 @@ import java.math.BigInteger; import java.math.MathContext; import java.nio.ByteOrder; +import java.util.AbstractCollection; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.Arrays; @@ -1082,6 +1083,31 @@ static class CancelException extends Error { super(null, null, false, false); } } + + static class ArrayCollection extends AbstractCollection { + private final Object[] arr; + + ArrayCollection(Object[] arr) { + this.arr = arr; + } + + @Override + public Iterator iterator() { + return Arrays.asList(arr).iterator(); + } + + @Override + public int size() { + return arr.length; + } + + @Override + public Object[] toArray() { + // intentional contract violation here: + // this way new ArrayList(new ArrayCollection(arr)) will not copy array at all + return arr; + } + } static ObjIntConsumer joinAccumulatorInt(CharSequence delimiter) { return (sb, i) -> (sb.length() > 0 ? sb.append(delimiter) : sb).append(i); diff --git a/src/main/java/javax/util/streamex/StreamFactory.java b/src/main/java/javax/util/streamex/StreamFactory.java index b19f9042..b7f34cc4 100644 --- a/src/main/java/javax/util/streamex/StreamFactory.java +++ b/src/main/java/javax/util/streamex/StreamFactory.java @@ -15,6 +15,7 @@ */ package javax.util.streamex; +import java.util.List; import java.util.Map.Entry; import java.util.Optional; import java.util.OptionalDouble; @@ -123,6 +124,11 @@ public void forEachOrdered(Consumer> action) { public A[] toArray(IntFunction generator) { return strategy.terminate(generator, stream::toArray); } + + @Override + public R toListAndThen(Function>, R> finisher) { + return strategy.terminate(finisher, super::toListAndThen); + } @Override public Entry reduce(Entry identity, BinaryOperator> accumulator) { @@ -210,6 +216,11 @@ public A[] toArray(IntFunction generator) { return strategy.terminate(generator, stream::toArray); } + @Override + public R toListAndThen(Function, R> finisher) { + return strategy.terminate(finisher, super::toListAndThen); + } + @Override public T reduce(T identity, BinaryOperator accumulator) { return strategy.terminate(() -> stream.reduce(identity, accumulator)); diff --git a/src/test/java/javax/util/streamex/CustomPoolTest.java b/src/test/java/javax/util/streamex/CustomPoolTest.java index 201b3d82..d73c95ce 100644 --- a/src/test/java/javax/util/streamex/CustomPoolTest.java +++ b/src/test/java/javax/util/streamex/CustomPoolTest.java @@ -100,6 +100,11 @@ public void testStreamEx() { .reduce(0, (x, s) -> x + s.length(), Integer::sum)); assertEquals("aabbbcccc", StreamEx.of("aa", "bbb", "cccc").parallel(pool).peek(this::checkThread).foldLeft("", String::concat)); + assertEquals(Arrays.asList(1, 2, 3), + StreamEx.of(1, 2, 3).parallel(pool).peek(this::checkThread).toListAndThen(list -> { + this.checkThread(list); + return list; + })); } @Test @@ -144,6 +149,13 @@ public void testEntryStream() { assertEquals(new SimpleEntry<>("b", 2), array[0]); assertEquals(new SimpleEntry<>("c", 3), array[1]); + List> list = EntryStream.of("a", 1, "b", 2, "c", 3).parallel(pool).peek(this::checkThread) + .filterValues(v -> v > 1).toListAndThen(l -> { + this.checkThread(l); + return l; + }); + assertEquals(Arrays.asList(array), list); + assertEquals( new SimpleEntry<>("abc", 6), EntryStream.of("a", 1, "b", 2, "c", 3).parallel(pool).peek(this::checkThread) diff --git a/src/test/java/javax/util/streamex/StreamExTest.java b/src/test/java/javax/util/streamex/StreamExTest.java index 9c71c3e6..81731599 100644 --- a/src/test/java/javax/util/streamex/StreamExTest.java +++ b/src/test/java/javax/util/streamex/StreamExTest.java @@ -165,6 +165,16 @@ public void testBasics() { assertFalse(StreamEx.of("a", "b", "c").unordered().spliterator().hasCharacteristics(Spliterator.ORDERED)); } + @Test + public void testToList() { + List list = StreamEx.of(1, 2, 3).toList(); + // Test that returned list is mutable + List list2 = StreamEx.of(4, 5, 6).parallel().toList(); + list2.add(7); + list.addAll(list2); + assertEquals(Arrays.asList(1, 2, 3, 4, 5, 6, 7), list); + } + @Test public void testFlatMap() { assertArrayEquals(new int[] { 0, 0, 1, 0, 0, 1, 0, 0 }, @@ -185,7 +195,7 @@ public void testAndThen() { HashSet set = StreamEx.of("a", "bb", "ccc").toListAndThen(HashSet::new); assertEquals(3, set.size()); assertTrue(set.contains("bb")); - + ArrayList list = StreamEx.of("a", "bb", "ccc").toSetAndThen(ArrayList::new); assertEquals(3, list.size()); assertTrue(list.contains("bb")); @@ -220,16 +230,16 @@ public void testToMap() { for (StreamExSupplier supplier : streamEx(() -> Stream.of("a", "bb", "ccc", "dd"))) { Map seqMap3 = supplier.get().toMap(String::length, Function.identity(), String::concat); assertEquals(supplier.toString(), expected3, seqMap3); - + try { supplier.get().toMap(String::length, Function.identity()); - } catch(IllegalStateException ex) { - if(!ex.getMessage().equals("Duplicate entry for key '2' (attempt to merge values 'bb' and 'dd')") - && !ex.getMessage().equals("Duplicate entry for key '2' (attempt to merge values 'dd' and 'bb')")) - fail(supplier+": wrong exception message: "+ex.getMessage()); + } catch (IllegalStateException ex) { + if (!ex.getMessage().equals("Duplicate entry for key '2' (attempt to merge values 'bb' and 'dd')") + && !ex.getMessage().equals("Duplicate entry for key '2' (attempt to merge values 'dd' and 'bb')")) + fail(supplier + ": wrong exception message: " + ex.getMessage()); continue; } - fail(supplier+": no exception"); + fail(supplier + ": no exception"); } } @@ -260,18 +270,19 @@ public void testToSortedMap() { expected3.put(2, "bbdd"); expected3.put(3, "ccc"); for (StreamExSupplier supplier : streamEx(() -> Stream.of("a", "bb", "ccc", "dd"))) { - SortedMap seqMap3 = supplier.get().toSortedMap(String::length, Function.identity(), String::concat); + SortedMap seqMap3 = supplier.get().toSortedMap(String::length, Function.identity(), + String::concat); assertEquals(supplier.toString(), expected3, seqMap3); - + try { supplier.get().toSortedMap(String::length, Function.identity()); - } catch(IllegalStateException ex) { - if(!ex.getMessage().equals("Duplicate entry for key '2' (attempt to merge values 'bb' and 'dd')") - && !ex.getMessage().equals("Duplicate entry for key '2' (attempt to merge values 'dd' and 'bb')")) - fail(supplier+": wrong exception message: "+ex.getMessage()); + } catch (IllegalStateException ex) { + if (!ex.getMessage().equals("Duplicate entry for key '2' (attempt to merge values 'bb' and 'dd')") + && !ex.getMessage().equals("Duplicate entry for key '2' (attempt to merge values 'dd' and 'bb')")) + fail(supplier + ": wrong exception message: " + ex.getMessage()); continue; } - fail(supplier+": no exception"); + fail(supplier + ": no exception"); } } @@ -286,13 +297,14 @@ public void testGroupingBy() { expectedMapSet.put(1, new HashSet<>(Arrays.asList("a"))); expectedMapSet.put(2, new HashSet<>(Arrays.asList("bb", "dd"))); expectedMapSet.put(3, new HashSet<>(Arrays.asList("ccc"))); - - for(StreamExSupplier supplier : streamEx(() -> StreamEx.of("a", "bb", "dd", "ccc"))) { + + for (StreamExSupplier supplier : streamEx(() -> StreamEx.of("a", "bb", "dd", "ccc"))) { assertEquals(supplier.toString(), expected, supplier.get().groupingBy(String::length)); Map> map = supplier.get().groupingTo(String::length, LinkedList::new); assertEquals(supplier.toString(), expected, map); assertTrue(map.get(1) instanceof LinkedList); - assertEquals(supplier.toString(), expectedMapSet, supplier.get().groupingBy(String::length, Collectors.toSet())); + assertEquals(supplier.toString(), expectedMapSet, + supplier.get().groupingBy(String::length, Collectors.toSet())); assertEquals(supplier.toString(), expectedMapSet, supplier.get().groupingBy(String::length, HashMap::new, Collectors.toSet())); ConcurrentHashMap> chm = supplier.get().groupingBy(String::length, @@ -367,7 +379,7 @@ public void testAppend() { List list = Arrays.asList(1, 2, 3, 4); assertEquals(Arrays.asList(1.0, 2, 3L, 1, 2, 3, 4), StreamEx.of(1.0, 2, 3L).append(list).toList()); - + StreamEx s = StreamEx.of(1, 2, 3); assertSame(s, s.append()); assertSame(s, s.append(Collections.emptyList())); @@ -890,7 +902,8 @@ public void testCross() { .mapKeyValue((input, output) -> input + "->" + output).joining(", ")); assertEquals("", StreamEx.of(inputs).cross(Collections.emptyList()).join("->").joining(", ")); assertEquals("i-i, j-j, k-k", StreamEx.of(inputs).cross(Stream::of).join("-").joining(", ")); - assertEquals("j-j, k-k", StreamEx.of(inputs).cross(x -> x.equals("i") ? null : Stream.of(x)).join("-").joining(", ")); + assertEquals("j-j, k-k", + StreamEx.of(inputs).cross(x -> x.equals("i") ? null : Stream.of(x)).join("-").joining(", ")); } @Test From 46efc8851f4bb7777da9db7991a9a8378227dcb2 Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Tue, 27 Oct 2015 10:11:48 +0600 Subject: [PATCH 24/42] mapFirst/mapLast: proof-of-concept implementation --- .../java/javax/util/streamex/DoubleStreamEx.java | 8 ++++++++ src/main/java/javax/util/streamex/IntStreamEx.java | 8 ++++++++ .../java/javax/util/streamex/LongStreamEx.java | 8 ++++++++ src/main/java/javax/util/streamex/StreamEx.java | 14 +++++++++++++- .../java/javax/util/streamex/IntStreamExTest.java | 7 +++++++ 5 files changed, 44 insertions(+), 1 deletion(-) diff --git a/src/main/java/javax/util/streamex/DoubleStreamEx.java b/src/main/java/javax/util/streamex/DoubleStreamEx.java index daa8fcfd..914db872 100644 --- a/src/main/java/javax/util/streamex/DoubleStreamEx.java +++ b/src/main/java/javax/util/streamex/DoubleStreamEx.java @@ -191,6 +191,14 @@ public DoubleStreamEx map(DoubleUnaryOperator mapper) { return strategy().newDoubleStreamEx(stream.map(mapper)); } + public DoubleStreamEx mapFirst(DoubleUnaryOperator mapper) { + return boxed().mapFirst(mapper::applyAsDouble).mapToDouble(Double::doubleValue); + } + + public DoubleStreamEx mapLast(DoubleUnaryOperator mapper) { + return boxed().mapLast(mapper::applyAsDouble).mapToDouble(Double::doubleValue); + } + @Override public StreamEx mapToObj(DoubleFunction mapper) { return strategy().newStreamEx(stream.mapToObj(mapper)); diff --git a/src/main/java/javax/util/streamex/IntStreamEx.java b/src/main/java/javax/util/streamex/IntStreamEx.java index 843b96d1..9be904bc 100644 --- a/src/main/java/javax/util/streamex/IntStreamEx.java +++ b/src/main/java/javax/util/streamex/IntStreamEx.java @@ -1509,7 +1509,15 @@ public IntStreamEx dropWhile(IntPredicate predicate) { } return delegate(new TDOfInt(stream.spliterator(), true, predicate)); } + + public IntStreamEx mapFirst(IntUnaryOperator mapper) { + return mapToObj(Integer::new).mapFirst(mapper::applyAsInt).mapToInt(Integer::intValue); + } + public IntStreamEx mapLast(IntUnaryOperator mapper) { + return mapToObj(Integer::new).mapLast(mapper::applyAsInt).mapToInt(Integer::intValue); + } + /** * Returns an empty sequential {@code IntStreamEx}. * diff --git a/src/main/java/javax/util/streamex/LongStreamEx.java b/src/main/java/javax/util/streamex/LongStreamEx.java index a92a5deb..223e5273 100644 --- a/src/main/java/javax/util/streamex/LongStreamEx.java +++ b/src/main/java/javax/util/streamex/LongStreamEx.java @@ -222,6 +222,14 @@ public LongStreamEx map(LongUnaryOperator mapper) { return strategy().newLongStreamEx(stream.map(mapper)); } + public LongStreamEx mapFirst(LongUnaryOperator mapper) { + return mapToObj(Long::new).mapFirst(mapper::applyAsLong).mapToLong(Long::longValue); + } + + public LongStreamEx mapLast(LongUnaryOperator mapper) { + return mapToObj(Long::new).mapLast(mapper::applyAsLong).mapToLong(Long::longValue); + } + @Override public StreamEx mapToObj(LongFunction mapper) { return strategy().newStreamEx(stream.mapToObj(mapper)); diff --git a/src/main/java/javax/util/streamex/StreamEx.java b/src/main/java/javax/util/streamex/StreamEx.java index d7b68e7c..1139911b 100644 --- a/src/main/java/javax/util/streamex/StreamEx.java +++ b/src/main/java/javax/util/streamex/StreamEx.java @@ -195,6 +195,18 @@ public EntryStream mapToEntry(Function keyM stream.map(e -> new SimpleImmutableEntry<>(keyMapper.apply(e), valueMapper.apply(e)))); } + public StreamEx mapFirst(Function mapper) { + return strategy().newStreamEx( + delegate(new PairSpliterator.PSOfRef((a, b) -> (a == NONE ? mapper.apply(b) : b), Stream.concat( + Stream.of(none()), stream).spliterator()))); + } + + public StreamEx mapLast(Function mapper) { + return strategy().newStreamEx( + delegate(new PairSpliterator.PSOfRef((a, b) -> (b == NONE ? mapper.apply(a) : a), Stream.concat( + stream, Stream.of(none())).spliterator()))); + } + /** * Creates a new {@code EntryStream} populated from entries of maps produced * by supplied mapper function which is applied to the every element of this @@ -1377,7 +1389,7 @@ public StreamEx intervalMap(BiPredicate sameInterva return left; }).map(pair -> mapper.apply(pair.a, pair.b)); } - + /** * Returns an empty sequential {@code StreamEx}. * diff --git a/src/test/java/javax/util/streamex/IntStreamExTest.java b/src/test/java/javax/util/streamex/IntStreamExTest.java index 3a2e200f..af9fb900 100644 --- a/src/test/java/javax/util/streamex/IntStreamExTest.java +++ b/src/test/java/javax/util/streamex/IntStreamExTest.java @@ -611,4 +611,11 @@ public void testFoldLeft() { assertEquals(144, IntStreamEx.rangeClosed(1, 3).foldLeft(0, accumulator)); assertEquals(144, IntStreamEx.rangeClosed(1, 3).parallel().foldLeft(0, accumulator)); } + + @Test + public void testMapFirst() { + // capitalize + String str = "testString"; + assertEquals("TestString", IntStreamEx.ofCodePoints(str).mapFirst(Character::toUpperCase).codePointsToString()); + } } From 37ff340d48dc78e1e9f67e790064c16575b91326 Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Tue, 27 Oct 2015 10:15:21 +0600 Subject: [PATCH 25/42] OrderedCancellableSpliterator3 removed (it does not faster than lock-based OrderedCancellableSpliterator2, but more risky and has limited splitting). OrderedCancellableSpliterator2 replaces old OrderedCancellableSpliterator; now it's the main implementation --- .../javax/util/streamex/AbstractStreamEx.java | 78 +---- .../OrderedCancellableSpliterator.java | 119 +++++--- .../OrderedCancellableSpliterator2.java | 185 ------------ .../OrderedCancellableSpliterator3.java | 282 ------------------ .../OrderedCancellableSpliteratorTest.java | 21 +- 5 files changed, 86 insertions(+), 599 deletions(-) delete mode 100644 src/main/java/javax/util/streamex/OrderedCancellableSpliterator2.java delete mode 100644 src/main/java/javax/util/streamex/OrderedCancellableSpliterator3.java diff --git a/src/main/java/javax/util/streamex/AbstractStreamEx.java b/src/main/java/javax/util/streamex/AbstractStreamEx.java index ae1a72ca..9eec6467 100644 --- a/src/main/java/javax/util/streamex/AbstractStreamEx.java +++ b/src/main/java/javax/util/streamex/AbstractStreamEx.java @@ -301,89 +301,13 @@ public R collect(Collector collector) { || c.characteristics().contains(Characteristics.UNORDERED)) { spltr = new UnorderedCancellableSpliterator<>(spliterator, c.supplier(), acc, combiner, finished); } else { - spltr = new OrderedCancellableSpliterator3<>(spliterator, c.supplier(), acc, combiner, finished); + spltr = new OrderedCancellableSpliterator<>(spliterator, c.supplier(), acc, combiner, finished); } return c.finisher().apply(strategy().newStreamEx(StreamSupport.stream(spltr, true)).findFirst().get()); } return rawCollect(collector); } - public R collect2(Collector collector) { - if (collector instanceof CancellableCollector) { - CancellableCollector c = (CancellableCollector) collector; - BiConsumer acc = c.accumulator(); - Predicate finished = c.finished(); - BinaryOperator combiner = c.combiner(); - Spliterator spliterator = stream.spliterator(); - if (!isParallel()) { - A a = c.supplier().get(); - if (!finished.test(a)) { - try { - // forEachRemaining can be much faster - // and take much less memory than tryAdvance for certain - // spliterators - spliterator.forEachRemaining(e -> { - acc.accept(a, e); - if (finished.test(a)) - throw new CancelException(); - }); - } catch (CancelException ex) { - // ignore - } - } - return c.finisher().apply(a); - } - Spliterator spltr; - if (!spliterator.hasCharacteristics(Spliterator.ORDERED) - || c.characteristics().contains(Characteristics.UNORDERED)) { - spltr = new UnorderedCancellableSpliterator<>(spliterator, c.supplier(), acc, combiner, finished); - } else { - spltr = new OrderedCancellableSpliterator2<>(spliterator, c.supplier(), acc, combiner, finished); - } - return c.finisher().apply(strategy().newStreamEx(StreamSupport.stream(spltr, true)).findFirst().get()); - } - return rawCollect(collector); - } - - public R collectOld(Collector collector) { - if (collector instanceof CancellableCollector) { - CancellableCollector c = (CancellableCollector) collector; - BiConsumer acc = c.accumulator(); - Predicate finished = c.finished(); - BinaryOperator combiner = c.combiner(); - Spliterator spliterator = stream.spliterator(); - if (!isParallel()) { - A a = c.supplier().get(); - if (!finished.test(a)) { - try { - // forEachRemaining can be much faster - // and take much less memory than tryAdvance for certain - // spliterators - spliterator.forEachRemaining(e -> { - acc.accept(a, e); - if (finished.test(a)) - throw new CancelException(); - }); - } catch (CancelException ex) { - // ignore - } - } - return c.finisher().apply(a); - } - Spliterator spltr; - if (!spliterator.hasCharacteristics(Spliterator.ORDERED) - || c.characteristics().contains(Characteristics.UNORDERED)) { - spltr = new UnorderedCancellableSpliterator<>(spliterator, c.supplier(), acc, combiner, finished); - return c.finisher().apply(strategy().newStreamEx(StreamSupport.stream(spltr, true)).findAny().get()); - } else { - spltr = new OrderedCancellableSpliterator<>(spliterator, c.supplier(), acc, finished); - return c.finisher().apply( - strategy().newStreamEx(StreamSupport.stream(spltr, true)).reduce(combiner).get()); - } - } - return rawCollect(collector); - } - @Override public Optional min(Comparator comparator) { return reduce(BinaryOperator.minBy(comparator)); diff --git a/src/main/java/javax/util/streamex/OrderedCancellableSpliterator.java b/src/main/java/javax/util/streamex/OrderedCancellableSpliterator.java index 9576e7ca..f12ab0c8 100644 --- a/src/main/java/javax/util/streamex/OrderedCancellableSpliterator.java +++ b/src/main/java/javax/util/streamex/OrderedCancellableSpliterator.java @@ -15,9 +15,10 @@ */ package javax.util.streamex; +import java.util.ArrayDeque; import java.util.Spliterator; - import java.util.function.BiConsumer; +import java.util.function.BinaryOperator; import java.util.function.Consumer; import java.util.function.Predicate; import java.util.function.Supplier; @@ -29,18 +30,22 @@ */ /* package */final class OrderedCancellableSpliterator implements Spliterator, Cloneable { private Spliterator source; + private final Object lock = new Object(); private final BiConsumer accumulator; private final Predicate cancelPredicate; + private final BinaryOperator combiner; private final Supplier supplier; private volatile boolean localCancelled; private OrderedCancellableSpliterator prefix; - private volatile OrderedCancellableSpliterator suffix; + private OrderedCancellableSpliterator suffix; + private A payload; OrderedCancellableSpliterator(Spliterator source, Supplier supplier, BiConsumer accumulator, - Predicate cancelPredicate) { + BinaryOperator combiner, Predicate cancelPredicate) { this.source = source; this.supplier = supplier; this.accumulator = accumulator; + this.combiner = combiner; this.cancelPredicate = cancelPredicate; } @@ -56,35 +61,83 @@ public boolean tryAdvance(Consumer action) { source.forEachRemaining(t -> { accumulator.accept(acc, t); if (cancelPredicate.test(acc)) { - this.source = null; - cancel(); + cancelSuffix(); throw new CancelException(); } if (localCancelled) { throw new CancelException(); } }); - this.source = null; - } - catch(CancelException ex) { - // ignore - } - if(this.source == null) { - action.accept(acc); - return true; + } catch (CancelException ex) { + if (localCancelled) { + return false; + } } this.source = null; + A result = acc; + while (true) { + if (prefix == null && suffix == null) { + action.accept(result); + return true; + } + ArrayDeque res = new ArrayDeque<>(); + res.offer(result); + synchronized (lock) { + if (localCancelled) + return false; + OrderedCancellableSpliterator s = prefix; + while (s != null) { + if (s.payload == null) + break; + res.offerFirst(s.payload); + s = s.prefix; + } + prefix = s; + if (s != null) { + s.suffix = this; + } + s = suffix; + while (s != null) { + if (s.payload == null) + break; + res.offerLast(s.payload); + s = s.suffix; + } + suffix = s; + if (s != null) { + s.prefix = this; + } + if (res.size() == 1) { + if (prefix == null && suffix == null) { + action.accept(result); + return true; + } + this.payload = result; + break; + } + } + result = res.pollFirst(); + while (!res.isEmpty()) { + result = combiner.apply(result, res.pollFirst()); + if (cancelPredicate.test(result)) { + cancelSuffix(); + } + } + } return false; } - private void cancel() { - this.localCancelled = true; - OrderedCancellableSpliterator suffix = this.suffix; - // Due to possible race with trySplit some spliterators can - // be skipped. This is handled in trySplit - while (suffix != null && !suffix.localCancelled) { - suffix.localCancelled = true; - suffix = suffix.suffix; + private void cancelSuffix() { + if (this.suffix == null) + return; + synchronized (lock) { + OrderedCancellableSpliterator suffix = this.suffix; + while (suffix != null && !suffix.localCancelled) { + suffix.prefix = null; + suffix.localCancelled = true; + suffix = suffix.suffix; + } + this.suffix = null; } } @@ -104,21 +157,17 @@ public Spliterator trySplit() { return null; } try { - @SuppressWarnings("unchecked") - OrderedCancellableSpliterator result = (OrderedCancellableSpliterator) this.clone(); - result.source = prefix; - this.prefix = result; - result.suffix = this; - OrderedCancellableSpliterator prefixPrefix = result.prefix; - if (prefixPrefix != null) - prefixPrefix.suffix = result; - if (this.localCancelled || result.localCancelled) { - // we can end up here due to the race with suffix updates in - // tryAdvance - this.localCancelled = result.localCancelled = true; - return null; + synchronized (lock) { + @SuppressWarnings("unchecked") + OrderedCancellableSpliterator result = (OrderedCancellableSpliterator) this.clone(); + result.source = prefix; + this.prefix = result; + result.suffix = this; + OrderedCancellableSpliterator prefixPrefix = result.prefix; + if (prefixPrefix != null) + prefixPrefix.suffix = result; + return result; } - return result; } catch (CloneNotSupportedException e) { throw new InternalError(); } diff --git a/src/main/java/javax/util/streamex/OrderedCancellableSpliterator2.java b/src/main/java/javax/util/streamex/OrderedCancellableSpliterator2.java deleted file mode 100644 index 453a5512..00000000 --- a/src/main/java/javax/util/streamex/OrderedCancellableSpliterator2.java +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Copyright 2015 Tagir Valeev - * - * 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 javax.util.streamex; - -import java.util.ArrayDeque; -import java.util.Spliterator; -import java.util.function.BiConsumer; -import java.util.function.BinaryOperator; -import java.util.function.Consumer; -import java.util.function.Predicate; -import java.util.function.Supplier; - -import static javax.util.streamex.StreamExInternals.*; - -/** - * @author Tagir Valeev - */ -/* package */final class OrderedCancellableSpliterator2 implements Spliterator, Cloneable { - private Spliterator source; - private final Object lock = new Object(); - private final BiConsumer accumulator; - private final Predicate cancelPredicate; - private final BinaryOperator combiner; - private final Supplier supplier; - private volatile boolean localCancelled; - private OrderedCancellableSpliterator2 prefix; - private OrderedCancellableSpliterator2 suffix; - private A payload; - - OrderedCancellableSpliterator2(Spliterator source, Supplier supplier, BiConsumer accumulator, - BinaryOperator combiner, Predicate cancelPredicate) { - this.source = source; - this.supplier = supplier; - this.accumulator = accumulator; - this.combiner = combiner; - this.cancelPredicate = cancelPredicate; - } - - @Override - public boolean tryAdvance(Consumer action) { - Spliterator source = this.source; - if (source == null || localCancelled) { - this.source = null; - return false; - } - A acc = supplier.get(); - try { - source.forEachRemaining(t -> { - accumulator.accept(acc, t); - if (cancelPredicate.test(acc)) { - cancelSuffix(); - throw new CancelException(); - } - if (localCancelled) { - throw new CancelException(); - } - }); - } catch (CancelException ex) { - if (localCancelled) { - return false; - } - } - this.source = null; - A result = acc; - while (true) { - if (prefix == null && suffix == null) { - action.accept(result); - return true; - } - ArrayDeque res = new ArrayDeque<>(); - res.offer(result); - synchronized (lock) { - if (localCancelled) - return false; - OrderedCancellableSpliterator2 s = prefix; - while (s != null) { - if (s.payload == null) - break; - res.offerFirst(s.payload); - s = s.prefix; - } - prefix = s; - if (s != null) { - s.suffix = this; - } - s = suffix; - while (s != null) { - if (s.payload == null) - break; - res.offerLast(s.payload); - s = s.suffix; - } - suffix = s; - if (s != null) { - s.prefix = this; - } - if (res.size() == 1) { - if (prefix == null && suffix == null) { - action.accept(result); - return true; - } - this.payload = result; - break; - } - } - result = res.pollFirst(); - while (!res.isEmpty()) { - result = combiner.apply(result, res.pollFirst()); - if (cancelPredicate.test(result)) { - cancelSuffix(); - } - } - } - return false; - } - - private void cancelSuffix() { - if (this.suffix == null) - return; - synchronized (lock) { - OrderedCancellableSpliterator2 suffix = this.suffix; - while (suffix != null && !suffix.localCancelled) { - suffix.prefix = null; - suffix.localCancelled = true; - suffix = suffix.suffix; - } - this.suffix = null; - } - } - - @Override - public void forEachRemaining(Consumer action) { - tryAdvance(action); - } - - @Override - public Spliterator trySplit() { - if (source == null || localCancelled) { - source = null; - return null; - } - Spliterator prefix = source.trySplit(); - if (prefix == null) { - return null; - } - try { - synchronized (lock) { - @SuppressWarnings("unchecked") - OrderedCancellableSpliterator2 result = (OrderedCancellableSpliterator2) this.clone(); - result.source = prefix; - this.prefix = result; - result.suffix = this; - OrderedCancellableSpliterator2 prefixPrefix = result.prefix; - if (prefixPrefix != null) - prefixPrefix.suffix = result; - return result; - } - } catch (CloneNotSupportedException e) { - throw new InternalError(); - } - } - - @Override - public long estimateSize() { - return source == null ? 0 : source.estimateSize(); - } - - @Override - public int characteristics() { - return source == null ? SIZED : ORDERED; - } -} diff --git a/src/main/java/javax/util/streamex/OrderedCancellableSpliterator3.java b/src/main/java/javax/util/streamex/OrderedCancellableSpliterator3.java deleted file mode 100644 index 76f0a481..00000000 --- a/src/main/java/javax/util/streamex/OrderedCancellableSpliterator3.java +++ /dev/null @@ -1,282 +0,0 @@ -/* - * Copyright 2015 Tagir Valeev - * - * 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 javax.util.streamex; - -import java.util.Map.Entry; -import java.util.Spliterator; -import java.util.concurrent.ConcurrentSkipListMap; -import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; -import java.util.function.BiConsumer; -import java.util.function.BinaryOperator; -import java.util.function.Consumer; -import java.util.function.Predicate; -import java.util.function.Supplier; - -import static javax.util.streamex.StreamExInternals.*; - -/** - * @author Tagir Valeev - */ -/* package */final class OrderedCancellableSpliterator3 implements Spliterator, Cloneable { - @SuppressWarnings("rawtypes") - private static final AtomicReferenceFieldUpdater updater - = AtomicReferenceFieldUpdater.newUpdater(OrderedCancellableSpliterator3.class, OrderedCancellableSpliterator3.class, "suffix"); - private Spliterator source; - private final ConcurrentSkipListMap map = new ConcurrentSkipListMap<>(); - private final BiConsumer accumulator; - private final Predicate cancelPredicate; - private final BinaryOperator combiner; - private final Supplier supplier; - private Key key = Key.ROOT; - private volatile boolean localCancelled; - private OrderedCancellableSpliterator3 prefix; - private volatile OrderedCancellableSpliterator3 suffix; - - private static class Key implements Comparable { - final int length; - final long bits1, bits2, bits3; - - static final int MAX_LENGTH = Long.SIZE*3; - static final Key ROOT = new Key(null, false); - - private Key(Key parent, boolean left) { - long b1 = 0, b2 = 0, b3 = 0; - int l = 0; - if(parent != null) { - l = parent.length+1; - b1 = parent.bits1; - b2 = parent.bits2; - b3 = parent.bits3; - if(!left) { - if(l < Long.SIZE) { - b1 |= (1L << (Long.SIZE-l)); - } else if(l < 2*Long.SIZE) { - b2 |= (1L << (2*Long.SIZE-l)); - } else { - b3 |= (1L << (MAX_LENGTH-l)); - } - } - } - this.length = l; - this.bits1 = b1; - this.bits2 = b2; - this.bits3 = b3; - } - - Key left() { - return new Key(this, true); - } - - Key right() { - return new Key(this, false); - } - - @Override - public int compareTo(Key o) { - int res = Long.compareUnsigned(bits1, o.bits1); - if(res == 0) - res = Long.compareUnsigned(bits2, o.bits2); - if(res == 0) - res = Long.compareUnsigned(bits3, o.bits3); - if(res == 0) - res = Integer.compare(o.length, length); - return res; - } - - @Override - public String toString() { - return String.format("%64s%64s%64s", Long.toBinaryString(bits1), Long.toBinaryString(bits2), Long.toBinaryString(bits3)).substring(0, length).replace(' ', '0'); - } - } - - OrderedCancellableSpliterator3(Spliterator source, Supplier supplier, BiConsumer accumulator, - BinaryOperator combiner, Predicate cancelPredicate) { - this.source = source; - this.supplier = supplier; - this.accumulator = accumulator; - this.combiner = combiner; - this.cancelPredicate = cancelPredicate; - this.map.put(key, none()); - } - - @Override - public boolean tryAdvance(Consumer action) { - Spliterator source = this.source; - if (source == null || localCancelled) { - this.source = null; - return false; - } - A acc = supplier.get(); - try { - source.forEachRemaining(t -> { - accumulator.accept(acc, t); - if (cancelPredicate.test(acc)) { - cancelSuffix(); - throw new CancelException(); - } - if (localCancelled) { - throw new CancelException(); - } - }); - } catch (CancelException ex) { - if (localCancelled) { - return false; - } - } - this.source = null; - A result = acc; - Entry lowerEntry = null, higherEntry = null; - boolean prefixFinished = prefix == null; - boolean suffixFinished = false; - while(true) { - while(!prefixFinished) { - if(lowerEntry == null) - lowerEntry = map.lowerEntry(key); - if(lowerEntry == null) { - prefixFinished = true; - break; - } - if(lowerEntry.getValue() == NONE) { - break; - } - if(!map.remove(lowerEntry.getKey(), lowerEntry.getValue())) { - lowerEntry = null; - continue; - } - result = combiner.apply(lowerEntry.getValue(), result); - if(cancelPredicate.test(result)) { - cancelSuffix(); - } - lowerEntry = null; - } - while(!suffixFinished && suffix != null) { - if(higherEntry == null) - higherEntry = map.higherEntry(key); - if(higherEntry == null) { - suffixFinished = true; - break; - } - if(higherEntry.getValue() == NONE) { - break; - } - if(!map.remove(higherEntry.getKey(), higherEntry.getValue())) { - higherEntry = null; - continue; - } - result = combiner.apply(result, higherEntry.getValue()); - if(cancelPredicate.test(result)) { - cancelSuffix(); - } - higherEntry = null; - } - if(prefixFinished && (suffixFinished || suffix == null)) { - action.accept(result); - return true; - } - map.put(key, result); - if(!prefixFinished) { - lowerEntry = map.lowerEntry(key); - if(lowerEntry != null && lowerEntry.getValue() != NONE) { - if(!map.replace(key, result, none())) { - return false; - } - higherEntry = null; - continue; - } - } - if(!suffixFinished && suffix != null) { - higherEntry = map.higherEntry(key); - if(higherEntry != null && higherEntry.getValue() != NONE) { - if(!map.replace(key, result, none())) { - return false; - } - lowerEntry = null; - continue; - } - } - return false; - } - } - - private void cancelSuffix() { - if (this.suffix == null) - return; - OrderedCancellableSpliterator3 suffix = this.suffix; - while (suffix != null && !suffix.localCancelled) { - suffix.localCancelled = true; - OrderedCancellableSpliterator3 next = suffix.suffix; - suffix.suffix = null; - suffix = next; - } - this.suffix = null; - } - - @Override - public void forEachRemaining(Consumer action) { - tryAdvance(action); - } - - @Override - public Spliterator trySplit() { - if (localCancelled) { - source = null; - return null; - } - if(key.length == Key.MAX_LENGTH || source == null) { - return null; - } - Spliterator prefix = source.trySplit(); - if (prefix == null) { - return null; - } - try { - Key left = key.left(); - Key right = key.right(); - @SuppressWarnings("unchecked") - OrderedCancellableSpliterator3 result = (OrderedCancellableSpliterator3) this.clone(); - result.source = prefix; - this.prefix = result; - result.suffix = this; - OrderedCancellableSpliterator3 prefixPrefix = result.prefix; - if (prefixPrefix != null) - updater.compareAndSet(prefixPrefix, this, result); - if (this.localCancelled || result.localCancelled) { - // we can end up here due to the race with suffix updates in - // tryAdvance - this.localCancelled = result.localCancelled = true; - return null; - } - map.put(left, none()); - map.put(right, none()); - map.remove(this.key); - this.key = right; - result.key = left; - return result; - } catch (CloneNotSupportedException e) { - throw new InternalError(); - } - } - - @Override - public long estimateSize() { - return source == null ? 0 : source.estimateSize(); - } - - @Override - public int characteristics() { - return source == null ? SIZED : ORDERED; - } -} diff --git a/src/test/java/javax/util/streamex/OrderedCancellableSpliteratorTest.java b/src/test/java/javax/util/streamex/OrderedCancellableSpliteratorTest.java index 98c68a46..a4b53d0f 100644 --- a/src/test/java/javax/util/streamex/OrderedCancellableSpliteratorTest.java +++ b/src/test/java/javax/util/streamex/OrderedCancellableSpliteratorTest.java @@ -46,26 +46,7 @@ public void testSpliterator() { List input = IntStreamEx.range(30).boxed().toList(); List expected = IntStreamEx.range(limit).boxed().toList(); checkSpliterator("head-short-circuit", Collections.singletonList(expected), - () -> new OrderedCancellableSpliterator2<>( - input.spliterator(), s, a, c, p)); - } - - @Test - public void testSpliteratorMap() { - int limit = 10; - Supplier> s = ArrayList::new; - BiConsumer, Integer> a = (acc, t) -> { - if(acc.size() < limit) acc.add(t); - }; - BinaryOperator> c = (a1, a2) -> { - a2.forEach(t -> a.accept(a1, t)); - return a1; - }; - Predicate> p = acc -> acc.size() >= limit; - List input = IntStreamEx.range(30).boxed().toList(); - List expected = IntStreamEx.range(limit).boxed().toList(); - checkSpliterator("head-short-circuit", Collections.singletonList(expected), - () -> new OrderedCancellableSpliterator3<>( + () -> new OrderedCancellableSpliterator<>( input.spliterator(), s, a, c, p)); } } From 400f4effd40ac3f91754a0524c214f4d48de45bf Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Tue, 27 Oct 2015 10:19:26 +0600 Subject: [PATCH 26/42] StreamEx: fixed error (seems that it compiled in different way in ECJ and Javac) --- src/main/java/javax/util/streamex/StreamEx.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/java/javax/util/streamex/StreamEx.java b/src/main/java/javax/util/streamex/StreamEx.java index 1139911b..9c42c34b 100644 --- a/src/main/java/javax/util/streamex/StreamEx.java +++ b/src/main/java/javax/util/streamex/StreamEx.java @@ -196,15 +196,19 @@ public EntryStream mapToEntry(Function keyM } public StreamEx mapFirst(Function mapper) { + @SuppressWarnings("unchecked") + Stream none = Stream.of((T)NONE); return strategy().newStreamEx( delegate(new PairSpliterator.PSOfRef((a, b) -> (a == NONE ? mapper.apply(b) : b), Stream.concat( - Stream.of(none()), stream).spliterator()))); + none, stream).spliterator()))); } public StreamEx mapLast(Function mapper) { + @SuppressWarnings("unchecked") + Stream none = Stream.of((T)NONE); return strategy().newStreamEx( delegate(new PairSpliterator.PSOfRef((a, b) -> (b == NONE ? mapper.apply(a) : a), Stream.concat( - stream, Stream.of(none())).spliterator()))); + stream, none).spliterator()))); } /** From 2079dd16ab0aef592fc78b0a33e0d7850c58828d Mon Sep 17 00:00:00 2001 From: Nikita Mandrik Date: Tue, 27 Oct 2015 10:44:27 +0600 Subject: [PATCH 27/42] MoreCollectors#onlyOne: types added fixes compilation error in Eclipse Mars --- src/main/java/javax/util/streamex/MoreCollectors.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/javax/util/streamex/MoreCollectors.java b/src/main/java/javax/util/streamex/MoreCollectors.java index 63d169f7..4554b650 100644 --- a/src/main/java/javax/util/streamex/MoreCollectors.java +++ b/src/main/java/javax/util/streamex/MoreCollectors.java @@ -512,7 +512,7 @@ private MoreCollectors() { * @since 0.4.0 */ public static Collector> onlyOne() { - return new CancellableCollectorImpl<>(() -> new Box>(null), + return new CancellableCollectorImpl>, Optional>(() -> new Box>(null), (box, t) -> box.a = box.a == null ? Optional.of(t) : Optional.empty(), (box1, box2) -> box1.a == null ? box2 : box2.a == null ? box1 : new Box<>(Optional.empty()), box -> box.a == null ? Optional.empty() : box.a, box -> box.a != null && !box.a.isPresent(), From d730a11f421f65df62270a2fb16bae157f5ed922 Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Tue, 27 Oct 2015 11:08:37 +0600 Subject: [PATCH 28/42] AbstractStreamEx: toList/toSet: documentation update (mutability is guaranteed) --- .../javax/util/streamex/AbstractStreamEx.java | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/main/java/javax/util/streamex/AbstractStreamEx.java b/src/main/java/javax/util/streamex/AbstractStreamEx.java index 513106be..7337cae3 100644 --- a/src/main/java/javax/util/streamex/AbstractStreamEx.java +++ b/src/main/java/javax/util/streamex/AbstractStreamEx.java @@ -82,11 +82,11 @@ final > M toMapThrowing(Function> void addToMap(M map, K key, V val) { V oldVal = map.putIfAbsent(key, val); if (oldVal != null) { - throw new IllegalStateException("Duplicate entry for key '" + key + "' (attempt to merge values '" - + oldVal + "' and '" + val + "')"); + throw new IllegalStateException("Duplicate entry for key '" + key + "' (attempt to merge values '" + oldVal + + "' and '" + val + "')"); } } - + R rawCollect(Collector collector) { return stream.collect(collector); } @@ -1012,10 +1012,11 @@ public S prepend(Stream other) { } /** - * Returns a {@link List} containing the elements of this stream. There are - * no guarantees on the type, mutability, serializability, or thread-safety - * of the {@code List} returned; if more control over the returned - * {@code List} is required, use {@link #toCollection(Supplier)}. + * Returns a {@link List} containing the elements of this stream. The + * returned {@code List} is guaranteed to be mutable, but there are no + * guarantees on the type, serializability, or thread-safety; if more + * control over the returned {@code List} is required, use + * {@link #toCollection(Supplier)}. * *

* This is a terminal operation. @@ -1025,7 +1026,7 @@ public S prepend(Stream other) { */ @SuppressWarnings("unchecked") public List toList() { - return new ArrayList((Collection)new ArrayCollection(toArray())); + return new ArrayList((Collection) new ArrayCollection(toArray())); } /** @@ -1042,16 +1043,18 @@ public List toList() { * @return result of applying the finisher transformation to the list of the * stream elements. * @since 0.2.3 + * @see #toList() */ public R toListAndThen(Function, R> finisher) { return finisher.apply(toList()); } /** - * Returns a {@link Set} containing the elements of this stream. There are - * no guarantees on the type, mutability, serializability, or thread-safety - * of the {@code Set} returned; if more control over the returned - * {@code Set} is required, use {@link #toCollection(Supplier)}. + * Returns a {@link Set} containing the elements of this stream. The + * returned {@code Set} is guaranteed to be mutable, but there are no + * guarantees on the type, serializability, or thread-safety; if more + * control over the returned {@code Set} is required, use + * {@link #toCollection(Supplier)}. * *

* This is a terminal operation. @@ -1077,6 +1080,7 @@ public Set toSet() { * @return result of applying the finisher transformation to the set of the * stream elements. * @since 0.2.3 + * @see #toSet() */ public R toSetAndThen(Function, R> finisher) { return collect(Collectors.collectingAndThen(Collectors.toSet(), finisher)); From 43e5546253025771a8198837ef0b512dedab4162 Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Tue, 27 Oct 2015 11:09:30 +0600 Subject: [PATCH 29/42] CHANGES.md updated --- CHANGES.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 0ada0c2d..09a8d4ef 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,7 +2,9 @@ ### 0.4.1 +* Added: `StreamEx/IntStreamEx/LongStreamEx/DoubleStreamEx.mapLast/mapFirst` methods. * Fixed: `StreamEx.cross(mapper)` now correctly handles the case when mapper returns null instead of empty stream. +* Optimized: ordered stateful short-circuit collectors now may process less elements in parallel. * Optimized: `StreamEx/EntryStream.toList()/toListAndThen()/foldRight()/scanRight()` now faster, especially for sized stream. * Updated documentation. From 24ee754dacc59368563c8a77a280ea22250ae4a0 Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Tue, 27 Oct 2015 11:29:10 +0600 Subject: [PATCH 30/42] Added note about possible package name change --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index b3e62572..ef605a1c 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,7 @@ +## Note for the users + +I'm thinking about changing the package name for the library. If you like StreamEx, please take a minute to visit [this issue](https://github.com/amaembo/streamex/issues/8) and submit your thoughts. Thank you! + # StreamEx 0.4.0 Enhancing Java 8 Streams. From ab1b6997687ce77b226b645f06800b56c9aa7047 Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Tue, 27 Oct 2015 15:26:02 +0600 Subject: [PATCH 31/42] MoreCollectorsTest: testHeadParallel2 removed (superceded with testHeadParallel); expected values moved out of the loop --- .../javax/util/streamex/MoreCollectorsTest.java | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/src/test/java/javax/util/streamex/MoreCollectorsTest.java b/src/test/java/javax/util/streamex/MoreCollectorsTest.java index f4133141..e892d4c6 100644 --- a/src/test/java/javax/util/streamex/MoreCollectorsTest.java +++ b/src/test/java/javax/util/streamex/MoreCollectorsTest.java @@ -180,23 +180,17 @@ public void testFirstLast() { @Test public void testHeadParallel() { + List expected = IntStreamEx.range(0, 2000, 2).boxed().toList(); + List expectedShort = Arrays.asList(0, 1); for (int i = 0; i < 1000; i++) { - assertEquals("#" + i, Arrays.asList(0, 1), + assertEquals("#" + i, expectedShort, IntStreamEx.range(1000).boxed().parallel().collect(MoreCollectors.head(2))); - assertEquals("#" + i, IntStreamEx.range(0, 2000, 2).boxed().toList(), IntStreamEx.range(10000).boxed() + assertEquals("#" + i, expected, IntStreamEx.range(10000).boxed() .parallel().filter(x -> x % 2 == 0).collect(MoreCollectors.head(1000))); } - assertEquals(Arrays.asList(0, 1), StreamEx.iterate(0, x -> x + 1).parallel().collect(MoreCollectors.head(2))); + assertEquals(expectedShort, StreamEx.iterate(0, x -> x + 1).parallel().collect(MoreCollectors.head(2))); } - @Test - public void testHeadParallel2() { - for (int i = 0; i < 10000; i++) { - assertEquals("#" + i, IntStreamEx.range(0, 20, 2).boxed().toList(), IntStreamEx.range(100).boxed() - .parallel().filter(x -> x % 2 == 0).collect(MoreCollectors.head(10))); - } - } - @Test public void testHeadTail() { List ints = IntStreamEx.range(1000).boxed().toList(); From 2403571aaf2b6edadeeddd644da56d62662c0ef6 Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Tue, 27 Oct 2015 15:30:56 +0600 Subject: [PATCH 32/42] CustomPoolTest: CustomStreamEx.rawCollect is covered again (coverage regression appeared when toList() was optimized) --- src/test/java/javax/util/streamex/CustomPoolTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/javax/util/streamex/CustomPoolTest.java b/src/test/java/javax/util/streamex/CustomPoolTest.java index d73c95ce..15d363e8 100644 --- a/src/test/java/javax/util/streamex/CustomPoolTest.java +++ b/src/test/java/javax/util/streamex/CustomPoolTest.java @@ -65,7 +65,7 @@ public void testStreamEx() { assertFalse(StreamEx.of("a", "b").parallel(pool).peek(this::checkThread).allMatch("a"::equals)); assertFalse(StreamEx.of("a", "b").parallel(pool).peek(this::checkThread).noneMatch("a"::equals)); assertEquals(Arrays.asList("b", "c"), StreamEx.of("a", "b", "c").parallel(pool).peek(this::checkThread).skip(1) - .toList()); + .collect(Collectors.toList())); assertEquals( 6, StreamEx.of("a", "bb", "ccc").parallel(pool).peek(this::checkThread) From 0e55e8605acadb580434b551b0a228a92429477a Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Tue, 27 Oct 2015 15:38:53 +0600 Subject: [PATCH 33/42] [#11] mapFirst/mapLast implementation fixed, tests --- .../java/javax/util/streamex/StreamEx.java | 16 +++-- .../util/streamex/DoubleStreamExTest.java | 43 +++++++----- .../javax/util/streamex/IntStreamExTest.java | 67 ++++++++++--------- .../javax/util/streamex/LongStreamExTest.java | 17 +++-- .../javax/util/streamex/StreamExTest.java | 16 +++++ 5 files changed, 102 insertions(+), 57 deletions(-) diff --git a/src/main/java/javax/util/streamex/StreamEx.java b/src/main/java/javax/util/streamex/StreamEx.java index 9c42c34b..813d8080 100644 --- a/src/main/java/javax/util/streamex/StreamEx.java +++ b/src/main/java/javax/util/streamex/StreamEx.java @@ -196,18 +196,26 @@ public EntryStream mapToEntry(Function keyM } public StreamEx mapFirst(Function mapper) { + // Cannot reuse NONE object here as the object appears in the stream and + // might become visible to other pipeline steps + // thus new Object is necessary every time @SuppressWarnings("unchecked") - Stream none = Stream.of((T)NONE); + T first = (T) new Object(); + Stream none = Stream.of(first); return strategy().newStreamEx( - delegate(new PairSpliterator.PSOfRef((a, b) -> (a == NONE ? mapper.apply(b) : b), Stream.concat( + delegate(new PairSpliterator.PSOfRef((a, b) -> (a == first ? mapper.apply(b) : b), Stream.concat( none, stream).spliterator()))); } public StreamEx mapLast(Function mapper) { + // Cannot reuse NONE object here as the object appears in the stream and + // might become visible to other pipeline steps + // thus new Object is necessary every time @SuppressWarnings("unchecked") - Stream none = Stream.of((T)NONE); + T last = (T) new Object(); + Stream none = Stream.of(last); return strategy().newStreamEx( - delegate(new PairSpliterator.PSOfRef((a, b) -> (b == NONE ? mapper.apply(a) : a), Stream.concat( + delegate(new PairSpliterator.PSOfRef((a, b) -> (b == last ? mapper.apply(a) : a), Stream.concat( stream, none).spliterator()))); } diff --git a/src/test/java/javax/util/streamex/DoubleStreamExTest.java b/src/test/java/javax/util/streamex/DoubleStreamExTest.java index 297d35b5..40fb6a09 100644 --- a/src/test/java/javax/util/streamex/DoubleStreamExTest.java +++ b/src/test/java/javax/util/streamex/DoubleStreamExTest.java @@ -61,7 +61,8 @@ public void testCreate() { assertArrayEquals(new double[] { 1, 1, 1, 1 }, DoubleStreamEx.generate(() -> 1).limit(4).toArray(), 0.0); assertArrayEquals(new double[] { 1, 1, 1, 1 }, DoubleStreamEx.constant(1.0, 4).toArray(), 0.0); assertEquals(10, DoubleStreamEx.of(new Random(), 10).count()); - assertArrayEquals(DoubleStreamEx.of(new Random(1), 10).toArray(), DoubleStreamEx.of(new Random(1)).limit(10).toArray(), 0.0); + assertArrayEquals(DoubleStreamEx.of(new Random(1), 10).toArray(), DoubleStreamEx.of(new Random(1)).limit(10) + .toArray(), 0.0); assertTrue(DoubleStreamEx.of(new Random(), 100, 1, 10).allMatch(x -> x >= 1 && x < 10)); assertArrayEquals(DoubleStreamEx.of(new Random(1), 100, 1, 10).toArray(), DoubleStreamEx.of(new Random(1), 1, 10).limit(100).toArray(), 0.0); @@ -122,7 +123,7 @@ public void testBasics() { assertTrue(DoubleStreamEx.of(1, 2, 3).spliterator().hasCharacteristics(Spliterator.ORDERED)); assertFalse(DoubleStreamEx.of(1, 2, 3).unordered().spliterator().hasCharacteristics(Spliterator.ORDERED)); - + OfDouble iterator = DoubleStreamEx.of(1.0, 2.0, 3.0).iterator(); assertEquals(1.0, iterator.next(), 0.0); assertEquals(2.0, iterator.next(), 0.0); @@ -142,7 +143,7 @@ public void testFlatMap() { assertEquals("1:.:5:2:2:.:3:3:.:2:0:.:9", DoubleStreamEx.of(1.5, 22.3, 3.2, 0.9).flatMapToObj(x -> StreamEx.split(String.valueOf(x), "")) .joining(":")); - + assertArrayEquals(new double[] { 0.0, 0.0, 1.0, 0.0, 1.0, 2.0 }, DoubleStreamEx.of(1, 2, 3).flatMap(x -> IntStreamEx.range((int) x).asDoubleStream()).toArray(), 0.0); } @@ -150,7 +151,8 @@ public void testFlatMap() { @Test public void testPrepend() { assertArrayEquals(new double[] { -1, 0, 1, 2, 3 }, DoubleStreamEx.of(1, 2, 3).prepend(-1, 0).toArray(), 0.0); - assertArrayEquals(new double[] { -1, 0, 1, 2, 3 }, DoubleStreamEx.of(1, 2, 3).prepend(DoubleStream.of(-1, 0)).toArray(), 0.0); + assertArrayEquals(new double[] { -1, 0, 1, 2, 3 }, DoubleStreamEx.of(1, 2, 3).prepend(DoubleStream.of(-1, 0)) + .toArray(), 0.0); DoubleStreamEx s = DoubleStreamEx.of(1, 2, 3); assertSame(s, s.prepend()); } @@ -158,7 +160,8 @@ public void testPrepend() { @Test public void testAppend() { assertArrayEquals(new double[] { 1, 2, 3, 4, 5 }, DoubleStreamEx.of(1, 2, 3).append(4, 5).toArray(), 0.0); - assertArrayEquals(new double[] { 1, 2, 3, 4, 5 }, DoubleStreamEx.of(1, 2, 3).append(DoubleStream.of(4, 5)).toArray(), 0.0); + assertArrayEquals(new double[] { 1, 2, 3, 4, 5 }, DoubleStreamEx.of(1, 2, 3).append(DoubleStream.of(4, 5)) + .toArray(), 0.0); DoubleStreamEx s = DoubleStreamEx.of(1, 2, 3); assertSame(s, s.append()); } @@ -208,7 +211,7 @@ public void testSort() { @Test public void testMinMax() { assertFalse(DoubleStreamEx.empty().maxBy(Double::valueOf).isPresent()); - assertFalse(DoubleStreamEx.empty().maxByLong(x -> (long)x).isPresent()); + assertFalse(DoubleStreamEx.empty().maxByLong(x -> (long) x).isPresent()); assertEquals(9, IntStreamEx.range(5, 12).asDoubleStream().max((a, b) -> String.valueOf(a).compareTo(String.valueOf(b))) .getAsDouble(), 0.0); @@ -233,9 +236,9 @@ public void testMinMax() { DoubleToLongFunction longKey = x -> String.valueOf(x).length(); DoubleUnaryOperator doubleKey = x -> String.valueOf(x).length(); DoubleFunction objKey = x -> String.valueOf(x).length(); - List> minFns = Arrays.asList(is -> is.minByInt(intKey), + List> minFns = Arrays.asList(is -> is.minByInt(intKey), is -> is.minByLong(longKey), is -> is.minByDouble(doubleKey), is -> is.minBy(objKey)); - List> maxFns = Arrays.asList(is -> is.maxByInt(intKey), + List> maxFns = Arrays.asList(is -> is.maxByInt(intKey), is -> is.maxByLong(longKey), is -> is.maxByDouble(doubleKey), is -> is.maxBy(objKey)); minFns.forEach(fn -> assertEquals(1, fn.apply(s.get()).getAsDouble(), 0.0)); minFns.forEach(fn -> assertEquals(1, fn.apply(s.get().parallel()).getAsDouble(), 0.0)); @@ -310,22 +313,24 @@ public void testRecreate() { DoubleStreamEx.iterate(0, i -> i + 1).parallel().skipOrdered(1).greater(0).boxed().findAny(i -> i == 500) .get(), 0.0); } - + @Test public void testTakeWhile() { - assertArrayEquals(LongStreamEx.range(100).asDoubleStream().toArray(), DoubleStreamEx.iterate(0, i -> i+1).takeWhile(i -> i<100).toArray(), 0.0); - assertEquals(0, DoubleStreamEx.iterate(0, i -> i+1).takeWhile(i -> i<0).count()); - assertEquals(1, DoubleStreamEx.of(1, 3, 2).takeWhile(i -> i<3).count()); - assertEquals(3, DoubleStreamEx.of(1, 2, 3).takeWhile(i -> i<100).count()); + assertArrayEquals(LongStreamEx.range(100).asDoubleStream().toArray(), DoubleStreamEx.iterate(0, i -> i + 1) + .takeWhile(i -> i < 100).toArray(), 0.0); + assertEquals(0, DoubleStreamEx.iterate(0, i -> i + 1).takeWhile(i -> i < 0).count()); + assertEquals(1, DoubleStreamEx.of(1, 3, 2).takeWhile(i -> i < 3).count()); + assertEquals(3, DoubleStreamEx.of(1, 2, 3).takeWhile(i -> i < 100).count()); } - + @Test public void testDropWhile() { - assertArrayEquals(new double[] {5,6,7,8,9,10,11,12,13,14}, LongStreamEx.range(100).asDoubleStream().dropWhile(i -> i % 10 < 5).limit(10).toArray(), 0.0); + assertArrayEquals(new double[] { 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 }, LongStreamEx.range(100).asDoubleStream() + .dropWhile(i -> i % 10 < 5).limit(10).toArray(), 0.0); assertEquals(100, LongStreamEx.range(100).asDoubleStream().sorted().dropWhile(i -> i % 10 < 0).count()); assertEquals(0, LongStreamEx.range(100).asDoubleStream().dropWhile(i -> i % 10 < 10).count()); } - + @Test public void testIndexOf() { assertEquals(11, LongStreamEx.range(50, 100).asDoubleStream().indexOf(x -> x > 60).getAsLong()); @@ -344,4 +349,10 @@ public void testFoldLeft() { assertEquals(144, DoubleStreamEx.of(1, 2, 3).foldLeft(0.0, accumulator), 144); assertEquals(144, DoubleStreamEx.of(1, 2, 3).parallel().foldLeft(0.0, accumulator), 144); } + + @Test + public void testMapFirstLast() { + assertArrayEquals(new double[] { -1, 2, 3, 4, 7 }, DoubleStreamEx.of(1, 2, 3, 4, 5).mapFirst(x -> x - 2.0) + .mapLast(x -> x + 2.0).toArray(), 0.0); + } } diff --git a/src/test/java/javax/util/streamex/IntStreamExTest.java b/src/test/java/javax/util/streamex/IntStreamExTest.java index af9fb900..6422b50c 100644 --- a/src/test/java/javax/util/streamex/IntStreamExTest.java +++ b/src/test/java/javax/util/streamex/IntStreamExTest.java @@ -87,14 +87,14 @@ public void testCreate() { assertArrayEquals(new int[] { 1, 5, 3 }, IntStreamEx.of(Spliterators.spliterator(new int[] { 1, 5, 3 }, 0)) .toArray()); - + BitSet bs = new BitSet(); bs.set(1); bs.set(3); bs.set(5); - assertArrayEquals(new int[] { 1, 3, 5 }, IntStreamEx.of(bs).toArray()); + assertArrayEquals(new int[] { 1, 3, 5 }, IntStreamEx.of(bs).toArray()); } - + @Test public void testRangeStep() { assertArrayEquals(new int[] { 0 }, IntStreamEx.range(0, 1000, 100000).toArray()); @@ -137,16 +137,16 @@ public void testRangeStep() { assertEquals(0, IntStreamEx.range(0, 1000, -2).count()); assertEquals(0, IntStreamEx.range(0, 0, -2).count()); assertEquals(0, IntStreamEx.range(0, 0, 2).count()); - + assertEquals(0, IntStreamEx.range(0, Integer.MIN_VALUE, 2).spliterator().getExactSizeIfKnown()); assertEquals(0, IntStreamEx.range(0, Integer.MAX_VALUE, -2).spliterator().getExactSizeIfKnown()); } - + @Test(expected = IllegalArgumentException.class) public void testRangeIllegalStep() { IntStreamEx.range(0, 1000, 0); } - + @Test public void testRangeClosedStep() { assertArrayEquals(new int[] { 0 }, IntStreamEx.rangeClosed(0, 1000, 100000).toArray()); @@ -267,14 +267,14 @@ public void testBasics() { assertEquals(2, iterator.nextInt()); assertEquals(3, iterator.nextInt()); assertFalse(iterator.hasNext()); - - List list = new ArrayList<>(); - IntStreamEx.range(10).parallel().forEachOrdered(x -> list.add(x)); - assertEquals(Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9), list); - - assertTrue(IntStreamEx.empty().noneMatch(x -> true)); - assertFalse(IntStreamEx.of(1).noneMatch(x -> true)); - assertTrue(IntStreamEx.of(1).noneMatch(x -> false)); + + List list = new ArrayList<>(); + IntStreamEx.range(10).parallel().forEachOrdered(x -> list.add(x)); + assertEquals(Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9), list); + + assertTrue(IntStreamEx.empty().noneMatch(x -> true)); + assertFalse(IntStreamEx.of(1).noneMatch(x -> true)); + assertTrue(IntStreamEx.of(1).noneMatch(x -> false)); } @Test @@ -295,8 +295,8 @@ public void testFlatMap() { double[] fractions = IntStreamEx.range(1, 5) .flatMapToDouble(i -> IntStreamEx.range(1, i).mapToDouble(j -> ((double) j) / i)).toArray(); assertArrayEquals(new double[] { 1 / 2.0, 1 / 3.0, 2 / 3.0, 1 / 4.0, 2 / 4.0, 3 / 4.0 }, fractions, 0.000001); - - assertArrayEquals(new int[] {0, 0, 1, 0, 1, 2}, IntStreamEx.of(1, 2, 3).flatMap(IntStreamEx::range).toArray()); + + assertArrayEquals(new int[] { 0, 0, 1, 0, 1, 2 }, IntStreamEx.of(1, 2, 3).flatMap(IntStreamEx::range).toArray()); } @Test @@ -419,16 +419,16 @@ public void testMinMax() { IntToLongFunction longKey = x -> String.valueOf(x).length(); IntToDoubleFunction doubleKey = x -> String.valueOf(x).length(); IntFunction objKey = x -> String.valueOf(x).length(); - List> minFns = Arrays.asList(is -> is.minByInt(intKey), + List> minFns = Arrays.asList(is -> is.minByInt(intKey), is -> is.minByLong(longKey), is -> is.minByDouble(doubleKey), is -> is.minBy(objKey)); - List> maxFns = Arrays.asList(is -> is.maxByInt(intKey), + List> maxFns = Arrays.asList(is -> is.maxByInt(intKey), is -> is.maxByLong(longKey), is -> is.maxByDouble(doubleKey), is -> is.maxBy(objKey)); minFns.forEach(fn -> assertEquals(1, fn.apply(s.get()).getAsInt())); minFns.forEach(fn -> assertEquals(1, fn.apply(s.get().parallel()).getAsInt())); maxFns.forEach(fn -> assertEquals(120, fn.apply(s.get()).getAsInt())); maxFns.forEach(fn -> assertEquals(120, fn.apply(s.get().parallel()).getAsInt())); } - + private IntStreamEx dropLast(IntStreamEx s) { return s.pairMap((a, b) -> a); } @@ -572,35 +572,37 @@ public void testRecreate() { assertEquals(expected, IntStreamEx.iterate(0, i -> i + 1).skipOrdered(1).greater(0).limit(99).boxed() .parallel().toSet()); } - + @Test public void testTakeWhile() { - assertArrayEquals(IntStreamEx.range(100).toArray(), IntStreamEx.iterate(0, i -> i+1).takeWhile(i -> i<100).toArray()); - assertEquals(0, IntStreamEx.iterate(0, i -> i+1).takeWhile(i -> i<0).count()); - assertEquals(1, IntStreamEx.of(1, 3, 2).takeWhile(i -> i<3).count()); - assertEquals(3, IntStreamEx.of(1, 2, 3).takeWhile(i -> i<100).count()); + assertArrayEquals(IntStreamEx.range(100).toArray(), IntStreamEx.iterate(0, i -> i + 1).takeWhile(i -> i < 100) + .toArray()); + assertEquals(0, IntStreamEx.iterate(0, i -> i + 1).takeWhile(i -> i < 0).count()); + assertEquals(1, IntStreamEx.of(1, 3, 2).takeWhile(i -> i < 3).count()); + assertEquals(3, IntStreamEx.of(1, 2, 3).takeWhile(i -> i < 100).count()); } - + @Test public void testDropWhile() { - assertArrayEquals(new int[] {5,6,7,8,9,10,11,12,13,14}, IntStreamEx.range(100).dropWhile(i -> i % 10 < 5).limit(10).toArray()); + assertArrayEquals(new int[] { 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 }, + IntStreamEx.range(100).dropWhile(i -> i % 10 < 5).limit(10).toArray()); assertEquals(100, IntStreamEx.range(100).dropWhile(i -> i % 10 < 0).count()); assertEquals(0, IntStreamEx.range(100).dropWhile(i -> i % 10 < 10).count()); } - + @Test public void testIndexOf() { assertEquals(5, IntStreamEx.range(50, 100).indexOf(55).getAsLong()); assertFalse(IntStreamEx.range(50, 100).indexOf(200).isPresent()); assertEquals(5, IntStreamEx.range(50, 100).parallel().indexOf(55).getAsLong()); assertFalse(IntStreamEx.range(50, 100).parallel().indexOf(200).isPresent()); - + assertEquals(11, IntStreamEx.range(50, 100).indexOf(x -> x > 60).getAsLong()); assertFalse(IntStreamEx.range(50, 100).indexOf(x -> x < 0).isPresent()); assertEquals(11, IntStreamEx.range(50, 100).parallel().indexOf(x -> x > 60).getAsLong()); assertFalse(IntStreamEx.range(50, 100).parallel().indexOf(x -> x < 0).isPresent()); } - + @Test public void testFoldLeft() { // non-associative @@ -611,11 +613,14 @@ public void testFoldLeft() { assertEquals(144, IntStreamEx.rangeClosed(1, 3).foldLeft(0, accumulator)); assertEquals(144, IntStreamEx.rangeClosed(1, 3).parallel().foldLeft(0, accumulator)); } - + @Test - public void testMapFirst() { + public void testMapFirstLast() { // capitalize String str = "testString"; assertEquals("TestString", IntStreamEx.ofCodePoints(str).mapFirst(Character::toUpperCase).codePointsToString()); + + assertArrayEquals(new int[] { -1, 2, 3, 4, 7 }, + IntStreamEx.of(1, 2, 3, 4, 5).mapFirst(x -> x - 2).mapLast(x -> x + 2).toArray()); } } diff --git a/src/test/java/javax/util/streamex/LongStreamExTest.java b/src/test/java/javax/util/streamex/LongStreamExTest.java index ed46ddaa..79547906 100644 --- a/src/test/java/javax/util/streamex/LongStreamExTest.java +++ b/src/test/java/javax/util/streamex/LongStreamExTest.java @@ -233,8 +233,7 @@ public void testFlatMap() { .toArray()); String expected = LongStreamEx.range(200).boxed() - .flatMap(i -> LongStreamEx.range(0, i). mapToObj(j -> i + ":" + j)) - .joining("/"); + .flatMap(i -> LongStreamEx.range(0, i). mapToObj(j -> i + ":" + j)).joining("/"); String res = LongStreamEx.range(200).flatMapToObj(i -> LongStreamEx.range(i).mapToObj(j -> i + ":" + j)) .joining("/"); String parallel = LongStreamEx.range(200).parallel() @@ -305,7 +304,7 @@ public void testSort() { assertArrayEquals(new long[] { 0, 3, 6, 1, 4, 7, 2, 5, 8 }, LongStreamEx.range(0, 9).sortedByLong(i -> i % 3 * 3 + i / 3).toArray()); assertArrayEquals(new long[] { 0, 4, 8, 1, 5, 9, 2, 6, 3, 7 }, - LongStreamEx.range(0, 10).sortedByInt(i -> (int)i % 4).toArray()); + LongStreamEx.range(0, 10).sortedByInt(i -> (int) i % 4).toArray()); assertArrayEquals(new long[] { 10, 11, 5, 6, 7, 8, 9 }, LongStreamEx.range(5, 12).sortedBy(String::valueOf) .toArray()); assertArrayEquals(new long[] { Long.MAX_VALUE, 1000, 1, 0, -10, Long.MIN_VALUE }, @@ -402,20 +401,20 @@ public void testDropWhile() { assertEquals(100, LongStreamEx.range(100).dropWhile(i -> i % 10 < 0).count()); assertEquals(0, LongStreamEx.range(100).dropWhile(i -> i % 10 < 10).count()); } - + @Test public void testIndexOf() { assertEquals(5, LongStreamEx.range(50, 100).indexOf(55).getAsLong()); assertFalse(LongStreamEx.range(50, 100).indexOf(200).isPresent()); assertEquals(5, LongStreamEx.range(50, 100).parallel().indexOf(55).getAsLong()); assertFalse(LongStreamEx.range(50, 100).parallel().indexOf(200).isPresent()); - + assertEquals(11, LongStreamEx.range(50, 100).indexOf(x -> x > 60).getAsLong()); assertFalse(LongStreamEx.range(50, 100).indexOf(x -> x < 0).isPresent()); assertEquals(11, LongStreamEx.range(50, 100).parallel().indexOf(x -> x > 60).getAsLong()); assertFalse(LongStreamEx.range(50, 100).parallel().indexOf(x -> x < 0).isPresent()); } - + @Test public void testFoldLeft() { // non-associative @@ -426,4 +425,10 @@ public void testFoldLeft() { assertEquals(144, LongStreamEx.rangeClosed(1, 3).foldLeft(0L, accumulator)); assertEquals(144, LongStreamEx.rangeClosed(1, 3).parallel().foldLeft(0L, accumulator)); } + + @Test + public void testMapFirstLast() { + assertArrayEquals(new long[] { -1, 2, 3, 4, 7 }, + LongStreamEx.of(1, 2, 3, 4, 5).mapFirst(x -> x - 2L).mapLast(x -> x + 2L).toArray()); + } } diff --git a/src/test/java/javax/util/streamex/StreamExTest.java b/src/test/java/javax/util/streamex/StreamExTest.java index a7a963da..8143b8e0 100644 --- a/src/test/java/javax/util/streamex/StreamExTest.java +++ b/src/test/java/javax/util/streamex/StreamExTest.java @@ -1473,4 +1473,20 @@ public void testIndexOfSimple() { assertEquals(9, StreamEx.of(input).parallel().indexOf(9).getAsLong()); } } + + @Test + public void testMapFirstLast() { + for(StreamExSupplier s : streamEx(() -> StreamEx.of(0, 343, 999))) { + assertEquals(s.toString(), Arrays.asList(2, 343, 997), s.get() + .mapFirst(x -> x + 2).mapLast(x -> x - 2).toList()); + } + for(StreamExSupplier s : streamEx(() -> IntStreamEx.range(1000).boxed())) { + assertEquals(s.toString(), Arrays.asList(2, 343, 997), s.get().filter(x -> x == 0 || x == 343 || x == 999) + .mapFirst(x -> x + 2).mapLast(x -> x - 2).toList()); + } + for (StreamExSupplier s : streamEx(() -> IntStreamEx.range(50).boxed() + .foldLeft(StreamEx.of(0), (stream, i) -> stream.append(i).mapLast(x -> x + 2)))) { + assertEquals(s.toString(), IntStreamEx.range(2, 52).boxed().prepend(0).toList(), s.get().toList()); + } + } } From 8111d5686d1a6a66a44152bda8014bd7973e75bd Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Tue, 27 Oct 2015 17:53:28 +0600 Subject: [PATCH 34/42] [#11] mapFirst/mapLast documentation --- .../javax/util/streamex/DoubleStreamEx.java | 34 +++++++++++++++++ .../java/javax/util/streamex/IntStreamEx.java | 34 +++++++++++++++++ .../javax/util/streamex/LongStreamEx.java | 34 +++++++++++++++++ .../java/javax/util/streamex/StreamEx.java | 38 ++++++++++++++++++- 4 files changed, 138 insertions(+), 2 deletions(-) diff --git a/src/main/java/javax/util/streamex/DoubleStreamEx.java b/src/main/java/javax/util/streamex/DoubleStreamEx.java index 914db872..666df253 100644 --- a/src/main/java/javax/util/streamex/DoubleStreamEx.java +++ b/src/main/java/javax/util/streamex/DoubleStreamEx.java @@ -191,10 +191,44 @@ public DoubleStreamEx map(DoubleUnaryOperator mapper) { return strategy().newDoubleStreamEx(stream.map(mapper)); } + /** + * Returns a stream where the first element is the replaced with the result + * of applying the given function while the other elements are left intact. + * + *

+ * This is an quasi-intermediate + * operation. + * + * @param mapper + * a non-interfering + * , stateless + * function to apply to the first element + * @return the new stream + * @since 0.4.1 + */ public DoubleStreamEx mapFirst(DoubleUnaryOperator mapper) { return boxed().mapFirst(mapper::applyAsDouble).mapToDouble(Double::doubleValue); } + /** + * Returns a stream where the last element is the replaced with the result + * of applying the given function while the other elements are left intact. + * + *

+ * This is an quasi-intermediate + * operation. + * + * @param mapper + * a non-interfering + * , stateless + * function to apply to the first element + * @return the new stream + * @since 0.4.1 + */ public DoubleStreamEx mapLast(DoubleUnaryOperator mapper) { return boxed().mapLast(mapper::applyAsDouble).mapToDouble(Double::doubleValue); } diff --git a/src/main/java/javax/util/streamex/IntStreamEx.java b/src/main/java/javax/util/streamex/IntStreamEx.java index 9be904bc..c369c3a1 100644 --- a/src/main/java/javax/util/streamex/IntStreamEx.java +++ b/src/main/java/javax/util/streamex/IntStreamEx.java @@ -1510,10 +1510,44 @@ public IntStreamEx dropWhile(IntPredicate predicate) { return delegate(new TDOfInt(stream.spliterator(), true, predicate)); } + /** + * Returns a stream where the first element is the replaced with the result + * of applying the given function while the other elements are left intact. + * + *

+ * This is an quasi-intermediate + * operation. + * + * @param mapper + * a non-interfering + * , stateless + * function to apply to the first element + * @return the new stream + * @since 0.4.1 + */ public IntStreamEx mapFirst(IntUnaryOperator mapper) { return mapToObj(Integer::new).mapFirst(mapper::applyAsInt).mapToInt(Integer::intValue); } + /** + * Returns a stream where the last element is the replaced with the result + * of applying the given function while the other elements are left intact. + * + *

+ * This is an quasi-intermediate + * operation. + * + * @param mapper + * a non-interfering + * , stateless + * function to apply to the first element + * @return the new stream + * @since 0.4.1 + */ public IntStreamEx mapLast(IntUnaryOperator mapper) { return mapToObj(Integer::new).mapLast(mapper::applyAsInt).mapToInt(Integer::intValue); } diff --git a/src/main/java/javax/util/streamex/LongStreamEx.java b/src/main/java/javax/util/streamex/LongStreamEx.java index 223e5273..a9fea599 100644 --- a/src/main/java/javax/util/streamex/LongStreamEx.java +++ b/src/main/java/javax/util/streamex/LongStreamEx.java @@ -222,10 +222,44 @@ public LongStreamEx map(LongUnaryOperator mapper) { return strategy().newLongStreamEx(stream.map(mapper)); } + /** + * Returns a stream where the first element is the replaced with the result + * of applying the given function while the other elements are left intact. + * + *

+ * This is an quasi-intermediate + * operation. + * + * @param mapper + * a non-interfering + * , stateless + * function to apply to the first element + * @return the new stream + * @since 0.4.1 + */ public LongStreamEx mapFirst(LongUnaryOperator mapper) { return mapToObj(Long::new).mapFirst(mapper::applyAsLong).mapToLong(Long::longValue); } + /** + * Returns a stream where the last element is the replaced with the result + * of applying the given function while the other elements are left intact. + * + *

+ * This is an quasi-intermediate + * operation. + * + * @param mapper + * a non-interfering + * , stateless + * function to apply to the first element + * @return the new stream + * @since 0.4.1 + */ public LongStreamEx mapLast(LongUnaryOperator mapper) { return mapToObj(Long::new).mapLast(mapper::applyAsLong).mapToLong(Long::longValue); } diff --git a/src/main/java/javax/util/streamex/StreamEx.java b/src/main/java/javax/util/streamex/StreamEx.java index 813d8080..f5aae9d9 100644 --- a/src/main/java/javax/util/streamex/StreamEx.java +++ b/src/main/java/javax/util/streamex/StreamEx.java @@ -195,6 +195,23 @@ public EntryStream mapToEntry(Function keyM stream.map(e -> new SimpleImmutableEntry<>(keyMapper.apply(e), valueMapper.apply(e)))); } + /** + * Returns a stream where the first element is the replaced with the result + * of applying the given function while the other elements are left intact. + * + *

+ * This is an quasi-intermediate + * operation. + * + * @param mapper + * a non-interfering + * , stateless + * function to apply to the first element + * @return the new stream + * @since 0.4.1 + */ public StreamEx mapFirst(Function mapper) { // Cannot reuse NONE object here as the object appears in the stream and // might become visible to other pipeline steps @@ -207,6 +224,23 @@ public StreamEx mapFirst(Function mapper) { none, stream).spliterator()))); } + /** + * Returns a stream where the last element is the replaced with the result + * of applying the given function while the other elements are left intact. + * + *

+ * This is an quasi-intermediate + * operation. + * + * @param mapper + * a non-interfering + * , stateless + * function to apply to the first element + * @return the new stream + * @since 0.4.1 + */ public StreamEx mapLast(Function mapper) { // Cannot reuse NONE object here as the object appears in the stream and // might become visible to other pipeline steps @@ -218,7 +252,7 @@ public StreamEx mapLast(Function mapper) { delegate(new PairSpliterator.PSOfRef((a, b) -> (b == last ? mapper.apply(a) : a), Stream.concat( stream, none).spliterator()))); } - + /** * Creates a new {@code EntryStream} populated from entries of maps produced * by supplied mapper function which is applied to the every element of this @@ -1401,7 +1435,7 @@ public StreamEx intervalMap(BiPredicate sameInterva return left; }).map(pair -> mapper.apply(pair.a, pair.b)); } - + /** * Returns an empty sequential {@code StreamEx}. * From 45a53a44a67d8f641ca2753b207c7660eb1f2cf1 Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Tue, 27 Oct 2015 21:10:37 +0600 Subject: [PATCH 35/42] [#12] CollapseSpliterator.forEachRemaining improved (tests passed) --- .../util/streamex/CollapseSpliterator.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/java/javax/util/streamex/CollapseSpliterator.java b/src/main/java/javax/util/streamex/CollapseSpliterator.java index e0232c73..f1c5bc84 100644 --- a/src/main/java/javax/util/streamex/CollapseSpliterator.java +++ b/src/main/java/javax/util/streamex/CollapseSpliterator.java @@ -100,7 +100,7 @@ public boolean tryAdvance(Consumer action) { return true; } } - if(cur == none()) {// start + if(cur == NONE) {// start if(!source.tryAdvance(this)) { return accept(pushRight(none(), none()), action); } @@ -124,15 +124,13 @@ public void forEachRemaining(Consumer action) { while (left != null) { accept(handleLeft(), action); } - if(cur == none()) { - if(!source.tryAdvance(this)) { - accept(pushRight(none(), none()), action); - return; - } + if(cur != NONE) { + acc = mapper.apply(cur); } - acc = mapper.apply(cur); source.forEachRemaining(next -> { - if(!this.mergeable.test(cur, next)) { + if(cur == NONE) { + acc = mapper.apply(next); + } else if(!this.mergeable.test(cur, next)) { action.accept(acc); acc = mapper.apply(next); } else { @@ -140,7 +138,9 @@ public void forEachRemaining(Consumer action) { } cur = next; }); - if(accept(pushRight(acc, cur), action)) { + if(cur == NONE) { + accept(pushRight(none(), none()), action); + } else if(accept(pushRight(acc, cur), action)) { if(right != null) { action.accept(right.acc); right = null; From d834368e6a12eca6fa014a47e8693e0bbd1862ce Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Wed, 28 Oct 2015 12:53:58 +0600 Subject: [PATCH 36/42] [#15] MoreCollectors.mapping: do not call mapper if short-circuiting downstream reduction is finished --- .../java/javax/util/streamex/MoreCollectors.java | 8 +++++--- .../javax/util/streamex/MoreCollectorsTest.java | 13 +++++++++++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/main/java/javax/util/streamex/MoreCollectors.java b/src/main/java/javax/util/streamex/MoreCollectors.java index 4554b650..db0fdad6 100644 --- a/src/main/java/javax/util/streamex/MoreCollectors.java +++ b/src/main/java/javax/util/streamex/MoreCollectors.java @@ -1215,9 +1215,11 @@ public static Collector collectingAndThen(Collector downstream) { if (downstream instanceof CancellableCollector) { BiConsumer downstreamAccumulator = downstream.accumulator(); - return new CancellableCollectorImpl<>(downstream.supplier(), (r, t) -> downstreamAccumulator.accept(r, - mapper.apply(t)), downstream.combiner(), downstream.finisher(), - ((CancellableCollector) downstream).finished(), downstream.characteristics()); + Predicate finished = ((CancellableCollector) downstream).finished(); + return new CancellableCollectorImpl<>(downstream.supplier(), (r, t) -> { + if (!finished.test(r)) + downstreamAccumulator.accept(r, mapper.apply(t)); + }, downstream.combiner(), downstream.finisher(), finished, downstream.characteristics()); } return Collectors.mapping(mapper, downstream); } diff --git a/src/test/java/javax/util/streamex/MoreCollectorsTest.java b/src/test/java/javax/util/streamex/MoreCollectorsTest.java index e892d4c6..e912bb04 100644 --- a/src/test/java/javax/util/streamex/MoreCollectorsTest.java +++ b/src/test/java/javax/util/streamex/MoreCollectorsTest.java @@ -353,6 +353,19 @@ public void testMapping() { Collector>> collectorLast = MoreCollectors.partitioningBy( str -> Character.isUpperCase(str.charAt(0)), MoreCollectors.mapping(String::length, MoreCollectors.last())); checkCollector("last", new BooleanMap<>(Optional.of(3), Optional.of(3)), input::stream, collectorLast); + + input = Arrays.asList("Abc", "Bac", "Aac", "Abv", "Bbc", "Bgd", "Atc", "Bpv"); + Map> expected = EntryStream.of( + 'A', Arrays.asList("Abc", "Aac"), + 'B', Arrays.asList("Bac", "Bbc") + ).toMap(); + AtomicInteger cnt = new AtomicInteger(); + Collector>> groupMap = Collectors.groupingBy(s -> s.charAt(0), + MoreCollectors.mapping(x -> {cnt.incrementAndGet(); return x;}, MoreCollectors.head(2))); + checkCollector("groupMap", expected, input::stream, groupMap); + cnt.set(0); + assertEquals(expected, input.stream().collect(groupMap)); + assertEquals(4, cnt.get()); } @Test From a50c48df47970e0c610ab5137e064f7e434cd1a0 Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Wed, 28 Oct 2015 16:00:54 +0600 Subject: [PATCH 37/42] [#14] MoreCollectors.flatMapping draft implementation --- .../javax/util/streamex/MoreCollectors.java | 73 ++++++++++++++----- .../util/streamex/StreamExInternals.java | 6 ++ 2 files changed, 60 insertions(+), 19 deletions(-) diff --git a/src/main/java/javax/util/streamex/MoreCollectors.java b/src/main/java/javax/util/streamex/MoreCollectors.java index db0fdad6..bb933d04 100644 --- a/src/main/java/javax/util/streamex/MoreCollectors.java +++ b/src/main/java/javax/util/streamex/MoreCollectors.java @@ -47,6 +47,7 @@ import java.util.stream.Collector; import java.util.stream.Collector.Characteristics; import java.util.stream.Collectors; +import java.util.stream.Stream; import static javax.util.streamex.StreamExInternals.*; @@ -283,9 +284,9 @@ private MoreCollectors() { R2 r2 = c2.finisher().apply(acc.b); return finisher.apply(r1, r2); }; - if (c1 instanceof CancellableCollector && c2 instanceof CancellableCollector) { - Predicate c1Finished = ((CancellableCollector) c1).finished(); - Predicate c2Finished = ((CancellableCollector) c2).finished(); + Predicate c1Finished = finished(c1); + Predicate c2Finished = finished(c2); + if (c1Finished != null && c2Finished != null) { Predicate> finished = acc -> c1Finished.test(acc.a) && c2Finished.test(acc.b); return new CancellableCollectorImpl<>(supplier, accumulator, combiner, resFinisher, finished, c); } @@ -1038,8 +1039,8 @@ class Container { downstreamAccumulator.accept(container, t); }; PartialCollector, M> partial = PartialCollector.grouping(mapFactory, downstream); - if (downstream instanceof CancellableCollectorImpl) { - Predicate downstreamFinished = ((CancellableCollectorImpl) downstream).finished(); + Predicate downstreamFinished = finished(downstream); + if (downstreamFinished != null) { int size = domain.size(); groupingBy = partial.asCancellable(accumulator, map -> { if (map.size() < size) @@ -1133,11 +1134,12 @@ class Container { */ public static Collector collectingAndThen(Collector downstream, Function finisher) { - if (downstream instanceof CancellableCollector) { + Predicate finished = finished(downstream); + if (finished != null) { return new CancellableCollectorImpl<>(downstream.supplier(), downstream.accumulator(), - downstream.combiner(), downstream.finisher().andThen(finisher), - ((CancellableCollector) downstream).finished(), downstream.characteristics().contains( - Characteristics.UNORDERED) ? UNORDERED_CHARACTERISTICS : NO_CHARACTERISTICS); + downstream.combiner(), downstream.finisher().andThen(finisher), finished, downstream + .characteristics().contains(Characteristics.UNORDERED) ? UNORDERED_CHARACTERISTICS + : NO_CHARACTERISTICS); } return Collectors.collectingAndThen(downstream, finisher); } @@ -1172,9 +1174,9 @@ public static Collector collectingAndThen(Collector Collector> partitioningBy(Predicate predicate, Collector downstream) { - if (downstream instanceof CancellableCollector) { + Predicate finished = finished(downstream); + if (finished != null) { BiConsumer accumulator = downstream.accumulator(); - Predicate finished = ((CancellableCollector) downstream).finished(); return BooleanMap.partialCollector(downstream).asCancellable( (map, t) -> accumulator.accept(predicate.test(t) ? map.trueValue : map.falseValue, t), map -> finished.test(map.trueValue) && finished.test(map.falseValue)); @@ -1213,17 +1215,50 @@ public static Collector collectingAndThen(Collector Collector mapping(Function mapper, Collector downstream) { - if (downstream instanceof CancellableCollector) { + Predicate finished = finished(downstream); + if (finished != null) { BiConsumer downstreamAccumulator = downstream.accumulator(); - Predicate finished = ((CancellableCollector) downstream).finished(); - return new CancellableCollectorImpl<>(downstream.supplier(), (r, t) -> { - if (!finished.test(r)) - downstreamAccumulator.accept(r, mapper.apply(t)); + return new CancellableCollectorImpl<>(downstream.supplier(), (acc, t) -> { + if (!finished.test(acc)) + downstreamAccumulator.accept(acc, mapper.apply(t)); }, downstream.combiner(), downstream.finisher(), finished, downstream.characteristics()); } return Collectors.mapping(mapper, downstream); } + public static Collector flatMapping( + Function> mapper, Collector downstream) { + BiConsumer downstreamAccumulator = downstream.accumulator(); + Predicate finished = finished(downstream); + if (finished != null) { + return new CancellableCollectorImpl<>(downstream.supplier(), (acc, t) -> { + if(finished.test(acc)) + return; + try (Stream stream = mapper.apply(t)) { + if (stream != null) { + try { + stream.spliterator().forEachRemaining(u -> { + downstreamAccumulator.accept(acc, u); + if(finished.test(acc)) + throw new CancelException(); + }); + } catch (CancelException ex) { + // ignore + } + } + } + }, downstream.combiner(), downstream.finisher(), finished, downstream.characteristics()); + } + return Collector.of(downstream.supplier(), (acc, t) -> { + try (Stream stream = mapper.apply(t)) { + if (stream != null) { + stream.spliterator().forEachRemaining(u -> downstreamAccumulator.accept(acc, u)); + } + } + }, downstream.combiner(), downstream.finisher(), + downstream.characteristics().toArray(new Characteristics[downstream.characteristics().size()])); + } + /** * Returns a {@code Collector} which passes only those elements to the * specified downstream collector which match given predicate. @@ -1261,10 +1296,10 @@ public static Collector collectingAndThen(Collector finished = finished(downstream); + if (finished != null) { return new CancellableCollectorImpl<>(downstream.supplier(), accumulator, downstream.combiner(), - downstream.finisher(), ((CancellableCollector) downstream).finished(), - downstream.characteristics()); + downstream.finisher(), finished, downstream.characteristics()); } return Collector.of(downstream.supplier(), accumulator, downstream.combiner(), downstream.finisher(), downstream.characteristics().toArray(new Characteristics[downstream.characteristics().size()])); diff --git a/src/main/java/javax/util/streamex/StreamExInternals.java b/src/main/java/javax/util/streamex/StreamExInternals.java index cf11b013..2003d4f6 100644 --- a/src/main/java/javax/util/streamex/StreamExInternals.java +++ b/src/main/java/javax/util/streamex/StreamExInternals.java @@ -1155,6 +1155,12 @@ static Stream flatTraverse(Stream src, Function> streamPr static Stream unwrap(Stream stream) { return stream instanceof AbstractStreamEx ? ((AbstractStreamEx) stream).stream : stream; } + + static Predicate finished(Collector collector) { + if(collector instanceof CancellableCollector) + return ((CancellableCollector)collector).finished(); + return null; + } @SuppressWarnings("unchecked") static T none() { From 4da67f3eed8415095b0a850cb6876295896548ff Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Thu, 29 Oct 2015 11:46:34 +0600 Subject: [PATCH 38/42] [#14] Tests for MoreCollectors.flatMapping --- .../util/streamex/MoreCollectorsTest.java | 110 +++++++++++++++--- 1 file changed, 94 insertions(+), 16 deletions(-) diff --git a/src/test/java/javax/util/streamex/MoreCollectorsTest.java b/src/test/java/javax/util/streamex/MoreCollectorsTest.java index e912bb04..269a04f6 100644 --- a/src/test/java/javax/util/streamex/MoreCollectorsTest.java +++ b/src/test/java/javax/util/streamex/MoreCollectorsTest.java @@ -168,9 +168,10 @@ private List getMaxAll(List ints, Comparator c) { public void testFirstLast() { Supplier> s = () -> IntStreamEx.range(1000).boxed(); checkShortCircuitCollector("first", Optional.of(0), 1, s, MoreCollectors.first()); - checkShortCircuitCollector("firstLong", Optional.of(0), - 1, () -> Stream.of(1).flatMap(x -> IntStream.range(0, 1000000000).boxed()), MoreCollectors.first(), true); - checkShortCircuitCollector("first", Optional.of(1), 1, () -> Stream.iterate(1, x -> x + 1), MoreCollectors.first(), true); + checkShortCircuitCollector("firstLong", Optional.of(0), 1, + () -> Stream.of(1).flatMap(x -> IntStream.range(0, 1000000000).boxed()), MoreCollectors.first(), true); + checkShortCircuitCollector("first", Optional.of(1), 1, () -> Stream.iterate(1, x -> x + 1), + MoreCollectors.first(), true); assertEquals(1, (int) StreamEx.iterate(1, x -> x + 1).parallel().collect(MoreCollectors.first()).get()); checkCollector("last", Optional.of(999), s, MoreCollectors.last()); @@ -185,8 +186,8 @@ public void testHeadParallel() { for (int i = 0; i < 1000; i++) { assertEquals("#" + i, expectedShort, IntStreamEx.range(1000).boxed().parallel().collect(MoreCollectors.head(2))); - assertEquals("#" + i, expected, IntStreamEx.range(10000).boxed() - .parallel().filter(x -> x % 2 == 0).collect(MoreCollectors.head(1000))); + assertEquals("#" + i, expected, IntStreamEx.range(10000).boxed().parallel().filter(x -> x % 2 == 0) + .collect(MoreCollectors.head(1000))); } assertEquals(expectedShort, StreamEx.iterate(0, x -> x + 1).parallel().collect(MoreCollectors.head(2))); } @@ -209,7 +210,7 @@ public void testHeadTail() { checkShortCircuitCollector("head(999)", ints.subList(0, 999), 999, ints::stream, MoreCollectors.head(999)); checkShortCircuitCollector("head(1000)", ints, 1000, ints::stream, MoreCollectors.head(1000)); checkShortCircuitCollector("head(MAX)", ints, 1000, ints::stream, MoreCollectors.head(Integer.MAX_VALUE)); - + checkShortCircuitCollector("head(10000)", IntStreamEx.rangeClosed(1, 10000).boxed().toList(), 10000, () -> Stream.iterate(1, x -> x + 1), MoreCollectors.head(10000), true); } @@ -287,7 +288,7 @@ public void testGroupingByWithDomainException() { Map> map = list.stream().collect(c); System.out.println(map); } - + @Test public void testGroupingByWithDomain() { List data = Arrays.asList("a", "foo", "test", "ququq", "bar", "blahblah"); @@ -313,7 +314,8 @@ public void testGroupingByWithDomain() { Entry::getValue, StreamEx.of("Girl", "Boy").toSet(), MoreCollectors.mapping(Entry::getKey, MoreCollectors.head(2))); AtomicInteger counter = new AtomicInteger(); - Map> map = EntryStream.of(name2sex).peek(c -> counter.incrementAndGet()).collect(groupingBy); + Map> map = EntryStream.of(name2sex).peek(c -> counter.incrementAndGet()) + .collect(groupingBy); assertEquals(Arrays.asList("Mary", "Lucie"), map.get("Girl")); assertEquals(Arrays.asList("John", "James"), map.get("Boy")); assertEquals(4, counter.get()); @@ -353,15 +355,16 @@ public void testMapping() { Collector>> collectorLast = MoreCollectors.partitioningBy( str -> Character.isUpperCase(str.charAt(0)), MoreCollectors.mapping(String::length, MoreCollectors.last())); checkCollector("last", new BooleanMap<>(Optional.of(3), Optional.of(3)), input::stream, collectorLast); - + input = Arrays.asList("Abc", "Bac", "Aac", "Abv", "Bbc", "Bgd", "Atc", "Bpv"); - Map> expected = EntryStream.of( - 'A', Arrays.asList("Abc", "Aac"), - 'B', Arrays.asList("Bac", "Bbc") - ).toMap(); + Map> expected = EntryStream.of('A', Arrays.asList("Abc", "Aac"), 'B', + Arrays.asList("Bac", "Bbc")).toMap(); AtomicInteger cnt = new AtomicInteger(); - Collector>> groupMap = Collectors.groupingBy(s -> s.charAt(0), - MoreCollectors.mapping(x -> {cnt.incrementAndGet(); return x;}, MoreCollectors.head(2))); + Collector>> groupMap = Collectors.groupingBy(s -> s.charAt(0), + MoreCollectors.mapping(x -> { + cnt.incrementAndGet(); + return x; + }, MoreCollectors.head(2))); checkCollector("groupMap", expected, input::stream, groupMap); cnt.set(0); assertEquals(expected, input.stream().collect(groupMap)); @@ -410,7 +413,7 @@ public void testAndLong() { MoreCollectors.andingLong(Long::longValue)); checkCollectorEmpty("andLongEmpty", OptionalLong.empty(), MoreCollectors.andingLong(Long::longValue)); } - + @Test public void testAndLongFlatMap() { checkShortCircuitCollector("andLongFlat", OptionalLong.of(0), 2, @@ -459,4 +462,79 @@ public void testToEnumSet() { MoreCollectors.toEnumSet(TimeUnit.class)); checkCollectorEmpty("Empty", EnumSet.noneOf(TimeUnit.class), MoreCollectors.toEnumSet(TimeUnit.class)); } + + @Test + public void testFlatMapping() { + { + Map> expected = IntStreamEx.rangeClosed(1, 100).boxed() + .toMap(x -> IntStreamEx.rangeClosed(1, x).boxed().toList()); + checkCollector( + "flatMappingSimple", + expected, + () -> IntStreamEx.rangeClosed(1, 100).boxed(), + Collectors.groupingBy(Function.identity(), + MoreCollectors.flatMapping(x -> IntStream.rangeClosed(1, x).boxed(), Collectors.toList()))); + } + + Function>, Stream> valuesStream = e -> e.getValue() == null ? null : e + .getValue().stream(); + List>> list = EntryStream + .of("a", Arrays.asList("bb", "cc", "dd"), "b", Arrays.asList("ee", "ff"), "c", null) + .append("c", Arrays.asList("gg"), "b", null, "a", Arrays.asList("hh")).toList(); + { + Map> expected = EntryStream.of(list.stream()) + .flatMapValues(l -> l == null ? null : l.stream()).grouping(); + checkCollector("flatMappingCombine", expected, list::stream, + Collectors.groupingBy(Entry::getKey, MoreCollectors.flatMapping(valuesStream, Collectors.toList()))); + AtomicInteger openClose = new AtomicInteger(); + checkCollector("flatMappingCombineClosed", expected, list::stream, + MoreCollectors.collectingAndThen(Collectors.groupingBy(Entry::getKey, MoreCollectors.flatMapping(valuesStream.andThen(s -> { + if(s == null) return null; + openClose.incrementAndGet(); + return s.onClose(openClose::decrementAndGet); + }), Collectors.toList())), res -> { + assertEquals(0, openClose.get()); + return res; + })); + boolean catched = false; + try { + list.stream().collect( + MoreCollectors.collectingAndThen(Collectors.groupingBy(Entry::getKey, MoreCollectors.flatMapping(valuesStream.andThen(s -> { + if(s == null) return null; + openClose.incrementAndGet(); + return s.onClose(openClose::decrementAndGet).peek(e -> { + if(e.equals("gg")) + throw new IllegalArgumentException(e); + }); + }), Collectors.toList())), res -> { + assertEquals(0, openClose.get()); + return res; + })); + } catch (IllegalArgumentException e1) { + assertEquals("gg", e1.getMessage()); + catched = true; + } + assertTrue(catched); + } + { + Map> expected = EntryStream.of("a", Arrays.asList("bb"), "b", Arrays.asList("ee"), + "c", Arrays.asList("gg")).toMap(); + Collector>, ?, List> headOne = MoreCollectors.flatMapping(valuesStream, + MoreCollectors.head(1)); + checkCollector("flatMappingSubShort", expected, list::stream, Collectors.groupingBy(Entry::getKey, headOne)); + checkShortCircuitCollector("flatMappingShort", expected, 4, list::stream, + MoreCollectors.groupingBy(Entry::getKey, StreamEx.of("a", "b", "c").toSet(), headOne)); + AtomicInteger cnt = new AtomicInteger(); + Collector>, ?, List> headPeek = MoreCollectors.flatMapping( + valuesStream.andThen(s -> s == null ? null : s.peek(x -> cnt.incrementAndGet())), MoreCollectors.head(1)); + assertEquals(expected, StreamEx.of(list).collect(Collectors.groupingBy(Entry::getKey, headPeek))); + assertEquals(3, cnt.get()); + cnt.set(0); + assertEquals( + expected, + StreamEx.of(list).collect( + MoreCollectors.groupingBy(Entry::getKey, StreamEx.of("a", "b", "c").toSet(), headPeek))); + assertEquals(3, cnt.get()); + } + } } From fb46f9a8214ee5f2e5062897fb1d2943e7eb552c Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Thu, 29 Oct 2015 11:57:54 +0600 Subject: [PATCH 39/42] [#14] MoreCollectorsTest: fixed compilation error with Javac 8u31 --- .../util/streamex/MoreCollectorsTest.java | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/test/java/javax/util/streamex/MoreCollectorsTest.java b/src/test/java/javax/util/streamex/MoreCollectorsTest.java index 269a04f6..365691bd 100644 --- a/src/test/java/javax/util/streamex/MoreCollectorsTest.java +++ b/src/test/java/javax/util/streamex/MoreCollectorsTest.java @@ -487,26 +487,28 @@ public void testFlatMapping() { checkCollector("flatMappingCombine", expected, list::stream, Collectors.groupingBy(Entry::getKey, MoreCollectors.flatMapping(valuesStream, Collectors.toList()))); AtomicInteger openClose = new AtomicInteger(); + Collector>, ?, Map>> groupingBy = Collectors.groupingBy(Entry::getKey, MoreCollectors.flatMapping(valuesStream.andThen(s -> { + if(s == null) return null; + openClose.incrementAndGet(); + return s.onClose(openClose::decrementAndGet); + }), Collectors.toList())); checkCollector("flatMappingCombineClosed", expected, list::stream, - MoreCollectors.collectingAndThen(Collectors.groupingBy(Entry::getKey, MoreCollectors.flatMapping(valuesStream.andThen(s -> { - if(s == null) return null; - openClose.incrementAndGet(); - return s.onClose(openClose::decrementAndGet); - }), Collectors.toList())), res -> { + MoreCollectors.collectingAndThen(groupingBy, res -> { assertEquals(0, openClose.get()); return res; })); boolean catched = false; try { + Collector>, ?, Map>> groupingByException = Collectors.groupingBy(Entry::getKey, MoreCollectors.flatMapping(valuesStream.andThen(s -> { + if(s == null) return null; + openClose.incrementAndGet(); + return s.onClose(openClose::decrementAndGet).peek(e -> { + if(e.equals("gg")) + throw new IllegalArgumentException(e); + }); + }), Collectors.toList())); list.stream().collect( - MoreCollectors.collectingAndThen(Collectors.groupingBy(Entry::getKey, MoreCollectors.flatMapping(valuesStream.andThen(s -> { - if(s == null) return null; - openClose.incrementAndGet(); - return s.onClose(openClose::decrementAndGet).peek(e -> { - if(e.equals("gg")) - throw new IllegalArgumentException(e); - }); - }), Collectors.toList())), res -> { + MoreCollectors.collectingAndThen(groupingByException, res -> { assertEquals(0, openClose.get()); return res; })); From 5a84d6865d949d03a0708e502f64ec38aebde825 Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Thu, 29 Oct 2015 12:10:52 +0600 Subject: [PATCH 40/42] [#14] MoreCollectorsTest: more tests for short-circuiting flatMapping --- .../javax/util/streamex/MoreCollectors.java | 16 +++++------ .../util/streamex/MoreCollectorsTest.java | 28 +++++++++++++++++++ 2 files changed, 35 insertions(+), 9 deletions(-) diff --git a/src/main/java/javax/util/streamex/MoreCollectors.java b/src/main/java/javax/util/streamex/MoreCollectors.java index bb933d04..106d0f65 100644 --- a/src/main/java/javax/util/streamex/MoreCollectors.java +++ b/src/main/java/javax/util/streamex/MoreCollectors.java @@ -1236,16 +1236,14 @@ public static Collector collectingAndThen(Collector stream = mapper.apply(t)) { if (stream != null) { - try { - stream.spliterator().forEachRemaining(u -> { - downstreamAccumulator.accept(acc, u); - if(finished.test(acc)) - throw new CancelException(); - }); - } catch (CancelException ex) { - // ignore - } + stream.spliterator().forEachRemaining(u -> { + downstreamAccumulator.accept(acc, u); + if(finished.test(acc)) + throw new CancelException(); + }); } + } catch (CancelException ex) { + // ignore } }, downstream.combiner(), downstream.finisher(), finished, downstream.characteristics()); } diff --git a/src/test/java/javax/util/streamex/MoreCollectorsTest.java b/src/test/java/javax/util/streamex/MoreCollectorsTest.java index 365691bd..2a7712e6 100644 --- a/src/test/java/javax/util/streamex/MoreCollectorsTest.java +++ b/src/test/java/javax/util/streamex/MoreCollectorsTest.java @@ -538,5 +538,33 @@ public void testFlatMapping() { MoreCollectors.groupingBy(Entry::getKey, StreamEx.of("a", "b", "c").toSet(), headPeek))); assertEquals(3, cnt.get()); } + { + Map> expected = EntryStream.of("a", Arrays.asList("bb", "cc"), "b", Arrays.asList("ee", "ff"), + "c", Arrays.asList("gg")).toMap(); + Collector>, ?, List> headTwo = MoreCollectors.flatMapping(valuesStream, + MoreCollectors.head(2)); + checkCollector("flatMappingSubShort", expected, list::stream, Collectors.groupingBy(Entry::getKey, headTwo)); + AtomicInteger openClose = new AtomicInteger(); + boolean catched = false; + try { + Collector>, ?, Map>> groupingByException = Collectors.groupingBy(Entry::getKey, MoreCollectors.flatMapping(valuesStream.andThen(s -> { + if(s == null) return null; + openClose.incrementAndGet(); + return s.onClose(openClose::decrementAndGet).peek(e -> { + if(e.equals("gg")) + throw new IllegalArgumentException(e); + }); + }), MoreCollectors.head(2))); + list.stream().collect( + MoreCollectors.collectingAndThen(groupingByException, res -> { + assertEquals(0, openClose.get()); + return res; + })); + } catch (IllegalArgumentException e1) { + assertEquals("gg", e1.getMessage()); + catched = true; + } + assertTrue(catched); + } } } From d9a2932accbbca080fd5b60e34b077382b569185 Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Fri, 30 Oct 2015 10:09:22 +0600 Subject: [PATCH 41/42] [#14] MoreCollectors.flatMapping: documentation, changes --- CHANGES.md | 1 + .../javax/util/streamex/MoreCollectors.java | 38 ++++++++++++++++++- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 09a8d4ef..5270d001 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,6 +3,7 @@ ### 0.4.1 * Added: `StreamEx/IntStreamEx/LongStreamEx/DoubleStreamEx.mapLast/mapFirst` methods. +* Added: `MoreCollectors.flatMapping` collector. * Fixed: `StreamEx.cross(mapper)` now correctly handles the case when mapper returns null instead of empty stream. * Optimized: ordered stateful short-circuit collectors now may process less elements in parallel. * Optimized: `StreamEx/EntryStream.toList()/toListAndThen()/foldRight()/scanRight()` now faster, especially for sized stream. diff --git a/src/main/java/javax/util/streamex/MoreCollectors.java b/src/main/java/javax/util/streamex/MoreCollectors.java index 106d0f65..691c30ae 100644 --- a/src/main/java/javax/util/streamex/MoreCollectors.java +++ b/src/main/java/javax/util/streamex/MoreCollectors.java @@ -1226,19 +1226,53 @@ public static Collector collectingAndThen(Collectorshort-circuiting, + * this method will also return a short-circuiting collector. + * + * @param + * the type of the input elements + * @param + * type of elements accepted by downstream collector + * @param + * intermediate accumulation type of the downstream collector + * @param + * result type of collector + * @param mapper + * a function to be applied to the input elements, which returns + * a stream of results + * @param downstream + * a collector which will receive the elements of the stream + * returned by mapper + * @return a collector which applies the mapping function to the input + * elements and provides the flat mapped results to the downstream + * collector + * @since 0.4.1 + */ public static Collector flatMapping( Function> mapper, Collector downstream) { BiConsumer downstreamAccumulator = downstream.accumulator(); Predicate finished = finished(downstream); if (finished != null) { return new CancellableCollectorImpl<>(downstream.supplier(), (acc, t) -> { - if(finished.test(acc)) + if (finished.test(acc)) return; try (Stream stream = mapper.apply(t)) { if (stream != null) { stream.spliterator().forEachRemaining(u -> { downstreamAccumulator.accept(acc, u); - if(finished.test(acc)) + if (finished.test(acc)) throw new CancelException(); }); } From 678e8f77968d02718476dc08a7a5475a2c3791ff Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Sun, 1 Nov 2015 15:46:09 +0600 Subject: [PATCH 42/42] CancellableCollector is an abstract class now (at least until it will be converted to public API) --- .../javax/util/streamex/AbstractStreamEx.java | 21 +++++++------- .../util/streamex/CancellableCollector.java | 29 ------------------- .../util/streamex/StreamExInternals.java | 10 +++++-- .../java/javax/util/streamex/TestHelpers.java | 7 +++-- 4 files changed, 21 insertions(+), 46 deletions(-) delete mode 100644 src/main/java/javax/util/streamex/CancellableCollector.java diff --git a/src/main/java/javax/util/streamex/AbstractStreamEx.java b/src/main/java/javax/util/streamex/AbstractStreamEx.java index 7337cae3..cc405dfa 100644 --- a/src/main/java/javax/util/streamex/AbstractStreamEx.java +++ b/src/main/java/javax/util/streamex/AbstractStreamEx.java @@ -272,14 +272,13 @@ public R collect(Supplier supplier, BiConsumer accumulator, */ @Override public R collect(Collector collector) { - if (collector instanceof CancellableCollector) { - CancellableCollector c = (CancellableCollector) collector; - BiConsumer acc = c.accumulator(); - Predicate finished = c.finished(); - BinaryOperator combiner = c.combiner(); + Predicate finished = finished(collector); + if (finished != null) { + BiConsumer acc = collector.accumulator(); + BinaryOperator combiner = collector.combiner(); Spliterator spliterator = stream.spliterator(); if (!isParallel()) { - A a = c.supplier().get(); + A a = collector.supplier().get(); if (!finished.test(a)) { try { // forEachRemaining can be much faster @@ -294,16 +293,16 @@ public R collect(Collector collector) { // ignore } } - return c.finisher().apply(a); + return collector.finisher().apply(a); } Spliterator spltr; if (!spliterator.hasCharacteristics(Spliterator.ORDERED) - || c.characteristics().contains(Characteristics.UNORDERED)) { - spltr = new UnorderedCancellableSpliterator<>(spliterator, c.supplier(), acc, combiner, finished); + || collector.characteristics().contains(Characteristics.UNORDERED)) { + spltr = new UnorderedCancellableSpliterator<>(spliterator, collector.supplier(), acc, combiner, finished); } else { - spltr = new OrderedCancellableSpliterator<>(spliterator, c.supplier(), acc, combiner, finished); + spltr = new OrderedCancellableSpliterator<>(spliterator, collector.supplier(), acc, combiner, finished); } - return c.finisher().apply(strategy().newStreamEx(StreamSupport.stream(spltr, true)).findFirst().get()); + return collector.finisher().apply(strategy().newStreamEx(StreamSupport.stream(spltr, true)).findFirst().get()); } return rawCollect(collector); } diff --git a/src/main/java/javax/util/streamex/CancellableCollector.java b/src/main/java/javax/util/streamex/CancellableCollector.java deleted file mode 100644 index 77fea141..00000000 --- a/src/main/java/javax/util/streamex/CancellableCollector.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2015 Tagir Valeev - * - * 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 javax.util.streamex; - -import java.util.function.Predicate; -import java.util.stream.Collector; - -/** - * Part of non-public API currently. Probably will be published in future. - * - * @author Tagir Valeev - */ -/* package */ interface CancellableCollector extends Collector { - - public Predicate finished(); -} diff --git a/src/main/java/javax/util/streamex/StreamExInternals.java b/src/main/java/javax/util/streamex/StreamExInternals.java index 2003d4f6..1846fff5 100644 --- a/src/main/java/javax/util/streamex/StreamExInternals.java +++ b/src/main/java/javax/util/streamex/StreamExInternals.java @@ -490,7 +490,7 @@ Collector asRef(BiConsumer accumulator) { characteristics.toArray(new Characteristics[characteristics.size()])); } - CancellableCollector asCancellable(BiConsumer accumulator, Predicate finished) { + Collector asCancellable(BiConsumer accumulator, Predicate finished) { return new CancellableCollectorImpl<>(supplier, accumulator, combiner(), finisher, finished, characteristics); } @@ -553,8 +553,12 @@ static PartialCollector joining(CharSequence delimiter, C return new PartialCollector<>(supplier, merger, StringBuilder::toString, NO_CHARACTERISTICS); } } + + static abstract class CancellableCollector implements Collector { + abstract Predicate finished(); + } - static final class CancellableCollectorImpl implements CancellableCollector { + static final class CancellableCollectorImpl extends CancellableCollector { private final Supplier supplier; private final BiConsumer accumulator; private final BinaryOperator combiner; @@ -599,7 +603,7 @@ public Set characteristics() { } @Override - public Predicate finished() { + Predicate finished() { return finished; } } diff --git a/src/test/java/javax/util/streamex/TestHelpers.java b/src/test/java/javax/util/streamex/TestHelpers.java index 71d9ac03..dc7df31a 100644 --- a/src/test/java/javax/util/streamex/TestHelpers.java +++ b/src/test/java/javax/util/streamex/TestHelpers.java @@ -16,6 +16,7 @@ package javax.util.streamex; import static org.junit.Assert.*; +import static javax.util.streamex.StreamExInternals.*; import java.util.ArrayList; import java.util.Arrays; @@ -101,7 +102,7 @@ static List> streamEx(Supplier> base) { } static void checkCollectorEmpty(String message, R expected, Collector collector) { - if (collector instanceof CancellableCollector) + if (finished(collector) != null) checkShortCircuitCollector(message, expected, 0, Stream::empty, collector); else checkCollector(message, expected, Stream::empty, collector); @@ -114,7 +115,7 @@ static void checkShortCircuitCollector(String message, R expected, int ex static void checkShortCircuitCollector(String message, R expected, int expectedConsumedElements, Supplier> base, Collector collector, boolean skipIdentity) { - assertTrue(message, collector instanceof CancellableCollector); + assertNotNull(message, finished(collector)); Collector withIdentity = Collectors.collectingAndThen(collector, Function.identity()); for (StreamExSupplier supplier : streamEx(base)) { AtomicInteger counter = new AtomicInteger(); @@ -129,7 +130,7 @@ static void checkShortCircuitCollector(String message, R expected, int ex static void checkCollector(String message, R expected, Supplier> base, Collector collector) { // use checkShortCircuitCollector for CancellableCollector - assertFalse(message, collector instanceof CancellableCollector); + assertNull(message, finished(collector)); for (StreamExSupplier supplier : streamEx(base)) { assertEquals(message + ": " + supplier, expected, supplier.get().collect(collector)); }