changeset 6642:804584b3ef6a

Expose Stream.spliterator(); move concat() out of Stream and into a static helper in Streams. Additional spliterator tests. Contributed-by: paul.sandoz@oracle.com Contributed-by: brian.goetz@oracle.com
author briangoetz
date Wed, 28 Nov 2012 17:25:31 -0500
parents 6f329acab6ff
children b4107f9b4330
files src/share/classes/java/util/Iterators.java src/share/classes/java/util/stream/AbstractPipeline.java src/share/classes/java/util/stream/BaseStream.java src/share/classes/java/util/stream/PipelineHelper.java src/share/classes/java/util/stream/ReferencePipeline.java src/share/classes/java/util/stream/Stream.java src/share/classes/java/util/stream/StreamShape.java src/share/classes/java/util/stream/StreamShapeFactory.java src/share/classes/java/util/stream/Streams.java src/share/classes/java/util/stream/op/ConcatOp.java src/share/classes/java/util/stream/primitive/IntPipeline.java src/share/classes/java/util/stream/primitive/IntSortedOp.java src/share/classes/java/util/stream/primitive/IntStream.java src/share/classes/java/util/stream/primitive/Primitives.java test-ng/tests/org/openjdk/tests/java/util/LambdaTestHelpers.java test-ng/tests/org/openjdk/tests/java/util/stream/OpTestCase.java test-ng/tests/org/openjdk/tests/java/util/stream/SpliteratorTest.java test-ng/tests/org/openjdk/tests/java/util/stream/StreamSpliteratorTest.java test-ng/tests/org/openjdk/tests/java/util/stream/StreamTestDataProvider.java test-ng/tests/org/openjdk/tests/java/util/stream/StreamTestScenario.java test-ng/tests/org/openjdk/tests/java/util/stream/op/ConcatOpTest.java test-ng/tests/org/openjdk/tests/java/util/stream/primitive/IntStreamSpliteratorTest.java test-ng/tests/org/openjdk/tests/java/util/stream/primitive/IntStreamTestDataProvider.java test-ng/tests/org/openjdk/tests/java/util/stream/primitive/IntStreamTestScenario.java
diffstat 24 files changed, 978 insertions(+), 209 deletions(-) [+]
line wrap: on
line diff
--- a/src/share/classes/java/util/Iterators.java	Wed Nov 28 14:08:04 2012 -0500
+++ b/src/share/classes/java/util/Iterators.java	Wed Nov 28 17:25:31 2012 -0500
@@ -161,9 +161,10 @@
         return spliterator(iterator, -1);
     }
 
