8015315: Stream.concat methods
authormduigou
Fri, 12 Jul 2013 12:15:22 -0700
changeset 18820 a87cdd6a8834
parent 18819 c4335fc31aeb
child 18821 50a3b8a90563
8015315: Stream.concat methods Reviewed-by: psandoz, mduigou Contributed-by: brian.goetz@oracle.com, henry.jen@oracle.com
jdk/src/share/classes/java/util/stream/DoubleStream.java
jdk/src/share/classes/java/util/stream/IntStream.java
jdk/src/share/classes/java/util/stream/LongStream.java
jdk/src/share/classes/java/util/stream/Stream.java
jdk/src/share/classes/java/util/stream/Streams.java
jdk/test/java/util/stream/bootlib/java/util/stream/LambdaTestHelpers.java
jdk/test/java/util/stream/test/org/openjdk/tests/java/util/stream/ConcatOpTest.java
jdk/test/java/util/stream/test/org/openjdk/tests/java/util/stream/ConcatTest.java
jdk/test/java/util/stream/test/org/openjdk/tests/java/util/stream/RangeTest.java
--- a/jdk/src/share/classes/java/util/stream/DoubleStream.java	Fri Jul 12 11:12:16 2013 -0700
+++ b/jdk/src/share/classes/java/util/stream/DoubleStream.java	Fri Jul 12 12:15:22 2013 -0700
@@ -746,4 +746,26 @@
         return StreamSupport.doubleStream(
                 new StreamSpliterators.InfiniteSupplyingSpliterator.OfDouble(Long.MAX_VALUE, s));
     }
+
+    /**
+     * Creates a lazy concatenated {@code DoubleStream} whose elements are all the
+     * elements of a first {@code DoubleStream} succeeded by all the elements of the
+     * second {@code DoubleStream}. The resulting stream is ordered if both
+     * of the input streams are ordered, and parallel if either of the input
+     * streams is parallel.
+     *
+     * @param a the first stream
+     * @param b the second stream to concatenate on to end of the first stream
+     * @return the concatenation of the two streams
+     */
+    public static DoubleStream concat(DoubleStream a, DoubleStream b) {
+        Objects.requireNonNull(a);
+        Objects.requireNonNull(b);
+
+        Spliterator.OfDouble split = new Streams.ConcatSpliterator.OfDouble(
+                a.spliterator(), b.spliterator());
+        return (a.isParallel() || b.isParallel())
+               ? StreamSupport.doubleParallelStream(split)
+               : StreamSupport.doubleStream(split);
+    }
 }
--- a/jdk/src/share/classes/java/util/stream/IntStream.java	Fri Jul 12 11:12:16 2013 -0700
+++ b/jdk/src/share/classes/java/util/stream/IntStream.java	Fri Jul 12 12:15:22 2013 -0700
@@ -800,4 +800,26 @@
                     new Streams.RangeIntSpliterator(startInclusive, endInclusive, true));
         }
     }
+
+    /**
+     * Creates a lazy concatenated {@code IntStream} whose elements are all the
+     * elements of a first {@code IntStream} succeeded by all the elements of the
+     * second {@code IntStream}. The resulting stream is ordered if both
+     * of the input streams are ordered, and parallel if either of the input
+     * streams is parallel.
+     *
+     * @param a the first stream
+     * @param b the second stream to concatenate on to end of the first stream
+     * @return the concatenation of the two streams
+     */
+    public static IntStream concat(IntStream a, IntStream b) {
+        Objects.requireNonNull(a);
+        Objects.requireNonNull(b);
+
+        Spliterator.OfInt split = new Streams.ConcatSpliterator.OfInt(
+                a.spliterator(), b.spliterator());
+        return (a.isParallel() || b.isParallel())
+               ? StreamSupport.intParallelStream(split)
+               : StreamSupport.intStream(split);
+    }
 }
--- a/jdk/src/share/classes/java/util/stream/LongStream.java	Fri Jul 12 11:12:16 2013 -0700
+++ b/jdk/src/share/classes/java/util/stream/LongStream.java	Fri Jul 12 12:15:22 2013 -0700
@@ -765,10 +765,8 @@
             // Split the range in two and concatenate
             // Note: if the range is [Long.MIN_VALUE, Long.MAX_VALUE) then
             // the lower range, [Long.MIN_VALUE, 0) will be further split in two