-    public static<T> Spliterator<T> spliterator(Iterator<T> iterator, int size) {
+    public static<T> Spliterator<T> spliterator(Iterator<T> iterator, int sizeIfKnown) {
         Objects.requireNonNull(iterator);
         return new Spliterator<T>() {
+            int size = sizeIfKnown;
             boolean traversing;
             int nextSize = 1;
 
@@ -183,6 +184,9 @@
                     int i=0;
                     while (i<array.length && iterator.hasNext())
                         array[i++] = iterator.next();
+                    if (size > 0) {
+                        size -= i;
+                    }
                     nextSize = Math.min(nextSize*2, MAX_ITERATOR_CHUNK_SIZE);
                     return Arrays.spliterator(array, 0, i);
                 }
@@ -196,7 +200,7 @@
 
             @Override
             public boolean isPredictableSplits() {
-                return false;
+                return size >= 0;
             }
 
             @Override
--- a/src/share/classes/java/util/stream/AbstractPipeline.java	Wed Nov 28 14:08:04 2012 -0500
+++ b/src/share/classes/java/util/stream/AbstractPipeline.java	Wed Nov 28 17:25:31 2012 -0500
@@ -146,27 +146,27 @@
         failOnLinked();
 
         if (StreamOpFlag.PARALLEL.isKnown(sourceState.getSourceFlags())) {
-            return evaluateParallel(terminal);
+            return evaluateParallel(terminal, depth);
         }
         else
             return evaluateSequential(terminal, sourceState.getSourceFlags());
     }
 
-    private<R> R evaluateParallel(TerminalOp<E_OUT, R> terminal) {
-        // Combined flags length is one greater than depth to hold initial value
+    private<R> R evaluateParallel(TerminalOp<E_OUT, R> terminal, int subDepth) {
+        // Combined flags length is one greater than subDepth to hold initial value
         // Given an op whose index is i then the flags input to that op are at flags[i]
         // and the flags output from that op are at flags[i + 1]
-        final int[] opsFlags = new int[depth + 1];
+        final int[] opsFlags = new int[subDepth + 1];
         int fromOp = 0;
         int upToOp = 0;
         Node<?> iNode = null;
         Spliterator<?> iSource = sourceState.spliterator();
         int iSourceFlags = sourceState.getSourceFlags();
         while (true) {
-            while (upToOp < depth && !ops[upToOp].isStateful())
+            while (upToOp < subDepth && !ops[upToOp].isStateful())
                 upToOp++;
 
-            if (upToOp < depth) {
+            if (upToOp < subDepth) {
                 IntermediateOp<?, ?> statefulOp = ops[upToOp];
                 iNode = evaluateParallel(iNode, iSource, iSourceFlags, opsFlags, fromOp, upToOp, statefulOp);
 
@@ -179,7 +179,7 @@
                 fromOp = ++upToOp;
 
                 if (!StreamOpFlag.PARALLEL.isKnown(iSourceFlags)) {
-                    return evaluateSequential(iNode, iSource, iSourceFlags, opsFlags, fromOp, depth, terminal);
+                    return evaluateSequential(iNode, iSource, iSourceFlags, opsFlags, fromOp, subDepth, terminal);
                 }
             }
             else {
@@ -252,14 +252,6 @@
             }
         }
 
-        protected boolean isOutputSizeKnown() {
-            return StreamOpFlag.SIZED.isKnown(getOpFlags());
-        }
-
-        protected boolean isShortCircuit() {
-            return StreamOpFlag.SHORT_CIRCUIT.isKnown(getOpFlags());
-        }
-
         protected boolean hasZeroDepth() {
             return upTo == from;
         }
@@ -464,24 +456,30 @@
      * @return a node that holds the collected output elements.
      */
     public Node<E_OUT> collectOutput() {
-        // @@@ Generalize CollectorOps for shape when primitive support is in place
-        TerminalOp<E_OUT, Node<E_OUT>> collect = new TerminalOp<E_OUT, Node<E_OUT>>() {
-            @Override
-            public StreamShape inputShape() {
-                return getOutputShape();
-            }
+        return evaluate(new NodeCollectorOp<E_OUT>(getOutputShape()));
+    }
 
-            @Override
-            public <P_IN> Node<E_OUT> evaluateParallel(ParallelPipelineHelper<P_IN, E_OUT> helper) {
-                return helper.collectOutput();
-            }
+    private static class NodeCollectorOp<T> implements TerminalOp<T, Node<T>> {
+        final StreamShape shape;
 
-            @Override
-            public <P_IN> Node<E_OUT> evaluateSequential(PipelineHelper<P_IN, E_OUT> helper) {
-                return helper.collectOutput();
-            }
-        };
-        return evaluate(collect);
+        private NodeCollectorOp(StreamShape shape) {
+            this.shape = shape;
+        }
+
+        @Override
+        public StreamShape inputShape() {
+            return shape;
+        }
+
+        @Override
+        public <P_IN> Node<T> evaluateParallel(ParallelPipelineHelper<P_IN, T> helper) {
+            return helper.collectOutput();
+        }
+
+        @Override
+        public <P_IN> Node<T> evaluateSequential(PipelineHelper<P_IN, T> helper) {
+            return helper.collectOutput();
+        }
     }
 
     /**
@@ -506,24 +504,96 @@
 
     // BaseStream
 
-    @Override
-    @SuppressWarnings("unchecked")
-    public Iterator<E_OUT> iterator() {
-        // Iteration can only be performed on the tail stream
-        failOnLinked();
-
-        Iterator iterator = sourceState.iterator();
-
-        // Ensure the parallel flag is cleared, if set
-        int sourceFlags = StreamOpFlag.PARALLEL.unsetStreamOpFlag(sourceState.getSourceFlags());
-        // Stream flags are combined with the initial ops flags at the start
+    private Iterator<E_OUT> wrapIterator(Iterator iterator, int sourceFlags) {
         int opFlags = StreamOpFlag.combineOpFlags(sourceFlags, StreamOpFlag.INITIAL_OPS_VALUE);
         for (int i = 0; i < depth; i++) {
             iterator = ops[i].wrapIterator(opFlags, iterator);
             opFlags = StreamOpFlag.combineOpFlags(ops[i].getOpFlags(), opFlags);
         }
+        return iterator;
+    }
 
-        return iterator;
+    @Override
+    @SuppressWarnings("unchecked")
+    public Iterator<E_OUT> iterator() {
+        sourceState.failOnConsumed();
+        // Iteration can only be performed on the tail stream
+        failOnLinked();
+
+        return wrapIterator(sourceState.iterator(),
+                            StreamOpFlag.PARALLEL.unsetStreamOpFlag(sourceState.getSourceFlags()));
+    }
+
+    @Override
+    public Spliterator<E_OUT> spliterator() {
+        sourceState.failOnConsumed();
+        // Iteration can only be performed on the tail stream
+        failOnLinked();
+
+        if (depth == 0) {
+            return isParallel()
+                   ? (Spliterator<E_OUT>) sourceState.spliterator()
+                   : (Spliterator<E_OUT>) getOutputShape().sequentialSpliterator(sourceState.spliterator());
+        }
+        else if (!StreamOpFlag.PARALLEL.isKnown(sourceState.getSourceFlags())) {
+            Spliterator<?> s = sourceState.spliterator();
+            Iterator<E_OUT> iterator = wrapIterator(s.iterator(),
+                                                    sourceState.getSourceFlags());
+            return getOutputShape().iteratorSpliterator(iterator,
+                                                        StreamOpFlag.SIZED.isKnown(combinedOpFlags) ? s.getSizeIfKnown() : -1);
+        }
+        else {
+            // Find the depth of the last stateful op to be evaluated in parallel
+            int _subDepth = 0;
+            for (int i = 0; i < depth; i++) {
+                IntermediateOp op = ops[i];
+                if (op.isStateful()) {
+                    _subDepth = i + 1;
+                    if (StreamOpFlag.PARALLEL.isCleared(op.getOpFlags())) {
+                        break;
+                    }
+                }
+            }
+            final int subDepth = _subDepth;
+
+            if (subDepth > 0) {
+                StreamShape statefulOpShape = ops[subDepth - 1].outputShape();
+                // Create a proxy spliterator that evaluates in parallel up to and including the last stateful op
+                // and uses the spliterator obtained from the resulting node
+                TerminalOp<E_OUT, Node<E_OUT>> collect = new NodeCollectorOp<>(statefulOpShape);
+                Spliterator<E_OUT> s = statefulOpShape.proxySpliterator(() -> evaluateParallel(collect, subDepth).spliterator());
+
+                // If there is one or more ops after the last stateful op
+                if (subDepth < depth) {
+                    // Calculate the stream flags up to and including the last stateful op
+                    int flags = StreamOpFlag.combineOpFlags(sourceState.getSourceFlags(), StreamOpFlag.INITIAL_OPS_VALUE);
+                    for (int i = 0; i < subDepth; i++) {
+                        flags = StreamOpFlag.combineOpFlags(ops[i].getOpFlags(), flags);
+                    }
+                    flags = StreamOpFlag.toStreamFlags(flags) | StreamOpFlag.IS_SIZED | StreamOpFlag.IS_ORDERED;
+
+                    // Wrap the proxy spliterator in a wrapping spliterator
+                    s = wrappingSpliterator(s, flags, subDepth, depth);
+
+                    // If the parallel flag is cleared then wrap in a sequential spliterator to ensure
+                    // no splits will occur
+                    if (!StreamOpFlag.PARALLEL.isKnown(flags)) {
+                        s = getOutputShape().sequentialSpliterator(s);
+                    }
+                }
+                return s;
+            } else {
+                // If there are no stateful ops then wrap the source spliterator in a wrapping spliterator
+                return wrappingSpliterator(sourceState.spliterator(), sourceState.getSourceFlags(), 0, depth);
+            }
+        }
+    }
+
+    private Spliterator<E_OUT> wrappingSpliterator(Spliterator<?> source, int sourceFlags, int fromOp, int upTpOp) {
+        final ParallelPipelineHelper<?, E_OUT> pph = new ParallelImplPipelineHelper<>(
+                null, source, sourceFlags,
+                new int[upTpOp + 1], fromOp, upTpOp, getOutputShape());
+        return getOutputShape().wrappingSpliterator(source, pph);
     }
 
     @Override
@@ -533,7 +603,7 @@
 
     @Override
     public boolean isParallel() {
-        return StreamOpFlag.PARALLEL.isKnown(sourceState.getSourceFlags());
+        return StreamOpFlag.PARALLEL.isKnown(getStreamFlags());
     }
 
     private interface SourceState<T> {
--- a/src/share/classes/java/util/stream/BaseStream.java	Wed Nov 28 14:08:04 2012 -0500
+++ b/src/share/classes/java/util/stream/BaseStream.java	Wed Nov 28 17:25:31 2012 -0500
@@ -35,7 +35,7 @@
  */
 public interface BaseStream<T> {
     /**
-     * Return the iterator for the elements of this stream. The same iterator
+     * Return an iterator for the elements of this stream. The same iterator
      * instance is returned for every invocation.  Once the elements of the
      * stream are consumed it is not possible to "rewind" the stream.
      *
@@ -44,6 +44,15 @@
     Iterator<T> iterator();
 
     /**
+     * Return a spliterator for the elements of this stream. The same spliterator
+     * instance is returned for every invocation.  Once the elements of the
+     * stream are consumed it is not possible to "rewind" the stream.
+     *
+     * @return the element iterator for this stream.
+     */
+    Spliterator<T> spliterator();
+
+    /**
      * Returns {@code true} if this stream may be split for parallel
      * processing.
      *
--- a/src/share/classes/java/util/stream/PipelineHelper.java	Wed Nov 28 14:08:04 2012 -0500
+++ b/src/share/classes/java/util/stream/PipelineHelper.java	Wed Nov 28 17:25:31 2012 -0500
@@ -40,6 +40,20 @@
     int getOpFlags();
 
     /**
+     * @return true if the output size is known, otherwise false.
+     */
+    default boolean isOutputSizeKnown() {
+        return StreamOpFlag.SIZED.isKnown(getOpFlags());
+    }
+
+    /**
+     * @return true if the pipeline is short-circuited, otherwise false.
+     */
+    default boolean isShortCircuit() {
+        return StreamOpFlag.SHORT_CIRCUIT.isKnown(getOpFlags());
+    }
+
+    /**
      * Get the size of the stream output from the pipeline if known.
      *
      * @return the size of the output stream, otherwise {@code -1} if the size is unknown.
--- a/src/share/classes/java/util/stream/ReferencePipeline.java	Wed Nov 28 14:08:04 2012 -0500
+++ b/src/share/classes/java/util/stream/ReferencePipeline.java	Wed Nov 28 17:25:31 2012 -0500
@@ -119,11 +119,6 @@
     }
 
     @Override
-    public Stream<U> concat(Stream<? extends U> other) {
-        return pipeline(new ConcatOp<>(other));
-    }
-
-    @Override
     public <A extends Destination<? super U>> A into(A target) {
         target.addAll(this);
         return target;
--- a/src/share/classes/java/util/stream/Stream.java	Wed Nov 28 14:08:04 2012 -0500
+++ b/src/share/classes/java/util/stream/Stream.java	Wed Nov 28 17:25:31 2012 -0500
@@ -98,14 +98,6 @@
      */
     Stream<T> slice(int skip, int limit);
 
-    /**
-     * Concatenate to the end of this stream.
-     *
-     * @param other the stream to concatenate.
-     * @return the concatenated stream.
-     */
-    Stream<T> concat(Stream<? extends T> other);
-
     <A extends Destination<? super T>> A into(A target);
 
     Object[] toArray();
--- a/src/share/classes/java/util/stream/StreamShape.java	Wed Nov 28 14:08:04 2012 -0500
+++ b/src/share/classes/java/util/stream/StreamShape.java	Wed Nov 28 17:25:31 2012 -0500
@@ -24,6 +24,8 @@
  */
 package java.util.stream;
 
+import java.util.Iterator;
+import java.util.function.Supplier;
 import java.util.stream.op.IntermediateOp;
 import java.util.stream.op.Node;
 import java.util.stream.op.NodeBuilder;
@@ -103,4 +105,44 @@
      * @return the node holding elements output from the pipeline.
      */
     <P_IN, T> Node<T> collect(ParallelPipelineHelper<P_IN, T> helper, boolean flattenTree);
+
+    /**
+     * Create a spliterator from an iterator compatible with this stream shape.
+     *
+     * @param i the iterator from which to create a spliterator.
+     * @param sizeIfKnown size of the iterator if known, otherwise -1.
+     * @param <T> the type of element.
+     * @return the spliterator, compatible with this shape, wrapping the iterator.
+     */
+    <T> Spliterator<T> iteratorSpliterator(Iterator<T> i, int sizeIfKnown);
+
+    /**
+     * Create a sequential spliterator from a spliterator compatible with this stream shape.
+     *
+     * @param s the spliterator to wrap as a sequential spliterator.
+     * @param <T> the type of element.
+     * @return the sequential spliterator compatible with this shape.
+     */
+    <T> Spliterator<T> sequentialSpliterator(Spliterator<T> s);
+
+    /**
+     * Create a proxy spliterator that proxies a supplier of spliterator compatible with this stream shape.
+     *
+     * @param s the supplier of a compatible spliterator.
+     * @param <T> the type of element.
+     * @return the proxy spliterator compatible with this shape.
+     */
+    <T> Spliterator<T> proxySpliterator(Supplier<Spliterator<T>> s);
+
+    /**
+     * Create a spliterator that wraps a source spliterator, compatible with this stream shape,
+     * and operations associated with a {@link ParallelPipelineHelper}.
+     *
+     * @param source the source spliterator compatible with this shape.
+     * @param pph the parallel pipeline helper.
+     * @param <P_IN> the input element.
+     * @param <P_OUT> the output element.
+     * @return the wrapping spliterator compatible with this shape.
+     */
+    <P_IN, P_OUT> Spliterator<P_OUT> wrappingSpliterator(Spliterator<P_IN> source, ParallelPipelineHelper<P_IN, P_OUT> pph);
 }
--- a/src/share/classes/java/util/stream/StreamShapeFactory.java	Wed Nov 28 14:08:04 2012 -0500
+++ b/src/share/classes/java/util/stream/StreamShapeFactory.java	Wed Nov 28 17:25:31 2012 -0500
@@ -24,11 +24,12 @@
  */
 package java.util.stream;
 
-import java.util.stream.primitive.IntTreeUtils;
-import java.util.stream.primitive.IntNodes;
-import java.util.stream.primitive.IntStream;
-import java.util.stream.primitive.IntPipeline;
-import java.util.stream.primitive.IntSpliterator;
+import java.util.Iterator;
+import java.util.Iterators;
+import java.util.function.Block;
+import java.util.function.IntBlock;
+import java.util.function.Supplier;
+import java.util.stream.primitive.*;
 import java.util.WeakHashMap;
 import java.util.stream.op.*;
 
@@ -76,6 +77,73 @@
             public <P_IN, T> Node<T> collect(ParallelPipelineHelper<P_IN, T> helper, boolean flattenTree) {
                 return TreeUtils.collect(helper, flattenTree);
             }
+
+            @Override
+            public <T> Spliterator<T> iteratorSpliterator(Iterator<T> i, int sizeIfKnown) {
+                return Iterators.spliterator(i, sizeIfKnown);
+            }
+
+            @Override
+            public <T> Spliterator<T> sequentialSpliterator(Spliterator<T> s) {
+                return Streams.sequentialSpliterator(s);
+            }
+
+            @Override
+            public <T> Spliterator<T> proxySpliterator(Supplier<Spliterator<T>> s) {
+                return Streams.proxySpliterator(s);
+            }
+
+            @Override
+            public <P_IN, P_OUT> Spliterator<P_OUT> wrappingSpliterator(Spliterator<P_IN> source, ParallelPipelineHelper<P_IN, P_OUT> pph) {
+                // @@@ Move to Streams?
+                class WrappingSpliterator implements Spliterator<P_OUT> {
+                    final Spliterator<P_IN> spliterator;
+                    WrappingSpliterator(Spliterator<P_IN> spliterator) {
+                        this.spliterator = spliterator;
+                    }
+
+                    @Override
+                    public int getNaturalSplits() {
+                        return spliterator.getNaturalSplits();
+                    }
+
+                    @Override
+                    public Spliterator<P_OUT> split() {
+                        return new WrappingSpliterator(spliterator.split());
+                    }
+
+                    @Override
+                    public Iterator<P_OUT> iterator() {
+                        return pph.wrapIterator(spliterator.iterator());
+                    }
+
+                    @Override
+                    public void forEach(Block<? super P_OUT> block) {
+                        if (!pph.isShortCircuit()) {
+                            Sink.OfReference<P_OUT> s = block::accept;
+                            Sink wrapped = pph.wrapSink(s);
+
+                            wrapped.begin(getSizeIfKnown());
+                            spliterator.forEach(wrapped);
+                            wrapped.end();
+                        } else {
+                            iterator().forEach(block);
+                        }
+                    }
+
+                    @Override
+                    public int getSizeIfKnown() {
+                        return pph.isOutputSizeKnown() ? spliterator.getSizeIfKnown() : -1;
+                    }
+
+                    @Override
+                    public boolean isPredictableSplits() {
+                        return source.isPredictableSplits();
+                    }
+                }
+
+                return new WrappingSpliterator(source);
+            }
         };
 
         set(referenceStreamShape);
@@ -106,17 +174,102 @@
             }
 
             @Override
-            public NodeBuilder makeNodeBuilder(int sizeIfKnown) {
+            public IntNodeBuilder makeNodeBuilder(int sizeIfKnown) {
                 // @@@ raw types
                 return IntNodes.makeBuilder(sizeIfKnown);
             }
 
             @Override
-            public Node collect(ParallelPipelineHelper helper, boolean flattenTree) {
+            public IntNode collect(ParallelPipelineHelper helper, boolean flattenTree) {
                 // @@@ raw types
                 return IntTreeUtils.collect(helper, flattenTree);
             }
 
+            @Override
+            public IntSpliterator iteratorSpliterator(Iterator i, int sizeIfKnown) {
+                return Primitives.spliterator(Primitives.adapt(i), sizeIfKnown);
+            }
+
+            @Override
+            public IntSpliterator sequentialSpliterator(Spliterator s) {
+                return Primitives.sequentialSpliterator(Primitives.adapt(s));
+            }
+
+            @Override
+            public IntSpliterator proxySpliterator(Supplier s) {
+                return Primitives.proxySpliterator(s);
+            }
+
+            @Override
+            public IntSpliterator wrappingSpliterator(Spliterator source, ParallelPipelineHelper pph) {
+                // @@@ Move to Primitives?
+                class WrappingSpliterator implements IntSpliterator {
+                    final Spliterator spliterator;
+
+                    WrappingSpliterator(Spliterator spliterator) {
+                        this.spliterator = spliterator;
+                    }
+
+                    @Override
+                    public int getNaturalSplits() {
+                        return spliterator.getNaturalSplits();
+                    }
+
+                    @Override
+                    public IntSpliterator split() {
+                        return new WrappingSpliterator(spliterator.split());
+                    }
+
+                    @Override
+                    public IntIterator iterator() {
+                        return Primitives.adapt(pph.wrapIterator(spliterator.iterator()));
+                    }
+
+                    @Override
+                    public void forEach(Block<? super Integer> block) {
+                        if (block instanceof IntBlock) {
+                            forEach((IntBlock) block);
+                        }
+                        else if (!pph.isShortCircuit()) {
+                            Sink.OfReference<Integer> blockSink = block::accept;
+                            Sink wrapped = pph.wrapSink(blockSink);
+
+                            wrapped.begin(getSizeIfKnown());
+                            spliterator.forEach(wrapped);
+                            wrapped.end();
+                        }
+                        else {
+                            iterator().forEach(block);
+                        }
+                    }
+
+                    @Override
+                    public void forEach(IntBlock block) {
+                        if (!pph.isShortCircuit()) {
+                            Sink.OfInt s = block::accept;
+                            Sink wrapped = pph.wrapSink(s);
+
+                            wrapped.begin(getSizeIfKnown());
+                            spliterator.forEach(wrapped);
+                            wrapped.end();
+                        } else {
+                            iterator().forEach(block);
+                        }
+                    }
+
+                    @Override
+                    public int getSizeIfKnown() {
+                        return pph.isOutputSizeKnown() ? spliterator.getSizeIfKnown() : -1;
+                    }
+
+                    @Override
+                    public boolean isPredictableSplits() {
+                        return source.isPredictableSplits();
+                    }
+                }
+
+                return new WrappingSpliterator(source);
+            }
         };
 
         set(intStreamShape);
--- a/src/share/classes/java/util/stream/Streams.java	Wed Nov 28 14:08:04 2012 -0500
+++ b/src/share/classes/java/util/stream/Streams.java	Wed Nov 28 17:25:31 2012 -0500
@@ -68,7 +68,17 @@
         return (Spliterator.Sequential<U>) () -> iterator;
     }
 
-    // Stream
+    public static<T> Spliterator.Sequential<T> sequentialSpliterator(Spliterator<T> spliterator) {
+        return (spliterator instanceof Spliterator.Sequential)
+               ? (Spliterator.Sequential<T>) spliterator
+               : new SequentialSpliterator<>(spliterator);
+    }
+
+    public static<T> Spliterator<T> proxySpliterator(Supplier<Spliterator<T>> proxy) {
+        return new ProxySpliterator<>(proxy);
+    }
+
+    // Stream construction
 
     @SuppressWarnings("unchecked")
     public static<T> Stream<T> emptyStream() {
@@ -96,6 +106,82 @@
         return new ReferencePipeline<>(new ProxySpliterator<>(proxy), flags | StreamOpFlag.IS_PARALLEL);
     }
 
+    public static<T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b) {
+        Spliterator<T> aSpliterator = (Spliterator<T>) a.spliterator();
+        Spliterator<T> bSpliterator = (Spliterator<T>) b.spliterator();
+        int combinedFlags = a.getStreamFlags() & b.getStreamFlags() & StreamOpFlag.IS_ORDERED;
+        if (aSpliterator.getSizeIfKnown() != -1 && bSpliterator.getSizeIfKnown() != -1)
+            combinedFlags |= StreamOpFlag.IS_SIZED;
+
+        Spliterator<T> split = new Spliterator<T>() {
+            boolean beforeSplit = true;
+
+            @Override
+            public int getNaturalSplits() {
+                return beforeSplit ? 1 : bSpliterator.getNaturalSplits();
+            }
+
+            @Override
+            public Spliterator<T> split() {
+                Spliterator<T> ret = beforeSplit ? aSpliterator : bSpliterator.split();
+                beforeSplit = false;
+                return ret;
+            }
+
+            @Override
+            public Iterator<T> iterator() {
+                return beforeSplit
+                       ? Iterators.concat(aSpliterator.iterator(), bSpliterator.iterator())
+                       : bSpliterator.iterator();
+            }
+
+            @Override
+            public void forEach(Block<? super T> block) {
+                if (beforeSplit) {
+                    aSpliterator.forEach(block);
+                    bSpliterator.forEach(block);
+                }
+                else
+                    bSpliterator.forEach(block);
+            }
+
+            @Override
+            public int getSizeIfKnown() {
+                if (beforeSplit) {
+                    int aSize = aSpliterator.getSizeIfKnown();
+                    int bSize = bSpliterator.getSizeIfKnown();
+                    return (aSize == -1 || bSize == -1) ? -1 : aSize + bSize;
+                }
+                else {
+                    return bSpliterator.getSizeIfKnown();
+                }
+            }
+
+            @Override
+            public int estimateSize() {
+                if (beforeSplit) {
+                    int aSize = aSpliterator.estimateSize();
+                    int bSize = bSpliterator.estimateSize();
+                    return (aSize == -1 || bSize == -1) ? -1 : aSize + bSize;
+                }
+                else {
+                    return bSpliterator.estimateSize();
+                }
+            }
+
+            @Override
+            public boolean isPredictableSplits() {
+                return beforeSplit
+                       ? aSpliterator.isPredictableSplits() && bSpliterator.isPredictableSplits()
+                       : bSpliterator.isPredictableSplits();
+            }
+        };
+
+        return (a.isParallel() || b.isParallel())
+               ? parallel(split, combinedFlags)
+               : stream(split, combinedFlags);
+    }
+
     // Infinite streams
 
     public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f) {
@@ -319,4 +405,32 @@
             return spliterator().isPredictableSplits();
         }
     }
+
+    private static class SequentialSpliterator<T> implements Spliterator.Sequential<T> {
+        private final Spliterator<T> underlying;
+
+        public SequentialSpliterator(Spliterator<T> underlying) {
+            this.underlying = underlying;
+        }
+
+        @Override
+        public Iterator<T> iterator() {
+            return underlying.iterator();
+        }
+
+        @Override
+        public void forEach(Block<? super T> block) {
+            underlying.forEach(block);
+        }
+
+        @Override
+        public int getSizeIfKnown() {
+            return underlying.getSizeIfKnown();
+        }
+
+        @Override
+        public int estimateSize() {
+            return underlying.estimateSize();
+        }
+    }
 }
--- a/src/share/classes/java/util/stream/op/ConcatOp.java	Wed Nov 28 14:08:04 2012 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,94 +0,0 @@
-/*
- * Copyright (c) 2012, 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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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 java.util.stream.op;
-
-import java.util.Iterator;
-import java.util.Iterators;
-import java.util.Objects;
-import java.util.stream.*;
-
-public class ConcatOp<T> implements StatefulOp<T, T> {
-
-    private final Stream<? extends T> stream;
-
-    // @@@ Support Streamable
-    //     This will ensure the operation can be used in detached pipelines
-    //     and there is a choice to obtain the serial or parallel stream
-    //     There are still cases where Stream is useful e.g. from I/O sources
-    // @@@ Requires flags are available on Streamable for analysis of the pipeline
-    // @@@ Might be possible for some consolidation if Streamable was generic to Stream/MapStream
-    public ConcatOp(Stream<? extends T> stream) {
-        this.stream = stream;
-    }
-
-    @Override
-    public int getOpFlags() {
-        return StreamOpFlag.NOT_SORTED | StreamOpFlag.NOT_DISTINCT | StreamOpFlag.NOT_SIZED;
-    }
-
-    @Override
-    public Iterator<T> wrapIterator(int flags, Iterator<T> source) {
-        Objects.requireNonNull(source);
-
-        return Iterators.concat(source, stream.iterator());
-    }
-
-    @Override
-    public Sink<T> wrapSink(int flags, Sink<T> sink) {
-        Objects.requireNonNull(sink);
-
-        return new Sink.ChainedReference<T>(sink) {
-            @Override
-            public void accept(T t) {
-                downstream.accept(t);
-            }
-
-            @Override
-            public void end() {
-                // Pull from the concatenating stream to ensure sequential access
-                // Note that stream.forEach(downstream) will not, in the parallel case,
-                // guarantee an order, and stream.sequential().forEach(downstream) will
-                // result in buffering of the stream contents
-                Iterator<? extends T> i = stream.iterator();
-                while (i.hasNext()) {
-                    downstream.accept(i.next());
-                }
-                downstream.end();
-            }
-        };
-    }
-
-    @Override
-    public <S> Node<T> evaluateParallel(ParallelPipelineHelper<S, T> helper) {
-        // Get all stuff from upstream
-        Node<T> upStreamNode = helper.collectOutput();
-
-        // Get stuff from concatenation
-        Node<T> concatStreamNode = Nodes.node(stream);
-
-        // Combine
-        return Nodes.node(upStreamNode, concatStreamNode);
-    }
-}
--- a/src/share/classes/java/util/stream/primitive/IntPipeline.java	Wed Nov 28 14:08:04 2012 -0500
+++ b/src/share/classes/java/util/stream/primitive/IntPipeline.java	Wed Nov 28 17:25:31 2012 -0500
@@ -29,6 +29,7 @@
 import java.util.function.IntBlock;
 import java.util.function.IntUnaryOperator;
 import java.util.stream.AbstractPipeline;
+import java.util.stream.Spliterator;
 import java.util.stream.Stream;
 import java.util.stream.StreamShapeFactory;
 import java.util.stream.op.FoldOp;
@@ -52,6 +53,11 @@
         return Primitives.adapt(super.iterator());
     }
 
+    @Override
+    public IntSpliterator spliterator() {
+        return Primitives.adapt(super.spliterator());
+    }
+
     //
 
     @Override
@@ -148,11 +154,6 @@
     }
 
     @Override
-    public IntStream concat(IntStream other) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
     public IntStream unordered() {
         throw new UnsupportedOperationException();
     }
--- a/src/share/classes/java/util/stream/primitive/IntSortedOp.java	Wed Nov 28 14:08:04 2012 -0500
+++ b/src/share/classes/java/util/stream/primitive/IntSortedOp.java	Wed Nov 28 17:25:31 2012 -0500
@@ -80,10 +80,10 @@
         Objects.requireNonNull(iterator);
 
         IntIterator intIterator = Primitives.adapt(iterator);
-        IntNodeBuilder nb = IntNodes.makeVariableSizeBuilder();
-        intIterator.forEach(nb);
+        IntSpinedList isl = new IntSpinedList();
+        intIterator.forEach(isl);
 
-        int[] ints = nb.build().asIntArray();
+        int[] ints = isl.asIntArray();
         Arrays.sort(ints);
 
         return Primitives.iterator(ints);
--- a/src/share/classes/java/util/stream/primitive/IntStream.java	Wed Nov 28 14:08:04 2012 -0500
+++ b/src/share/classes/java/util/stream/primitive/IntStream.java	Wed Nov 28 17:25:31 2012 -0500
@@ -43,6 +43,9 @@
     @Override
     IntIterator iterator();
 
+    @Override
+    IntSpliterator spliterator();
+
     //
 
     // @@@ Do the following make sense?
@@ -83,8 +86,6 @@
 
     IntStream skip(int n);
 
-    IntStream concat(IntStream other);
-
     IntStream sequential();
 
     IntStream unordered();
--- a/src/share/classes/java/util/stream/primitive/Primitives.java	Wed Nov 28 14:08:04 2012 -0500
+++ b/src/share/classes/java/util/stream/primitive/Primitives.java	Wed Nov 28 17:25:31 2012 -0500
@@ -24,11 +24,8 @@
  */
 package java.util.stream.primitive;
 