-//            long m = startInclusive + Long.divideUnsigned(endExclusive - startInclusive, 2) + 1;
-//            return Streams.concat(range(startInclusive, m), range(m, endExclusive));
-            // This is temporary until Streams.concat is supported
-            throw new UnsupportedOperationException();
+            long m = startInclusive + Long.divideUnsigned(endExclusive - startInclusive, 2) + 1;
+            return concat(range(startInclusive, m), range(m, endExclusive));
         } else {
             return StreamSupport.longStream(
                     new Streams.RangeLongSpliterator(startInclusive, endExclusive, false));
@@ -801,13 +799,33 @@
             // Note: if the range is [Long.MIN_VALUE, Long.MAX_VALUE] then
             // the lower range, [Long.MIN_VALUE, 0), and upper range,
             // [0, Long.MAX_VALUE], will both be further split in two
-//            long m = startInclusive + Long.divideUnsigned(endInclusive - startInclusive, 2) + 1;
-//            return Streams.concat(range(startInclusive, m), rangeClosed(m, endInclusive));
-            // This is temporary until Streams.concat is supported
-            throw new UnsupportedOperationException();
+            long m = startInclusive + Long.divideUnsigned(endInclusive - startInclusive, 2) + 1;
+            return concat(range(startInclusive, m), rangeClosed(m, endInclusive));
         } else {
             return StreamSupport.longStream(
                     new Streams.RangeLongSpliterator(startInclusive, endInclusive, true));
         }
     }
+
+    /**
+     * Creates a lazy concatenated {@code LongStream} whose elements are all the
+     * elements of a first {@code LongStream} succeeded by all the elements of the
+     * second {@code LongStream}. The resulting stream is ordered if both
+     * of the input streams are ordered, and parallel if either of the input
+     * streams is parallel.
+     *
+     * @param a the first stream
+     * @param b the second stream to concatenate on to end of the first stream
+     * @return the concatenation of the two streams
+     */
+    public static LongStream concat(LongStream a, LongStream b) {
+        Objects.requireNonNull(a);
+        Objects.requireNonNull(b);
+
+        Spliterator.OfLong split = new Streams.ConcatSpliterator.OfLong(
+                a.spliterator(), b.spliterator());
+        return (a.isParallel() || b.isParallel())
+               ? StreamSupport.longParallelStream(split)
+               : StreamSupport.longStream(split);
+    }
 }
--- a/jdk/src/share/classes/java/util/stream/Stream.java	Fri Jul 12 11:12:16 2013 -0700
+++ b/jdk/src/share/classes/java/util/stream/Stream.java	Fri Jul 12 12:15:22 2013 -0700
@@ -883,4 +883,29 @@
         return StreamSupport.stream(
                 new StreamSpliterators.InfiniteSupplyingSpliterator.OfRef<>(Long.MAX_VALUE, s));
     }
+
+    /**
+     * Creates a lazy concatenated {@code Stream} whose elements are all the
+     * elements of a first {@code Stream} succeeded by all the elements of the
+     * second {@code Stream}. The resulting stream is ordered if both
+     * of the input streams are ordered, and parallel if either of the input
+     * streams is parallel.
+     *
+     * @param <T> The type of stream elements
+     * @param a the first stream
+     * @param b the second stream to concatenate on to end of the first
+     *        stream
+     * @return the concatenation of the two input streams
+     */
+    public static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b) {
+        Objects.requireNonNull(a);
+        Objects.requireNonNull(b);
+
+        @SuppressWarnings("unchecked")
+        Spliterator<T> split = new Streams.ConcatSpliterator.OfRef<>(
+                (Spliterator<T>) a.spliterator(), (Spliterator<T>) b.spliterator());
+        return (a.isParallel() || b.isParallel())
+               ? StreamSupport.parallelStream(split)
+               : StreamSupport.stream(split);
+    }
 }
--- a/jdk/src/share/classes/java/util/stream/Streams.java	Fri Jul 12 11:12:16 2013 -0700
+++ b/jdk/src/share/classes/java/util/stream/Streams.java	Fri Jul 12 12:15:22 2013 -0700
@@ -43,7 +43,7 @@
  *
  * @since 1.8
  */
-class Streams {
+final class Streams {
 
     private Streams() {
         throw new Error("no instances");
@@ -670,4 +670,147 @@
             }
         }
     }