-import java.util.function.IntBlock;
-import java.util.function.IntSupplier;
-import java.util.function.IntUnaryOperator;
+import java.util.function.*;
 import java.util.*;
-import java.util.function.Block;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 import java.util.stream.*;
@@ -75,6 +72,17 @@
         return () -> iterator(array, offset, length);
     }
 
+    public static IntSpliterator adapt(final Spliterator<Integer> i) {
+        if (i instanceof IntSpliterator) {
+            return (IntSpliterator) i;
+        }
+        else {
+            Logger.getLogger(Primitives.class.getName()).log(Level.WARNING, "Using Primitives.adapt(Splterator)");
+            throw new UnsupportedOperationException(i.getClass().getName() + " not an instance of IntSpliterator");
+            // @@@ Implement?
+        }
+    }
+
     // IntIterator
 
     public static IntIterator emptyIntIterator() {
@@ -96,6 +104,10 @@
         return EMPTY_INT_SPLITERATOR;
     }
 
+    public static IntSpliterator proxySpliterator(Supplier<IntSpliterator> proxy) {
+        return new ProxyIntSpliterator(proxy);
+    }
+
     public static<T extends Sized & IntIterable> IntSpliterator sequentialSpliterator(T entity) {
         Objects.requireNonNull(entity);
         return new IntIterableSequentialSpliterator(entity, entity);
@@ -117,6 +129,20 @@
         return (IntSpliterator.Sequential) () -> iterator;
     }
 