+
+    abstract static class ConcatSpliterator<T, T_SPLITR extends Spliterator<T>>
+            implements Spliterator<T> {
+        protected final T_SPLITR aSpliterator;
+        protected final T_SPLITR bSpliterator;
+        // True when no split has occurred, otherwise false
+        boolean beforeSplit;
+        // Never read after splitting
+        final boolean unsized;
+
+        public ConcatSpliterator(T_SPLITR aSpliterator, T_SPLITR bSpliterator) {
+            this.aSpliterator = aSpliterator;
+            this.bSpliterator = bSpliterator;
+            beforeSplit = true;
+            // The spliterator is unsized before splitting if a and b are
+            // sized and the sum of the estimates overflows
+            unsized = aSpliterator.hasCharacteristics(SIZED)
+                      && aSpliterator.hasCharacteristics(SIZED)
+                      && aSpliterator.estimateSize() + bSpliterator.estimateSize() < 0;
+        }
+
+        @Override
+        public T_SPLITR trySplit() {
+            T_SPLITR ret = beforeSplit ? aSpliterator : (T_SPLITR) bSpliterator.trySplit();
+            beforeSplit = false;
+            return ret;
+        }
+
+        @Override
+        public boolean tryAdvance(Consumer<? super T> consumer) {
+            boolean hasNext;
+            if (beforeSplit) {
+                hasNext = aSpliterator.tryAdvance(consumer);
+                if (!hasNext) {
+                    beforeSplit = false;
+                    hasNext = bSpliterator.tryAdvance(consumer);
+                }
+            }
+            else
+                hasNext = bSpliterator.tryAdvance(consumer);
+            return hasNext;
+        }
+
+        @Override
+        public void forEachRemaining(Consumer<? super T> consumer) {
+            if (beforeSplit)
+                aSpliterator.forEachRemaining(consumer);
+            bSpliterator.forEachRemaining(consumer);
+        }
+
+        @Override
+        public long estimateSize() {
+            if (beforeSplit) {
+                // If one or both estimates are Long.MAX_VALUE then the sum
+                // will either be Long.MAX_VALUE or overflow to a negative value
+                long size = aSpliterator.estimateSize() + bSpliterator.estimateSize();
+                return (size >= 0) ? size : Long.MAX_VALUE;
+            }
+            else {
+                return bSpliterator.estimateSize();
+            }
+        }
+
+        @Override
+        public int characteristics() {
+            if (beforeSplit) {
+                // Concatenation loses DISTINCT and SORTED characteristics
+                return aSpliterator.characteristics() & bSpliterator.characteristics()
+                       & ~(Spliterator.DISTINCT | Spliterator.SORTED
+                           | (unsized ? Spliterator.SIZED | Spliterator.SUBSIZED : 0));
+            }
+            else {
+                return bSpliterator.characteristics();
+            }
+        }
+
+        @Override
+        public Comparator<? super T> getComparator() {
+            if (beforeSplit)
+                throw new IllegalStateException();
+            return bSpliterator.getComparator();
+        }
+
+        static class OfRef<T> extends ConcatSpliterator<T, Spliterator<T>> {
+            OfRef(Spliterator<T> aSpliterator, Spliterator<T> bSpliterator) {
+                super(aSpliterator, bSpliterator);
+            }
+        }
+
+        private static abstract class OfPrimitive<T, T_CONS, T_SPLITR extends Spliterator.OfPrimitive<T, T_CONS, T_SPLITR>>
+                extends ConcatSpliterator<T, T_SPLITR>
+                implements Spliterator.OfPrimitive<T, T_CONS, T_SPLITR> {
+            private OfPrimitive(T_SPLITR aSpliterator, T_SPLITR bSpliterator) {
+                super(aSpliterator, bSpliterator);
+            }
+
+            @Override
+            public boolean tryAdvance(T_CONS action) {
+                boolean hasNext;
+                if (beforeSplit) {
+                    hasNext = aSpliterator.tryAdvance(action);
+                    if (!hasNext) {
+                        beforeSplit = false;
+                        hasNext = bSpliterator.tryAdvance(action);
+                    }
+                }
+                else
+                    hasNext = bSpliterator.tryAdvance(action);
+                return hasNext;
+            }
+
+            @Override
+            public void forEachRemaining(T_CONS action) {
+                if (beforeSplit)
+                    aSpliterator.forEachRemaining(action);
+                bSpliterator.forEachRemaining(action);
+            }
+        }
+
+        static class OfInt
+                extends ConcatSpliterator.OfPrimitive<Integer, IntConsumer, Spliterator.OfInt>
+                implements Spliterator.OfInt {
+            OfInt(Spliterator.OfInt aSpliterator, Spliterator.OfInt bSpliterator) {
+                super(aSpliterator, bSpliterator);
+            }
+        }
+
+        static class OfLong
+                extends ConcatSpliterator.OfPrimitive<Long, LongConsumer, Spliterator.OfLong>
+                implements Spliterator.OfLong {
+            OfLong(Spliterator.OfLong aSpliterator, Spliterator.OfLong bSpliterator) {
+                super(aSpliterator, bSpliterator);
+            }
+        }
+
+        static class OfDouble
+                extends ConcatSpliterator.OfPrimitive<Double, DoubleConsumer, Spliterator.OfDouble>
+                implements Spliterator.OfDouble {
+            OfDouble(Spliterator.OfDouble aSpliterator, Spliterator.OfDouble bSpliterator) {
+                super(aSpliterator, bSpliterator);
+            }
+        }
+    }
 }