+    public static IntSpliterator.Sequential sequentialSpliterator(IntSpliterator spliterator) {
+        return (spliterator instanceof IntSpliterator.Sequential)
+               ? (IntSpliterator.Sequential) spliterator
+               : new SequentialIntSpliterator(spliterator);
+    }
+
+    public static IntSpliterator spliterator(IntIterator iterator) {
+        return spliterator(iterator, -1);
+    }
+
+    public static IntSpliterator spliterator(IntIterator iterator, int size) {
+        return new IntIteratorIntSpliterator(iterator, size);
+    }
+
     public static IntSpliterator spliterator(int[] array) {
         return spliterator(array, 0, array.length);
     }
@@ -681,4 +707,157 @@
                 iterator.forEach(block);
         }
     }
+
+    private static class ProxyIntSpliterator implements IntSpliterator {
+        private final Supplier<IntSpliterator> supplier;
+        private IntSpliterator spliterator;
+
+        private ProxyIntSpliterator(Supplier<IntSpliterator> supplier) {
+            this.supplier = supplier;
+        }
+
+        private IntSpliterator spliterator() {
+            if (spliterator == null)
+                spliterator = supplier.get();
+            return spliterator;
+        }
+
+        @Override
+        public int getNaturalSplits() {
+            return spliterator().getNaturalSplits();
+        }
+
+        @Override
+        public IntSpliterator split() {
+            return spliterator().split();
+        }
+
+        @Override
+        public IntIterator iterator() {
+            return spliterator().iterator();
+        }
+
+        @Override
+        public void forEach(Block<? super Integer> block) {
+            if (block instanceof IntBlock) {
+                forEach((IntBlock) block);
+            }
+            else {
+                spliterator().forEach(block);
+            }
+        }
+
+        @Override
+        public void forEach(IntBlock block) {
+            spliterator().forEach(block);
+        }
+
+        @Override
+        public int getSizeIfKnown() {
+            return spliterator().getSizeIfKnown();
+        }
+
+        @Override
+        public int estimateSize() {
+            return spliterator().estimateSize();
+        }
+
+        @Override
+        public boolean isPredictableSplits() {
+            return spliterator().isPredictableSplits();
+        }
+    }
+
+    private static class SequentialIntSpliterator implements IntSpliterator.Sequential {
+        private final IntSpliterator underlying;
+
+        public SequentialIntSpliterator(IntSpliterator underlying) {
+            this.underlying = underlying;
+        }
+
+        @Override
+        public IntIterator iterator() {
+            return underlying.iterator();
+        }
+
+        @Override
+        public void forEach(Block<? super Integer> block) {
+            underlying.forEach(block);
+        }
+
+        @Override
+        public void forEach(IntBlock block) {
+            underlying.forEach(block);
+        }
+
+        @Override
+        public int getSizeIfKnown() {
+            return underlying.getSizeIfKnown();
+        }
+
+        @Override
+        public int estimateSize() {
+            return underlying.estimateSize();
+        }
+    }
+
+    private static class IntIteratorIntSpliterator implements IntSpliterator {
+        private static final int MAX_ITERATOR_CHUNK_SIZE = 1024;
+
+        final IntIterator iterator;
+        int sizeIfKnown;
+        boolean traversing;
+        int nextSize = 1;
+
+        private IntIteratorIntSpliterator(IntIterator iterator, int sizeIfKnown) {
+            this.iterator = iterator;
+            this.sizeIfKnown = sizeIfKnown;
+        }
+
+        @Override
+        public int getNaturalSplits() {
+            return iterator.hasNext() ? 1 : 0;
+        }
+
+        @Override
+        public IntSpliterator split() {
+            if (traversing)
+                throw new IllegalStateException();
+            else if (!iterator.hasNext())
+                return Primitives.emptyIntSpliterator();
+            else {
+                int[] array = new int[nextSize];
+                int i = 0;
+                while (i < array.length && iterator.hasNext())
+                    array[i++] = iterator.nextInt();
+                if (sizeIfKnown >= 0) {
+                    sizeIfKnown -= i;
+                }
+                nextSize = Math.min(nextSize * 2, MAX_ITERATOR_CHUNK_SIZE);
+                return Primitives.spliterator(array, 0, i);
+            }
+        }
+
+        @Override
+        public IntIterator iterator() {
+            traversing = true;
+            return iterator;
+        }
+
+        @Override
+        public boolean isPredictableSplits() {
+            return sizeIfKnown >= 0;
+        }
+
+        @Override
+        public int getSizeIfKnown() {
+            return sizeIfKnown;
+        }
+
+        @Override
+        public int estimateSize() {
+            return sizeIfKnown;
+        }
+    };
+
 }
--- a/test-ng/tests/org/openjdk/tests/java/util/LambdaTestHelpers.java	Wed Nov 28 14:08:04 2012 -0500
+++ b/test-ng/tests/org/openjdk/tests/java/util/LambdaTestHelpers.java	Wed Nov 28 17:25:31 2012 -0500
@@ -26,6 +26,7 @@
 
 import java.util.*;
 import java.util.function.*;
+import java.util.stream.Streamable;
 import java.util.stream.TerminalSink;
 import java.util.stream.Stream;
 import java.util.stream.primitive.IntPredicate;
@@ -251,9 +252,15 @@
     public static <T> boolean equalsContentsUnordered(Iterable<T> a, Iterable<T> b) {
         Set<T> sa = new HashSet<>();
         a.forEach(sa::add);
+//        for (T t : a) {
+//            sa.add(t);
+//        }
 
         Set<T> sb = new HashSet<>();
         b.forEach(sb::add);
+//        for (T t : b) {
+//            sb.add(t);
+//        }
 
         return Objects.equals(sa, sb);
     }
--- a/test-ng/tests/org/openjdk/tests/java/util/stream/OpTestCase.java	Wed Nov 28 14:08:04 2012 -0500
+++ b/test-ng/tests/org/openjdk/tests/java/util/stream/OpTestCase.java	Wed Nov 28 17:25:31 2012 -0500
@@ -29,6 +29,8 @@
 import org.testng.Assert;
 import org.testng.annotations.Test;
 
+import java.io.PrintWriter;
+import java.io.StringWriter;
 import java.util.*;
 import java.util.function.BiPredicate;
 import java.util.function.Block;
@@ -269,19 +271,49 @@
                 after.accept(data);
             }
 
+            List<Error> errors = new ArrayList<>();
             for (BaseStreamTestScenario test : testSet) {
-                before.accept(data);
+                try {
+                    before.accept(data);
 
-                NodeBuilder<U> resultBuilder = shape.makeNodeBuilder(-1);
-                resultBuilder.begin(-1);
-                test.run(data, resultBuilder, m);
-                resultBuilder.end();
-                Node<U> result = resultBuilder.build();
+                    NodeBuilder<U> resultBuilder = shape.makeNodeBuilder(-1);
+                    resultBuilder.begin(-1);
 
-                assertTrue(getEqualator(test).test(result, refResult),
-                           String.format("%s %s: %s != %s", data.toString(), test, refResult, result));
+                    test.run(data, resultBuilder, m);
+                    resultBuilder.end();
+                    Node<U> result = resultBuilder.build();
 
-                after.accept(data);
+                    assertTrue(getEqualator(test).test(result, refResult),
+                               String.format("%s: %s != %s", test, refResult, result));
+
+                    after.accept(data);
+                } catch (AssertionError ae) {
+                    errors.add(ae);
+                } catch (Throwable t) {
+                    errors.add(new Error(String.format("%s: %s", test, t), t));
+                }
+            }
+
+            if (!errors.isEmpty()) {
+                StringBuilder sb = new StringBuilder();
+                int i = 1;
+                for (Error t : errors) {
+                    sb.append(i++).append(": ");
+                    if (t instanceof AssertionError) {
+                        sb.append(t).append("\n");
+                    }
+                    else {
+                        StringWriter sw = new StringWriter();
+                        PrintWriter pw = new PrintWriter(sw);
+
+                        t.getCause().printStackTrace(pw);
+                        pw.flush();
+                        sb.append(t).append("\n").append(sw);
+                    }
+                }
+                sb.append("--");
+
+                fail(String.format("%d failure(s) for test data: %s\n%s", i - 1, data.toString(), sb));
             }
 
             return refResult;
--- a/test-ng/tests/org/openjdk/tests/java/util/stream/SpliteratorTest.java	Wed Nov 28 14:08:04 2012 -0500
+++ b/test-ng/tests/org/openjdk/tests/java/util/stream/SpliteratorTest.java	Wed Nov 28 17:25:31 2012 -0500
@@ -24,7 +24,6 @@
  */
 package org.openjdk.tests.java.util.stream;
 
-import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
 
 import java.util.ArrayList;
@@ -34,6 +33,7 @@
 import java.util.stream.Spliterator;
 
 import static org.testng.Assert.*;