--- a/jdk/test/java/util/stream/bootlib/java/util/stream/LambdaTestHelpers.java	Fri Jul 12 11:12:16 2013 -0700
+++ b/jdk/test/java/util/stream/bootlib/java/util/stream/LambdaTestHelpers.java	Fri Jul 12 12:15:22 2013 -0700
@@ -360,35 +360,26 @@
     private static<T> Map<T, Integer> toBoxedMultiset(Iterator<T> it) {
         Map<Object, Integer> result = new HashMap<>();
 
-        it.forEachRemaining(new OmnivorousConsumer<T>() {
-            @Override
-            public void accept(T t) {
-                add(t);
-            }
-
-            @Override
-            public void accept(int value) {
-                add(value);
-            }
-
-            @Override
-            public void accept(long value) {
-                add(value);
-            }
-
-            @Override
-            public void accept(double value) {
-                add(value);
-            }
-
-            void add(Object o) {
+        it.forEachRemaining(toBoxingConsumer(o -> {
                 if (result.containsKey(o))
                     result.put(o, result.get(o) + 1);
                 else
                     result.put(o, 1);
-            }
+            }));
+
+        return (Map<T, Integer>) result;
+    }
 
-        });
+    @SuppressWarnings("unchecked")
+    public static<T> Map<T, Integer> toBoxedMultiset(Spliterator<T> it) {
+        Map<Object, Integer> result = new HashMap<>();
+
+        it.forEachRemaining(toBoxingConsumer(o -> {
+                if (result.containsKey(o))
+                    result.put(o, result.get(o) + 1);
+                else
+                    result.put(o, 1);
+            }));
 
         return (Map<T, Integer>) result;
     }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/util/stream/test/org/openjdk/tests/java/util/stream/ConcatOpTest.java	Fri Jul 12 12:15:22 2013 -0700
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package org.openjdk.tests.java.util.stream;
+
+import java.util.stream.OpTestCase;
+import java.util.stream.StreamTestDataProvider;
+
+import org.testng.annotations.Test;
+
+import java.util.stream.Stream;
+import java.util.stream.IntStream;
+import java.util.stream.LongStream;
+import java.util.stream.DoubleStream;
+import java.util.stream.TestData;
+
+import static java.util.stream.LambdaTestHelpers.*;
+
+public class ConcatOpTest extends OpTestCase {
+
+    // Sanity to make sure all type of stream source works
+    @Test(dataProvider = "StreamTestData<Integer>", dataProviderClass = StreamTestDataProvider.class)
+    public void testOpsSequential(String name, TestData.OfRef<Integer> data) {
+        exerciseOpsInt(data,
+                       s -> Stream.concat(s, data.stream()),
+                       s -> IntStream.concat(s, data.stream().mapToInt(Integer::intValue)),
+                       s -> LongStream.concat(s, data.stream().mapToLong(Integer::longValue)),
+                       s -> DoubleStream.concat(s, data.stream().mapToDouble(Integer::doubleValue)));
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/util/stream/test/org/openjdk/tests/java/util/stream/ConcatTest.java	Fri Jul 12 12:15:22 2013 -0700
@@ -0,0 +1,229 @@
+/*
+ * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package org.openjdk.tests.java.util.stream;
+
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Factory;
+import org.testng.annotations.Test;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Spliterator;
+import java.util.TreeSet;
+import java.util.stream.DoubleStream;
+import java.util.stream.IntStream;
+import java.util.stream.LongStream;
+import java.util.stream.Stream;
+
+import static java.util.stream.LambdaTestHelpers.*;
+import static org.testng.Assert.*;
+
+@Test
+public class ConcatTest {
+    private static Object[][] cases;
+
+    static {
+        List<Integer> part1 = Arrays.asList(5, 3, 4, 1, 2, 6, 2, 4);
+        List<Integer> part2 = Arrays.asList(8, 8, 6, 6, 9, 7, 10, 9);
+        List<Integer> p1p2 = Arrays.asList(5, 3, 4, 1, 2, 6, 2, 4, 8, 8, 6, 6, 9, 7, 10, 9);
+        List<Integer> p2p1 = Arrays.asList(8, 8, 6, 6, 9, 7, 10, 9, 5, 3, 4, 1, 2, 6, 2, 4);
+        List<Integer> empty = new LinkedList<>(); // To be ordered
+        assertTrue(empty.isEmpty());
+        LinkedHashSet<Integer> distinctP1 = new LinkedHashSet<>(part1);
+        LinkedHashSet<Integer> distinctP2 = new LinkedHashSet<>(part2);
+        TreeSet<Integer> sortedP1 = new TreeSet<>(part1);
+        TreeSet<Integer> sortedP2 = new TreeSet<>(part2);
+
+        cases = new Object[][] {
+            { "regular", part1, part2, p1p2 },
+            { "reverse regular", part2, part1, p2p1 },
+            { "front distinct", distinctP1, part2, Arrays.asList(5, 3, 4, 1, 2, 6, 8, 8, 6, 6, 9, 7, 10, 9) },
+            { "back distinct", part1, distinctP2, Arrays.asList(5, 3, 4, 1, 2, 6, 2, 4, 8, 6, 9, 7, 10) },
+            { "both distinct", distinctP1, distinctP2, Arrays.asList(5, 3, 4, 1, 2, 6, 8, 6, 9, 7, 10) },
+            { "front sorted", sortedP1, part2, Arrays.asList(1, 2, 3, 4, 5, 6, 8, 8, 6, 6, 9, 7, 10, 9) },
+            { "back sorted", part1, sortedP2, Arrays.asList(5, 3, 4, 1, 2, 6, 2, 4, 6, 7, 8, 9, 10) },
+            { "both sorted", sortedP1, sortedP2, Arrays.asList(1, 2, 3, 4, 5, 6, 6, 7, 8, 9, 10) },
+            { "reverse both sorted", sortedP2, sortedP1, Arrays.asList(6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6) },
+            { "empty something", empty, part1, part1 },
+            { "something empty", part1, empty, part1 },
+            { "empty empty", empty, empty, empty }
+        };
+    }
+
+    @DataProvider(name = "cases")
+    private static Object[][] getCases() {
+        return cases;
+    }
+
+    @Factory(dataProvider = "cases")
+    public static Object[] createTests(String scenario, Collection<Integer> c1, Collection<Integer> c2, Collection<Integer> expected) {
+        return new Object[] {
+            new ConcatTest(scenario, c1, c2, expected)
+        };
+    }
+
+    protected final String scenario;
+    protected final Collection<Integer> c1;
+    protected final Collection<Integer> c2;
+    protected final Collection<Integer> expected;
+
+    public ConcatTest(String scenario, Collection<Integer> c1, Collection<Integer> c2, Collection<Integer> expected) {
+        this.scenario = scenario;
+        this.c1 = c1;
+        this.c2 = c2;
+        this.expected = expected;
+
+        // verify prerequisite
+        Stream<Integer> s1s = c1.stream();
+        Stream<Integer> s2s = c2.stream();
+        Stream<Integer> s1p = c1.parallelStream();
+        Stream<Integer> s2p = c2.parallelStream();
+        assertTrue(s1p.isParallel());
+        assertTrue(s2p.isParallel());
+        assertFalse(s1s.isParallel());
+        assertFalse(s2s.isParallel());
+
+        assertTrue(s1s.spliterator().hasCharacteristics(Spliterator.ORDERED));
+        assertTrue(s1p.spliterator().hasCharacteristics(Spliterator.ORDERED));
+        assertTrue(s2s.spliterator().hasCharacteristics(Spliterator.ORDERED));
+        assertTrue(s2p.spliterator().hasCharacteristics(Spliterator.ORDERED));
+    }
+
+    private <T> void assertConcatContent(Spliterator<T> sp, boolean ordered, Spliterator<T> expected) {
+        // concat stream cannot guarantee uniqueness
+        assertFalse(sp.hasCharacteristics(Spliterator.DISTINCT), scenario);
+        // concat stream cannot guarantee sorted
+        assertFalse(sp.hasCharacteristics(Spliterator.SORTED), scenario);
+        // concat stream is ordered if both are ordered
+        assertEquals(sp.hasCharacteristics(Spliterator.ORDERED), ordered, scenario);
+
+        // Verify elements
+        if (ordered) {
+            assertEquals(toBoxedList(sp),
+                         toBoxedList(expected),
+                         scenario);
+        } else {
+            assertEquals(toBoxedMultiset(sp),
+                         toBoxedMultiset(expected),
+                         scenario);
+        }
+    }
+
+    private void assertRefConcat(Stream<Integer> s1, Stream<Integer> s2, boolean parallel, boolean ordered) {
+        Stream<Integer> result = Stream.concat(s1, s2);
+        assertEquals(result.isParallel(), parallel);
+        assertConcatContent(result.spliterator(), ordered, expected.spliterator());
+    }
+
+    private void assertIntConcat(Stream<Integer> s1, Stream<Integer> s2, boolean parallel, boolean ordered) {
+        IntStream result = IntStream.concat(s1.mapToInt(Integer::intValue),
+                                            s2.mapToInt(Integer::intValue));
+        assertEquals(result.isParallel(), parallel);
+        assertConcatContent(result.spliterator(), ordered,
+                            expected.stream().mapToInt(Integer::intValue).spliterator());
+    }
+
+    private void assertLongConcat(Stream<Integer> s1, Stream<Integer> s2, boolean parallel, boolean ordered) {
+        LongStream result = LongStream.concat(s1.mapToLong(Integer::longValue),
+                                              s2.mapToLong(Integer::longValue));
+        assertEquals(result.isParallel(), parallel);
+        assertConcatContent(result.spliterator(), ordered,
+                            expected.stream().mapToLong(Integer::longValue).spliterator());
+    }
+
+    private void assertDoubleConcat(Stream<Integer> s1, Stream<Integer> s2, boolean parallel, boolean ordered) {
+        DoubleStream result = DoubleStream.concat(s1.mapToDouble(Integer::doubleValue),
+                                                  s2.mapToDouble(Integer::doubleValue));
+        assertEquals(result.isParallel(), parallel);
+        assertConcatContent(result.spliterator(), ordered,
+                            expected.stream().mapToDouble(Integer::doubleValue).spliterator());
+    }
+
+    public void testRefConcat() {
+        // sequential + sequential -> sequential
+        assertRefConcat(c1.stream(), c2.stream(), false, true);
+        // parallel + parallel -> parallel
+        assertRefConcat(c1.parallelStream(), c2.parallelStream(), true, true);
+        // sequential + parallel -> parallel
+        assertRefConcat(c1.stream(), c2.parallelStream(), true, true);
+        // parallel + sequential -> parallel
+        assertRefConcat(c1.parallelStream(), c2.stream(), true, true);
+
+        // not ordered
+        assertRefConcat(c1.stream().unordered(), c2.stream(), false, false);
+        assertRefConcat(c1.stream(), c2.stream().unordered(), false, false);
+        assertRefConcat(c1.parallelStream().unordered(), c2.stream().unordered(), true, false);
+    }
+
+    public void testIntConcat() {
+        // sequential + sequential -> sequential
+        assertIntConcat(c1.stream(), c2.stream(), false, true);
+        // parallel + parallel -> parallel
+        assertIntConcat(c1.parallelStream(), c2.parallelStream(), true, true);
+        // sequential + parallel -> parallel
+        assertIntConcat(c1.stream(), c2.parallelStream(), true, true);
+        // parallel + sequential -> parallel
+        assertIntConcat(c1.parallelStream(), c2.stream(), true, true);
+
+        // not ordered
+        assertIntConcat(c1.stream().unordered(), c2.stream(), false, false);
+        assertIntConcat(c1.stream(), c2.stream().unordered(), false, false);
+        assertIntConcat(c1.parallelStream().unordered(), c2.stream().unordered(), true, false);
+    }
+
+    public void testLongConcat() {
+        // sequential + sequential -> sequential
+        assertLongConcat(c1.stream(), c2.stream(), false, true);
+        // parallel + parallel -> parallel
+        assertLongConcat(c1.parallelStream(), c2.parallelStream(), true, true);
+        // sequential + parallel -> parallel
+        assertLongConcat(c1.stream(), c2.parallelStream(), true, true);
+        // parallel + sequential -> parallel
+        assertLongConcat(c1.parallelStream(), c2.stream(), true, true);
+
+        // not ordered
+        assertLongConcat(c1.stream().unordered(), c2.stream(), false, false);
+        assertLongConcat(c1.stream(), c2.stream().unordered(), false, false);
+        assertLongConcat(c1.parallelStream().unordered(), c2.stream().unordered(), true, false);
+    }
+
+    public void testDoubleConcat() {
+        // sequential + sequential -> sequential
+        assertDoubleConcat(c1.stream(), c2.stream(), false, true);
+        // parallel + parallel -> parallel
+        assertDoubleConcat(c1.parallelStream(), c2.parallelStream(), true, true);
+        // sequential + parallel -> parallel
+        assertDoubleConcat(c1.stream(), c2.parallelStream(), true, true);
+        // parallel + sequential -> parallel
+        assertDoubleConcat(c1.parallelStream(), c2.stream(), true, true);
+
+        // not ordered
+        assertDoubleConcat(c1.stream().unordered(), c2.stream(), false, false);
+        assertDoubleConcat(c1.stream(), c2.stream().unordered(), false, false);
+        assertDoubleConcat(c1.parallelStream().unordered(), c2.stream().unordered(), true, false);
+    }
+}
--- a/jdk/test/java/util/stream/test/org/openjdk/tests/java/util/stream/RangeTest.java	Fri Jul 12 11:12:16 2013 -0700
+++ b/jdk/test/java/util/stream/test/org/openjdk/tests/java/util/stream/RangeTest.java	Fri Jul 12 12:15:22 2013 -0700
@@ -226,116 +226,114 @@
         assertEquals(first, LongStream.iterate(0, i -> i + 1).parallel().filter(i -> i > 10000).findFirst().getAsLong());
     }
 
-    // Enable when Stream.concat is present and range implementations are
-    // updated to use that
-//    private static void assertSizedAndSubSized(Spliterator<?> s) {
-//        assertTrue(s.hasCharacteristics(Spliterator.SIZED | Spliterator.SUBSIZED));
-//    }
-//
-//    private static void assertNotSizedAndSubSized(Spliterator<?> s) {
-//        assertFalse(s.hasCharacteristics(Spliterator.SIZED | Spliterator.SUBSIZED));
-//    }
-//
-//    public void testLongLongRange() {
-//        // Test [Long.MIN_VALUE, Long.MAX_VALUE)
-//        // This will concatenate streams of three ranges
-//        //   [Long.MIN_VALUE, x) [x, 0) [0, Long.MAX_VALUE)
-//        // where x = Long.divideUnsigned(0 - Long.MIN_VALUE, 2) + 1
-//        {
-//            Spliterator.OfLong s = LongStream.range(Long.MIN_VALUE, Long.MAX_VALUE).spliterator();
-//
-//            assertEquals(s.estimateSize(), Long.MAX_VALUE);
-//            assertNotSizedAndSubSized(s);
-//
-//            Spliterator.OfLong s1 = s.trySplit();
-//            assertNotSizedAndSubSized(s1);
-//            assertSizedAndSubSized(s);
-//
-//            Spliterator.OfLong s2 = s1.trySplit();
-//            assertSizedAndSubSized(s1);
-//            assertSizedAndSubSized(s2);
-//
-//            assertTrue(s.estimateSize() == Long.MAX_VALUE);
-//            assertTrue(s1.estimateSize() < Long.MAX_VALUE);
-//            assertTrue(s2.estimateSize() < Long.MAX_VALUE);
-//
-//            assertEquals(s.estimateSize() + s1.estimateSize() + s2.estimateSize(),
-//                         Long.MAX_VALUE - Long.MIN_VALUE);
-//        }
-//
-//        long[][] ranges = { {Long.MIN_VALUE, 0}, {-1, Long.MAX_VALUE} };
-//        for (int i = 0; i < ranges.length; i++) {
-//            long start = ranges[i][0];
-//            long end = ranges[i][1];
-//
-//            Spliterator.OfLong s = LongStream.range(start, end).spliterator();
-//
-//            assertEquals(s.estimateSize(), Long.MAX_VALUE);
-//            assertNotSizedAndSubSized(s);
-//
-//            Spliterator.OfLong s1 = s.trySplit();
-//            assertSizedAndSubSized(s1);
-//            assertSizedAndSubSized(s);
-//
-//            assertTrue(s.estimateSize() < Long.MAX_VALUE);
-//            assertTrue(s1.estimateSize() < Long.MAX_VALUE);
-//
-//            assertEquals(s.estimateSize() + s1.estimateSize(), end - start);
-//        }
-//    }
-//
-//    public void testLongLongRangeClosed() {
-//        // Test [Long.MIN_VALUE, Long.MAX_VALUE]
-//        // This will concatenate streams of four ranges
-//        //   [Long.MIN_VALUE, x) [x, 0) [0, y) [y, Long.MAX_VALUE]
-//        // where x = Long.divideUnsigned(0 - Long.MIN_VALUE, 2) + 1
-//        //       y = Long.divideUnsigned(Long.MAX_VALUE, 2) + 1
-//
-//        {
-//            Spliterator.OfLong s = LongStream.rangeClosed(Long.MIN_VALUE, Long.MAX_VALUE).spliterator();
-//
-//            assertEquals(s.estimateSize(), Long.MAX_VALUE);
-//            assertNotSizedAndSubSized(s);
-//
-//            Spliterator.OfLong s1 = s.trySplit();
-//            assertNotSizedAndSubSized(s1);
-//            assertNotSizedAndSubSized(s);
-//
-//            Spliterator.OfLong s2 = s1.trySplit();
-//            assertSizedAndSubSized(s1);
-//            assertSizedAndSubSized(s2);
-//
-//            Spliterator.OfLong s3 = s.trySplit();
-//            assertSizedAndSubSized(s3);
-//            assertSizedAndSubSized(s);
-//
-//            assertTrue(s.estimateSize() < Long.MAX_VALUE);
-//            assertTrue(s3.estimateSize() < Long.MAX_VALUE);
-//            assertTrue(s1.estimateSize() < Long.MAX_VALUE);
-//            assertTrue(s2.estimateSize() < Long.MAX_VALUE);
-//
-//            assertEquals(s.estimateSize() + s3.estimateSize() + s1.estimateSize() + s2.estimateSize(),
-//                         Long.MAX_VALUE - Long.MIN_VALUE + 1);
-//        }
-//
-//        long[][] ranges = { {Long.MIN_VALUE, 0}, {-1, Long.MAX_VALUE} };
-//        for (int i = 0; i < ranges.length; i++) {
-//            long start = ranges[i][0];
-//            long end = ranges[i][1];
-//
-//            Spliterator.OfLong s = LongStream.rangeClosed(start, end).spliterator();
-//
-//            assertEquals(s.estimateSize(), Long.MAX_VALUE);
-//            assertNotSizedAndSubSized(s);
-//
-//            Spliterator.OfLong s1 = s.trySplit();
-//            assertSizedAndSubSized(s1);
-//            assertSizedAndSubSized(s);
-//
-//            assertTrue(s.estimateSize() < Long.MAX_VALUE);
-//            assertTrue(s1.estimateSize() < Long.MAX_VALUE);
-//
-//            assertEquals(s.estimateSize() + s1.estimateSize(), end - start + 1);
-//        }
-//    }
+    private static void assertSizedAndSubSized(Spliterator<?> s) {
+        assertTrue(s.hasCharacteristics(Spliterator.SIZED | Spliterator.SUBSIZED));
+    }
+
+    private static void assertNotSizedAndSubSized(Spliterator<?> s) {
+        assertFalse(s.hasCharacteristics(Spliterator.SIZED | Spliterator.SUBSIZED));
+    }
+
+    public void testLongLongRange() {
+        // Test [Long.MIN_VALUE, Long.MAX_VALUE)
+        // This will concatenate streams of three ranges
+        //   [Long.MIN_VALUE, x) [x, 0) [0, Long.MAX_VALUE)
+        // where x = Long.divideUnsigned(0 - Long.MIN_VALUE, 2) + 1
+        {
+            Spliterator.OfLong s = LongStream.range(Long.MIN_VALUE, Long.MAX_VALUE).spliterator();
+
+            assertEquals(s.estimateSize(), Long.MAX_VALUE);
+            assertNotSizedAndSubSized(s);
+
+            Spliterator.OfLong s1 = s.trySplit();
+            assertNotSizedAndSubSized(s1);
+            assertSizedAndSubSized(s);
+
+            Spliterator.OfLong s2 = s1.trySplit();
+            assertSizedAndSubSized(s1);
+            assertSizedAndSubSized(s2);
+
+            assertTrue(s.estimateSize() == Long.MAX_VALUE);
+            assertTrue(s1.estimateSize() < Long.MAX_VALUE);
+            assertTrue(s2.estimateSize() < Long.MAX_VALUE);
+
+            assertEquals(s.estimateSize() + s1.estimateSize() + s2.estimateSize(),
+                         Long.MAX_VALUE - Long.MIN_VALUE);
+        }
+
+        long[][] ranges = { {Long.MIN_VALUE, 0}, {-1, Long.MAX_VALUE} };
+        for (int i = 0; i < ranges.length; i++) {
+            long start = ranges[i][0];
+            long end = ranges[i][1];
+
+            Spliterator.OfLong s = LongStream.range(start, end).spliterator();
+
+            assertEquals(s.estimateSize(), Long.MAX_VALUE);
+            assertNotSizedAndSubSized(s);
+
+            Spliterator.OfLong s1 = s.trySplit();
+            assertSizedAndSubSized(s1);
+            assertSizedAndSubSized(s);
+
+            assertTrue(s.estimateSize() < Long.MAX_VALUE);
+            assertTrue(s1.estimateSize() < Long.MAX_VALUE);
+
+            assertEquals(s.estimateSize() + s1.estimateSize(), end - start);
+        }
+    }
+
+    public void testLongLongRangeClosed() {
+        // Test [Long.MIN_VALUE, Long.MAX_VALUE]
+        // This will concatenate streams of four ranges
+        //   [Long.MIN_VALUE, x) [x, 0) [0, y) [y, Long.MAX_VALUE]
+        // where x = Long.divideUnsigned(0 - Long.MIN_VALUE, 2) + 1
+        //       y = Long.divideUnsigned(Long.MAX_VALUE, 2) + 1
+
+        {
+            Spliterator.OfLong s = LongStream.rangeClosed(Long.MIN_VALUE, Long.MAX_VALUE).spliterator();
+
+            assertEquals(s.estimateSize(), Long.MAX_VALUE);
+            assertNotSizedAndSubSized(s);
+
+            Spliterator.OfLong s1 = s.trySplit();
+            assertNotSizedAndSubSized(s1);
+            assertNotSizedAndSubSized(s);
+
+            Spliterator.OfLong s2 = s1.trySplit();
+            assertSizedAndSubSized(s1);
+            assertSizedAndSubSized(s2);
+
+            Spliterator.OfLong s3 = s.trySplit();
+            assertSizedAndSubSized(s3);
+            assertSizedAndSubSized(s);
+
+            assertTrue(s.estimateSize() < Long.MAX_VALUE);
+            assertTrue(s3.estimateSize() < Long.MAX_VALUE);
+            assertTrue(s1.estimateSize() < Long.MAX_VALUE);
+            assertTrue(s2.estimateSize() < Long.MAX_VALUE);
+
+            assertEquals(s.estimateSize() + s3.estimateSize() + s1.estimateSize() + s2.estimateSize(),
+                         Long.MAX_VALUE - Long.MIN_VALUE + 1);
+        }
+
+        long[][] ranges = { {Long.MIN_VALUE, 0}, {-1, Long.MAX_VALUE} };
+        for (int i = 0; i < ranges.length; i++) {
+            long start = ranges[i][0];
+            long end = ranges[i][1];
+
+            Spliterator.OfLong s = LongStream.rangeClosed(start, end).spliterator();
+
+            assertEquals(s.estimateSize(), Long.MAX_VALUE);
+            assertNotSizedAndSubSized(s);
+
+            Spliterator.OfLong s1 = s.trySplit();
+            assertSizedAndSubSized(s1);
+            assertSizedAndSubSized(s);
+
+            assertTrue(s.estimateSize() < Long.MAX_VALUE);
+            assertTrue(s1.estimateSize() < Long.MAX_VALUE);
+
+            assertEquals(s.estimateSize() + s1.estimateSize(), end - start + 1);
+        }
+    }
 }