+import static org.testng.Assert.assertEquals;
 
 /**
  * SpliteratorTest
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test-ng/tests/org/openjdk/tests/java/util/stream/StreamSpliteratorTest.java	Wed Nov 28 17:25:31 2012 -0500
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2012, 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.Test;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Block;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.stream.Spliterator;
+import java.util.stream.Stream;
+import java.util.stream.Streams;
+
+import static org.openjdk.tests.java.util.LambdaTestHelpers.cInteger;
+import static org.openjdk.tests.java.util.LambdaTestHelpers.mDoubler;
+
+@Test
+public class StreamSpliteratorTest extends OpTestCase {
+
+    SpliteratorTest proxy = new SpliteratorTest();
+
+    @Test(dataProvider = "StreamTestData<Integer>", dataProviderClass = StreamTestDataProvider.class)
+    public void testStreamSpliterators(String name, StreamTestData<Integer> data) {
+        for (Function<Stream<Integer>, Stream<Integer>> f : streamFunctions()) {
+            exerciseWithSpliterator(data, f);
+        }
+    }
+
+    void exerciseWithSpliterator(StreamTestData<Integer> data, Function<Stream<Integer>, Stream<Integer>> f) {
+        withData(data).
+                stream(streamToStreamOfSpliterator(f)).
+                exercise();
+
+        withData(data).
+                stream(streamToParStreamOfSpliterator(f)).
+                exercise();
+    }
+
+    Function<Stream<Integer>, Stream<Integer>> streamToStreamOfSpliterator(Function<Stream<Integer>, Stream<Integer>> f) {
+        return (in) -> {
+            Stream<Integer> out = f.apply(in);
+            return Streams.stream(out.spliterator(), out.getStreamFlags());
+        };
+    }
+
+    Function<Stream<Integer>, Stream<Integer>> streamToParStreamOfSpliterator(Function<Stream<Integer>, Stream<Integer>> f) {
+        return (in) -> {
+            Stream<Integer> out = f.apply(in);
+            return Streams.parallel(out.spliterator(), out.getStreamFlags());
+        };
+    }
+
+    @Test(dataProvider = "StreamTestData<Integer>", dataProviderClass = StreamTestDataProvider.class)
+    public void testSpliterators(String name, StreamTestData<Integer> data) {
+        for (Block<Supplier<Spliterator<Integer>>> b : spliteratorBlocks()) {
+            for (Function<Stream<Integer>, Stream<Integer>> f : streamFunctions()) {
+                b.accept(() -> f.apply(data.stream()).spliterator());
+            }
+        }
+    }
+
+    @Test(dataProvider = "StreamTestData<Integer>", dataProviderClass = StreamTestDataProvider.class)
+    public void testParSpliterators(String name, StreamTestData<Integer> data) {
+        for (Block<Supplier<Spliterator<Integer>>> b : spliteratorBlocks()) {
+            for (Function<Stream<Integer>, Stream<Integer>> f : streamFunctions()) {
+                b.accept(() -> f.apply(data.parallel()).spliterator());
+            }
+        }
+    }
+
+    List<Function<Stream<Integer>, Stream<Integer>>> streamFunctions() {
+        return Arrays.<Function<Stream<Integer>, Stream<Integer>>>asList(
+                s -> s,
+                s -> s.map(mDoubler),
+                s -> s.sorted(cInteger),
+                s -> s.sorted(cInteger).map(mDoubler),
+                s -> s.map(mDoubler).sorted(cInteger).map(mDoubler),
+                s -> s.map(mDoubler).sequential().sorted(cInteger).map(mDoubler),
+                s -> s.map(mDoubler).sorted(cInteger).sequential().map(mDoubler));
+    }
+
+    List<Block<Supplier<Spliterator<Integer>>>> spliteratorBlocks() {
+        return Arrays.<Block<Supplier<Spliterator<Integer>>>>asList(
+                s -> proxy.testIteratorAgainstForEach("testIteratorAgainstForEach", s),
+                s -> proxy.testMixedIteratorForeach("testMixedIteratorForeach", s),
+                s -> proxy.testSplitOnce("testSplitOnce", s),
+                s -> proxy.testSplitSixDeep("testSplitSixDeep", s)
+        );
+    }
+}
--- a/test-ng/tests/org/openjdk/tests/java/util/stream/StreamTestDataProvider.java	Wed Nov 28 14:08:04 2012 -0500
+++ b/test-ng/tests/org/openjdk/tests/java/util/stream/StreamTestDataProvider.java	Wed Nov 28 17:25:31 2012 -0500
@@ -114,7 +114,9 @@
             spliterators.add(new Object[] {"sS(Arrays.asList(array)):" + name, (Supplier<Spliterator<Integer>>) () -> Streams.sequentialSpliterator(Arrays.asList(ints)) });
             spliterators.add(new Object[] {"sS((It)Arrays.asList(array)):" + name, (Supplier<Spliterator<Integer>>) () -> Streams.sequentialSpliterator((Iterable<Integer>) Arrays.asList(ints)) });
             spliterators.add(new Object[] {"SpinedList.s():" + name, (Supplier<Spliterator<Integer>>) () -> new SpinedList<>(Arrays.asList(ints)).spliterator() });
-            // @@@ Need lots more!
+            spliterators.add(new Object[] {"Iterators.s(Arrays.s(array).iterator(), size):" + name, (Supplier<Spliterator<Integer>>) () -> Iterators.spliterator(Arrays.asList(ints).iterator(), ints.length) });
+            spliterators.add(new Object[] {"Iterators.s(Arrays.s(array).iterator()):" + name, (Supplier<Spliterator<Integer>>) () -> Iterators.spliterator(Arrays.asList(ints).iterator()) });
+            // Need more!
         }
         spliteratorTestData = spliterators.toArray(new Object[0][]);
     }
--- a/test-ng/tests/org/openjdk/tests/java/util/stream/StreamTestScenario.java	Wed Nov 28 14:08:04 2012 -0500
+++ b/test-ng/tests/org/openjdk/tests/java/util/stream/StreamTestScenario.java	Wed Nov 28 17:25:31 2012 -0500
@@ -36,7 +36,11 @@
 
     STREAM_FOR_EACH(false) {
         <T, U, S_IN extends BaseStream<T>> void _run(OpTestCase.TestData<T, S_IN> data, Block<U> b, Function<S_IN, Stream<U>> m) {
-            m.apply(data.stream()).forEach(b);
+            Stream<U> s = m.apply(data.stream());
+            if (s.isParallel()) {
+                s = s.sequential();
+            }
+            s.forEach(b);
         }
     },
 
@@ -66,6 +70,14 @@
         }
     },
 
+    // Wrap as stream, and spliterate then iterate in pull mode
+    STREAM_SPLITERATOR_ITERATOR(false) {
+        <T, U, S_IN extends BaseStream<T>> void _run(OpTestCase.TestData<T, S_IN> data, Block<U> b, Function<S_IN, Stream<U>> m) {
+            for (Iterator<U> seqIter = m.apply(data.stream()).spliterator().iterator(); seqIter.hasNext(); )
+                b.accept(seqIter.next());
+        }
+    },
+
     // Wrap as parallel stream + sequential
     PAR_STREAM_SEQUENTIAL_FOR_EACH(true) {
         <T, U, S_IN extends BaseStream<T>> void _run(OpTestCase.TestData<T, S_IN> data, Block<U> b, Function<S_IN, Stream<U>> m) {
@@ -81,6 +93,16 @@
         }
     },
 
+    // Wrap as parallel stream, get the spliterator, wrap as a stream + toArray
+    PAR_STREAM_SPLITERATOR_STREAM_TO_ARRAY(true) {
+        <T, U, S_IN extends BaseStream<T>> void _run(OpTestCase.TestData<T, S_IN> data, Block<U> b, Function<S_IN, Stream<U>> m) {
+            Stream<U> s = m.apply(data.parallel());
+            Stream<U> ss = Streams.parallel(s.spliterator(), s.getStreamFlags() | StreamOpFlag.IS_SIZED | StreamOpFlag.IS_ORDERED);
+            for (Object t : ss.toArray())
+                b.accept((U) t);
+        }
+    },
+
     // Wrap as parallel stream + toArray and clear SIZED flag
     PAR_STREAM_TO_ARRAY_CLEAR_SIZED(true) {
         <T, U, S_IN extends BaseStream<T>> void _run(OpTestCase.TestData<T, S_IN> data, Block<U> b, Function<S_IN, Stream<U>> m) {
--- a/test-ng/tests/org/openjdk/tests/java/util/stream/op/ConcatOpTest.java	Wed Nov 28 14:08:04 2012 -0500
+++ b/test-ng/tests/org/openjdk/tests/java/util/stream/op/ConcatOpTest.java	Wed Nov 28 17:25:31 2012 -0500
@@ -31,6 +31,7 @@
 import org.testng.annotations.Test;
 
 import java.util.*;
+import java.util.stream.Stream;
 import java.util.stream.Streams;
 
 import static org.openjdk.tests.java.util.LambdaTestHelpers.*;
@@ -82,38 +83,43 @@
     }
 
     public void testConcat() {
-        assertContents(Collections.<Integer>emptyList().stream().
-                concat(Collections.<Integer>emptyList().stream()).iterator(),
+        assertContents(Streams.concat(Collections.<Integer>emptyList().stream(),
+                                      Collections.<Integer>emptyList().stream()).iterator(),
                        Collections.<Integer>emptyList().iterator());
 
-        assertContents(countTo(10).stream().
-                concat(Collections.<Integer>emptyList().stream()).iterator(),
+        assertContents(Streams.concat(countTo(10).stream(),
+                                      Collections.<Integer>emptyList().stream()).iterator(),
                        countTo(10).stream().iterator());
 
-        assertContents(countTo(5).stream().
-                concat(range(6, 10).stream()).iterator(),
+        assertContents(Streams.concat(countTo(5).stream(), range(6, 10).stream()).iterator(),
                        countTo(10).stream().iterator());
 
-        assertContents(countTo(2).stream().
-                concat(range(3, 4).stream()).
-                concat(range(5, 6).stream()).
-                concat(range(7, 8).stream()).
-                concat(range(9, 10).stream()).iterator(),
-                       countTo(10).stream().iterator());
+        Stream<Integer> x = countTo(2).stream();
+        x = Streams.concat(x, range(3, 4).stream());
+        x = Streams.concat(x, range(5, 6).stream());
+        x = Streams.concat(x, range(7, 8).stream());
+        x = Streams.concat(x, range(9, 10).stream());
+        assertContents(x.iterator(), countTo(10).stream().iterator());
     }
 
     @Test(dataProvider = "StreamTestData<Integer>", dataProviderClass = StreamTestDataProvider.class)
     public void testOpsSequential(String name, StreamTestData<Integer> data) {
-        withData(data).
-                stream(s -> s.concat(data.stream())).
-                exercise();
+        withData(data).stream(s -> Streams.concat(s, data.stream())).exercise();
+    }
+
+    @Test(dataProvider = "StreamTestData<Integer>", dataProviderClass = StreamTestDataProvider.class)
+    public void testOpsSequential2(String name, StreamTestData<Integer> data) {
+        withData(data).stream(s -> { s = s.map(i -> i); return Streams.concat(s, data.stream()); }).exercise();
+    }
+
+    @Test(dataProvider = "StreamTestData<Integer>", dataProviderClass = StreamTestDataProvider.class)
+    public void testOpsSequential3(String name, StreamTestData<Integer> data) {
+        withData(data).stream(s -> { s = s.uniqueElements().map(i-> (Integer)(i + i)); return Streams.concat(s, data.stream()); }).exercise();
     }
 
     @Test(dataProvider = "StreamTestData<Integer>", dataProviderClass = StreamTestDataProvider.class)
     public void testOpsParallel(String name, StreamTestData<Integer> data) {
-        withData(data).
-                stream(s -> s.concat(data.parallel())).
-                exercise();
+        withData(data).stream(s -> Streams.concat(s, data.parallel())).exercise();
     }
 }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test-ng/tests/org/openjdk/tests/java/util/stream/primitive/IntStreamSpliteratorTest.java	Wed Nov 28 17:25:31 2012 -0500
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2012, 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.primitive;
+
+import org.openjdk.tests.java.util.stream.OpTestCase;
+import org.testng.annotations.Test;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Function;
+import java.util.stream.primitive.IntStream;
+import java.util.stream.primitive.Primitives;
+
+import static org.openjdk.tests.java.util.LambdaTestHelpers.irDoubler;
+
+@Test
+public class IntStreamSpliteratorTest extends OpTestCase {
+
+    @Test(dataProvider = "IntStreamTestData", dataProviderClass = IntStreamTestDataProvider.class)
+    public void testStreamSpliterators(String name, IntStreamTestData data) {
+        for (Function<IntStream, IntStream> f : streamFunctions()) {
+            exerciseWithSpliterator(data, f);
+        }
+    }
+
+    void exerciseWithSpliterator(IntStreamTestData data, Function<IntStream, IntStream> f) {
+        withData(data).
+                stream(streamToStreamOfSpliterator(f)).
+                exercise();
+
+        withData(data).
+                stream(streamToParStreamOfSpliterator(f)).
+                exercise();
+    }
+
+    Function<IntStream, IntStream> streamToStreamOfSpliterator(Function<IntStream, IntStream> f) {
+        return (in) -> {
+            IntStream out = f.apply(in);
+            return Primitives.stream(out.spliterator(), out.getStreamFlags());
+        };
+    }
+
+    Function<IntStream, IntStream> streamToParStreamOfSpliterator(Function<IntStream, IntStream> f) {
+        return (in) -> {
+            IntStream out = f.apply(in);
+            return Primitives.parallel(out.spliterator(), out.getStreamFlags());
+        };
+    }
+
+    List<Function<IntStream, IntStream>> streamFunctions() {
+        return Arrays.<Function<IntStream, IntStream>>asList(
+                s -> s,
+                s -> s.map(irDoubler),
+                s -> s.sorted(),
+                s -> s.sorted().map(irDoubler),
+                s -> s.map(irDoubler).sorted().map(irDoubler),
+                s -> s.map(irDoubler).sequential().sorted().map(irDoubler),
+                s -> s.map(irDoubler).sorted().sequential().map(irDoubler));
+    }
+}
+
--- a/test-ng/tests/org/openjdk/tests/java/util/stream/primitive/IntStreamTestDataProvider.java	Wed Nov 28 14:08:04 2012 -0500
+++ b/test-ng/tests/org/openjdk/tests/java/util/stream/primitive/IntStreamTestDataProvider.java	Wed Nov 28 17:25:31 2012 -0500
@@ -89,7 +89,6 @@
             final int[] ints = (int[])data[1];
 
             list.add(e("array:" + name, ints, IntStreamTestData.ArrayData::new));
-            list.add(e("SpinedList:" + name, ints, IntStreamTestData.ArrayData::new));
 
             IntSpinedList isl = new IntSpinedList();
             for (int i : ints) {
@@ -108,7 +107,7 @@
         return new Object[] { description, m.combine(description, data) };
     }
 
-    // Return an array of ( String name, IntStreamTestData<Integer> )
+    // Return an array of ( String name, IntStreamTestData )
     @DataProvider(name = "IntStreamTestData")
     public static Object[][] makeIntStreamTestData() {
         return testData;
--- a/test-ng/tests/org/openjdk/tests/java/util/stream/primitive/IntStreamTestScenario.java	Wed Nov 28 14:08:04 2012 -0500
+++ b/test-ng/tests/org/openjdk/tests/java/util/stream/primitive/IntStreamTestScenario.java	Wed Nov 28 17:25:31 2012 -0500
@@ -26,6 +26,7 @@
 
 import org.openjdk.tests.java.util.stream.OpTestCase;
 
+import java.util.Iterator;
 import java.util.function.Block;
 import java.util.function.IntBlock;
 import java.util.function.Function;
@@ -33,13 +34,18 @@
 import java.util.stream.op.FlagDeclaringOp;
 import java.util.stream.primitive.IntIterator;
 import java.util.stream.primitive.IntStream;
+import java.util.stream.primitive.Primitives;
 
 @SuppressWarnings({"rawtypes", "unchecked"})
 public enum IntStreamTestScenario implements OpTestCase.BaseStreamTestScenario {
 
     STREAM_FOR_EACH(false) {
         <T, S_IN extends BaseStream<T>> void _run(OpTestCase.TestData<T, S_IN> data, IntBlock b, Function<S_IN, IntStream> m) {
-            m.apply(data.stream()).forEach(b);
+            IntStream s = m.apply(data.stream());
+            if (s.isParallel()) {
+                s = s.sequential();
+            }
+            s.forEach(b);
         }
     },
 
@@ -58,6 +64,14 @@
         }
     },
 
+    // Wrap as stream, and spliterate then iterate in pull mode
+    STREAM_SPLITERATOR_ITERATOR(false) {
+        <T, S_IN extends BaseStream<T>> void _run(OpTestCase.TestData<T, S_IN> data, IntBlock b, Function<S_IN, IntStream> m) {
+            for (IntIterator seqIter = m.apply(data.stream()).spliterator().iterator(); seqIter.hasNext(); )
+                b.accept(seqIter.nextInt());
+        }
+    },
+
     PAR_STREAM_SEQUENTIAL_FOR_EACH(true) {
         <T, S_IN extends BaseStream<T>> void _run(OpTestCase.TestData<T, S_IN> data, IntBlock b, Function<S_IN, IntStream> m) {
             m.apply(data.parallel()).sequential().forEach(b);
@@ -71,6 +85,16 @@
         }
     },
 
+    // Wrap as parallel stream, get the spliterator, wrap as a stream + toArray
+    PAR_STREAM_SPLITERATOR_STREAM_TO_ARRAY(true) {
+        <T, S_IN extends BaseStream<T>> void _run(OpTestCase.TestData<T, S_IN> data, IntBlock b, Function<S_IN, IntStream> m) {
+            IntStream s = m.apply(data.parallel());
+            IntStream ss = Primitives.parallel(s.spliterator(), s.getStreamFlags() | StreamOpFlag.IS_SIZED | StreamOpFlag.IS_ORDERED);
+            for (int t : ss.toArray())
+                b.accept(t);
+        }
+    },
+
     PAR_STREAM_TO_ARRAY_CLEAR_SIZED(true) {
         <T, S_IN extends BaseStream<T>> void _run(OpTestCase.TestData<T, S_IN> data, IntBlock b, Function<S_IN, IntStream> m) {
             AbstractPipeline<?, ?> pipe1 = data.par(new FlagDeclaringOp(StreamOpFlag.NOT_SIZED) {