changeset 8100:f264f7085f2a

Merge NodeUtils into Nodes
author briangoetz
date Fri, 12 Apr 2013 14:24:35 -0400
parents b41092d39293
children 75605b4fdcf9
files src/share/classes/java/util/stream/DoublePipeline.java src/share/classes/java/util/stream/IntPipeline.java src/share/classes/java/util/stream/LongPipeline.java src/share/classes/java/util/stream/NodeUtils.java src/share/classes/java/util/stream/Nodes.java src/share/classes/java/util/stream/ReferencePipeline.java test-ng/boottests/java/util/stream/DoubleNodeTest.java test-ng/boottests/java/util/stream/IntNodeTest.java test-ng/boottests/java/util/stream/LongNodeTest.java test-ng/boottests/java/util/stream/NodeTest.java
diffstat 10 files changed, 849 insertions(+), 968 deletions(-) [+]
line wrap: on
line diff
--- a/src/share/classes/java/util/stream/DoublePipeline.java	Fri Apr 12 10:12:54 2013 -0700
+++ b/src/share/classes/java/util/stream/DoublePipeline.java	Fri Apr 12 14:24:35 2013 -0400
@@ -126,7 +126,7 @@
                                              Spliterator<P_IN> spliterator,
                                              boolean flattenTree,
                                              IntFunction<Double[]> generator) {
-        return NodeUtils.collectDouble(helper, spliterator, flattenTree);
+        return Nodes.collectDouble(helper, spliterator, flattenTree);
     }
 
     @Override
@@ -445,7 +445,7 @@
 
     @Override
     public final double[] toArray() {
-        return NodeUtils.flattenDouble((Node.OfDouble) evaluateToArrayNode(Double[]::new))
+        return Nodes.flattenDouble((Node.OfDouble) evaluateToArrayNode(Double[]::new))
                         .asDoubleArray();
     }
 
--- a/src/share/classes/java/util/stream/IntPipeline.java	Fri Apr 12 10:12:54 2013 -0700
+++ b/src/share/classes/java/util/stream/IntPipeline.java	Fri Apr 12 14:24:35 2013 -0400
@@ -122,7 +122,7 @@
                                               Spliterator<P_IN> spliterator,
                                               boolean flattenTree,
                                               IntFunction<Integer[]> generator) {
-        return NodeUtils.collectInt(helper, spliterator, flattenTree);
+        return Nodes.collectInt(helper, spliterator, flattenTree);
     }
 
     @Override
@@ -474,7 +474,7 @@
 
     @Override
     public final int[] toArray() {
-        return NodeUtils.flattenInt((Node.OfInt) evaluateToArrayNode(Integer[]::new))
+        return Nodes.flattenInt((Node.OfInt) evaluateToArrayNode(Integer[]::new))
                         .asIntArray();
     }
 
--- a/src/share/classes/java/util/stream/LongPipeline.java	Fri Apr 12 10:12:54 2013 -0700
+++ b/src/share/classes/java/util/stream/LongPipeline.java	Fri Apr 12 14:24:35 2013 -0400
@@ -122,7 +122,7 @@
                                            Spliterator<P_IN> spliterator,
                                            boolean flattenTree,
                                            IntFunction<Long[]> generator) {
-        return NodeUtils.collectLong(helper, spliterator, flattenTree);
+        return Nodes.collectLong(helper, spliterator, flattenTree);
     }
 
     @Override
@@ -459,7 +459,7 @@
 
     @Override
     public final long[] toArray() {
-        return NodeUtils.flattenLong((Node.OfLong) evaluateToArrayNode(Long[]::new)).asLongArray();
+        return Nodes.flattenLong((Node.OfLong) evaluateToArrayNode(Long[]::new)).asLongArray();
     }
 
 
--- a/src/share/classes/java/util/stream/NodeUtils.java	Fri Apr 12 10:12:54 2013 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,797 +0,0 @@
-/*
- * 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.  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;
-
-import java.util.Spliterator;
-import java.util.concurrent.CountedCompleter;
-import java.util.function.IntFunction;
-
-/**
- * Fork/Join-based parallel computing utilities for collecting output from a {@link PipelineHelper}
- * to a {@link Node} and flattening a {@link Node}.
- *
- * @since 1.8
- */
-final class NodeUtils {
-
-    private NodeUtils() {
-        throw new Error("no instances");
-    }
-
-    /**
-     * Collect, in parallel, elements output from a pipeline and encapsulate those elements
-     * in a (reference) {@link Node}.
-     *
-     * @implSpec
-     * If the exact size of the output from the pipeline is known and the source {@link Spliterator}
-     * has the {@link Spliterator#SUBSIZED} characteristic then a flat {@link Node} will be returned
-     * whose content is an array. Since the size is known the array can be constructed in advance and
-     * output elements can be placed into the array concurrently, by leaf Fork/Join tasks,
-     * at the correct offsets.
-     * If the exact size is not known then output elements are collected into a conc-{@code Node} whose
-     * shape mirrors that of the computation. This conc-{@code Node} can then be flattened in parallel
-     * to produce a flat {@code Node} whose content is an array.
-     *
-     * @param helper the pipeline helper capturing the pipeline.
-     * @param flattenTree if true the returned {@link java.util.stream.Node} is flat and has no children, otherwise
-     *                    the {@link java.util.stream.Node} may be a root node in a tree whose shape mirrors that of the
-     *                    parallel computation.
-     * @param generator the array generator
-     * @return the {@link Node} encapsulating the output elements.
-     */
-    public static <P_IN, P_OUT> Node<P_OUT> collect(PipelineHelper<P_OUT> helper,
-                                                    Spliterator<P_IN> spliterator,
-                                                    boolean flattenTree,
-                                                    IntFunction<P_OUT[]> generator) {
-        long size = helper.exactOutputSizeIfKnown(spliterator);
-        if (size >= 0 && spliterator.hasCharacteristics(Spliterator.SUBSIZED)) {
-            if (size >= Streams.MAX_ARRAY_SIZE)
-                throw new IllegalArgumentException("Stream size exceeds max array size");
-            P_OUT[] array = generator.apply((int) size);
-            new SizedCollectorTask.OfRef<>(spliterator, helper, array).invoke();
-            return Nodes.node(array);
-        } else {
-            Node<P_OUT> node = new CollectorTask<>(helper, generator, spliterator).invoke();
-            return flattenTree ? flatten(node, generator) : node;
-        }
-    }
-
-    /**
-     * Flatten, in parallel, a {@link Node}.
-     *
-     * @implSpec
-     * An array will be created, from the generator, whose length is {@link Node#count()}.
-     * Then the node tree will be traversed and leaf node elements will be placed into the array
-     * concurrently, by leaf Fork/Join tasks, at the correct offsets.
-     *
-     * @param node the node to flatten.
-     * @param generator the array factory to be utilized to create array instances.
-     * @param <T> type of elements contained by the node
-     * @return a flattened {@code Node}. If the input node is already flat then
-     * that node is returned directly.
-     */
-    public static <T> Node<T> flatten(Node<T> node, IntFunction<T[]> generator) {
-        if (node.getChildCount() > 0) {
-            T[] array = generator.apply((int) node.count());
-            new ToArrayTask.OfRef<>(node, array, 0).invoke();
-            return Nodes.node(array);
-        } else {
-            return node;
-        }
-    }
-
-    // Ints
-
-    /**
-     * Collect, in parallel, {@code int} elements output from a pipeline and encapsulate those elements
-     * in a {@link Node.OfInt}.
-     *
-     * @implSpec
-     * If the exact size of the output from the pipeline is known and the source {@link Spliterator}
-     * has the {@link Spliterator#SUBSIZED} characteristic then a flat {@link Node.OfInt} will be returned
-     * whose content is an int[] array. Since the size is known the array can be constructed in advance and
-     * output elements can be placed into the array concurrently, by leaf Fork/Join tasks,
-     * at the correct offsets.
-     * If the exact size is not known then output elements are collected into a conc-{@code Node.OfInt} whose
-     * shape mirrors that of the computation. This conc-{@code Node.OfInt} can then be flattened in parallel
-     * to produce a flat {@code Node.OfInt} whose content is an int[] array.
-     *
-     * @param helper the pipeline helper capturing the pipeline.
-     * @param flattenTree if true the returned {@link Node.OfInt} is flat and has no children, otherwise
-     *                    the {@link Node.OfInt} may be a root node in a tree whose shape mirrors that of the
-     *                    parallel computation.
-     * @param <P_IN> type of input elements to the pipeline
-     * @return the {@link Node.OfInt} encapsulating the output elements.
-     */
-    public static <P_IN> Node.OfInt collectInt(PipelineHelper<Integer> helper,
-                                               Spliterator<P_IN> spliterator,
-                                               boolean flattenTree) {
-        long size = helper.exactOutputSizeIfKnown(spliterator);
-        if (size >= 0 && spliterator.hasCharacteristics(Spliterator.SUBSIZED)) {
-            if (size >= Streams.MAX_ARRAY_SIZE)
-                throw new IllegalArgumentException("Stream size exceeds max array size");
-            int[] array = new int[(int) size];
-            new SizedCollectorTask.OfInt<>(spliterator, helper, array).invoke();
-            return Nodes.node(array);
-        }
-        else {
-            Node.OfInt node = new IntCollectorTask<>(helper, spliterator).invoke();
-            return flattenTree ? flattenInt(node) : node;
-        }
-    }
-
-    /**
-     * Flatten, in parallel, a {@link Node.OfInt}.
-     *
-     * @implSpec
-     * An int[] array will be created whose length is {@link Node.OfInt#count()}.
-     * Then the node tree will be traversed and leaf node elements will be placed into the array
-     * concurrently, by leaf Fork/Join tasks, at the correct offsets.
-     *
-     * @param node the node to flatten.
-     * @return a flattened {@code Node.OfInt}. If the input node is already flat then
-     * that node is returned directly.
-     */
-    public static Node.OfInt flattenInt(Node.OfInt node) {
-        if (node.getChildCount() > 0) {
-            int[] array = new int[(int) node.count()];
-            new ToArrayTask.OfInt(node, array, 0).invoke();
-            return Nodes.node(array);
-        } else {
-            return node;
-        }
-    }
-
-    // Longs
-
-    /**
-     * Collect, in parallel, {@code long} elements output from a pipeline and encapsulate those elements
-     * in a {@link Node.OfLong}.
-     *
-     * @implSpec
-     * If the exact size of the output from the pipeline is known and the source {@link Spliterator}
-     * has the {@link Spliterator#SUBSIZED} characteristic then a flat {@link Node.OfLong} will be returned
-     * whose content is an long[] array. Since the size is known the array can be constructed in advance and
-     * output elements can be placed into the array concurrently, by leaf Fork/Join tasks,
-     * at the correct offsets.
-     * If the exact size is not known then output elements are collected into a conc-{@code Node.OfLong} whose
-     * shape mirrors that of the computation. This conc-{@code Node.OfLong} can then be flattened in parallel
-     * to produce a flat {@code Node.OfLong} whose content is an long[] array.
-     *
-     * @param helper the pipeline helper capturing the pipeline.
-     * @param flattenTree if true the returned {@link Node.OfLong} is flat and has no children, otherwise
-     *                    the {@link Node.OfLong} may be a root node in a tree whose shape mirrors that of the
-     *                    parallel computation.
-     * @param <P_IN> type of input elements to the pipeline
-     * @return the {@link Node.OfLong} encapsulating the output elements.
-     */
-    public static <P_IN> Node.OfLong collectLong(PipelineHelper<Long> helper,
-                                                 Spliterator<P_IN> spliterator,
-                                                 boolean flattenTree) {
-        long size = helper.exactOutputSizeIfKnown(spliterator);
-        if (size >= 0 && spliterator.hasCharacteristics(Spliterator.SUBSIZED)) {
-            if (size >= Streams.MAX_ARRAY_SIZE)
-                throw new IllegalArgumentException("Stream size exceeds max array size");
-            long[] array = new long[(int) size];
-            new SizedCollectorTask.OfLong<>(spliterator, helper, array).invoke();
-            return Nodes.node(array);
-        }
-        else {
-            Node.OfLong node = new LongCollectorTask<>(helper, spliterator).invoke();
-            return flattenTree ? flattenLong(node) : node;
-        }
-    }
-
-    /**
-     * Flatten, in parallel, a {@link Node.OfLong}.
-     *
-     * @implSpec
-     * An long[] array will be created whose length is {@link Node.OfLong#count()}.
-     * Then the node tree will be traversed and leaf node elements will be placed into the array
-     * concurrently, by leaf Fork/Join tasks, at the correct offsets.
-     *
-     * @param node the node to flatten.
-     * @return a flattened {@code Node.OfLong}. If the input node is already flat then
-     * that node is returned directly.
-     */
-    public static Node.OfLong flattenLong(Node.OfLong node) {
-        if (node.getChildCount() > 0) {
-            long[] array = new long[(int) node.count()];
-            new ToArrayTask.OfLong(node, array, 0).invoke();
-            return Nodes.node(array);
-        } else {
-            return node;
-        }
-    }
-
-    // Doubles
-
-    /**
-     * Collect, in parallel, {@code double} elements output from a pipeline and encapsulate those elements
-     * in a {@link Node.OfDouble}.
-     *
-     * @implSpec
-     * If the exact size of the output from the pipeline is known and the source {@link Spliterator}
-     * has the {@link Spliterator#SUBSIZED} characteristic then a flat {@link Node.OfDouble} will be returned
-     * whose content is an double[] array. Since the size is known the array can be constructed in advance and
-     * output elements can be placed into the array concurrently, by leaf Fork/Join tasks,
-     * at the correct offsets.
-     * If the exact size is not known then output elements are collected into a conc-{@code Node.OfDouble} whose
-     * shape mirrors that of the computation. This conc-{@code Node.OfDouble} can then be flattened in parallel
-     * to produce a flat {@code Node.OfDouble} whose content is an double[] array.
-     *
-     * @param helper the pipeline helper capturing the pipeline.
-     * @param flattenTree if true the returned {@link Node.OfDouble} is flat and has no children, otherwise
-     *                    the {@link Node.OfDouble} may be a root node in a tree whose shape mirrors that of the
-     *                    parallel computation.
-     * @param <P_IN> type of input elements to the pipeline
-     * @return the {@link Node.OfDouble} encapsulating the output elements.
-     */
-    public static <P_IN> Node.OfDouble collectDouble(PipelineHelper<Double> helper,
-                                                     Spliterator<P_IN> spliterator,
-                                                     boolean flattenTree) {
-        long size = helper.exactOutputSizeIfKnown(spliterator);
-        if (size >= 0 && spliterator.hasCharacteristics(Spliterator.SUBSIZED)) {
-            if (size >= Streams.MAX_ARRAY_SIZE)
-                throw new IllegalArgumentException("Stream size exceeds max array size");
-            double[] array = new double[(int) size];
-            new SizedCollectorTask.OfDouble<>(spliterator, helper, array).invoke();
-            return Nodes.node(array);
-        }
-        else {
-            Node.OfDouble node = new DoubleCollectorTask<>(helper, spliterator).invoke();
-            return flattenTree ? flattenDouble(node) : node;
-        }
-    }
-
-    /**
-     * Flatten, in parallel, a {@link Node.OfDouble}.
-     *
-     * @implSpec
-     * An double[] array will be created whose length is {@link Node.OfDouble#count()}.
-     * Then the node tree will be traversed and leaf node elements will be placed into the array
-     * concurrently, by leaf Fork/Join tasks, at the correct offsets.
-     *
-     * @param node the node to flatten.
-     * @return a flattened {@code Node.OfDouble}. If the input node is already flat then
-     * that node is returned directly.
-     */
-    public static Node.OfDouble flattenDouble(Node.OfDouble node) {
-        if (node.getChildCount() > 0) {
-            double[] array = new double[(int) node.count()];
-            new ToArrayTask.OfDouble(node, array, 0).invoke();
-            return Nodes.node(array);
-        } else {
-            return node;
-        }
-    }
-
-    // Reference implementations
-
-    private static final class CollectorTask<T, U> extends AbstractTask<T, U, Node<U>, CollectorTask<T, U>> {
-        private final PipelineHelper<U> helper;
-        private final IntFunction<U[]> generator;
-
-        CollectorTask(PipelineHelper<U> helper, IntFunction<U[]> generator, Spliterator<T> spliterator) {
-            super(helper, spliterator);
-            this.helper = helper;
-            this.generator = generator;
-        }
-
-        CollectorTask(CollectorTask<T, U> parent, Spliterator<T> spliterator) {
-            super(parent, spliterator);
-            helper = parent.helper;
-            generator = parent.generator;
-        }
-
-        @Override
-        protected CollectorTask<T, U> makeChild(Spliterator<T> spliterator) {
-            return new CollectorTask<>(this, spliterator);
-        }
-
-        @Override
-        protected Node<U> doLeaf() {
-            Node.Builder<U> builder = Nodes.builder(helper.exactOutputSizeIfKnown(spliterator),
-                                                    generator);
-            return helper.wrapAndCopyInto(builder, spliterator).build();
-        }
-
-        @Override
-        public void onCompletion(CountedCompleter caller) {
-            if (!isLeaf()) {
-                int numChildren = 0;
-                for (CollectorTask<T, U> cur = children; cur != null; cur = cur.nextSibling)
-                    ++numChildren;
-                @SuppressWarnings("unchecked")
-                Node<U>[] nodes = (Node<U>[]) new Node[numChildren];
-                int idx = 0;
-                for (CollectorTask<T, U> cur = children; cur != null; cur = cur.nextSibling)
-                    nodes[idx++] = cur.getLocalResult();
-                setLocalResult(Nodes.node(nodes));
-            }
-            super.onCompletion(caller);
-        }
-    }
-
-    private static abstract class SizedCollectorTask<P_IN, P_OUT, T_SINK extends Sink<P_OUT>,
-                                                     K extends SizedCollectorTask<P_IN, P_OUT, T_SINK, K>>
-            extends CountedCompleter<Void>
-            implements Sink<P_OUT> {
-        protected final Spliterator<P_IN> spliterator;
-        protected final PipelineHelper<P_OUT> helper;
-        protected final long targetSize;
-        protected long offset;
-        protected long length;
-        // For Sink implementation
-        protected int index, fence;
-
-        SizedCollectorTask(Spliterator<P_IN> spliterator, PipelineHelper<P_OUT> helper, int arrayLength) {
-            assert spliterator.hasCharacteristics(Spliterator.SUBSIZED);
-            this.spliterator = spliterator;
-            this.helper = helper;
-            this.targetSize = AbstractTask.suggestTargetSize(spliterator.estimateSize());
-            this.offset = 0;
-            this.length = arrayLength;
-        }
-
-        SizedCollectorTask(K parent, Spliterator<P_IN> spliterator, long offset, long length, int arrayLength) {
-            super(parent);
-            assert spliterator.hasCharacteristics(Spliterator.SUBSIZED);
-            this.spliterator = spliterator;
-            this.helper = parent.helper;
-            this.targetSize = parent.targetSize;
-            this.offset = offset;
-            this.length = length;
-
-            if (offset < 0 || length < 0 || (offset + length - 1 >= arrayLength)) {
-                throw new IllegalArgumentException(
-                        String.format("offset and length interval [%d, %d + %d) is not within array size interval [0, %d)",
-                                      offset, offset, length, arrayLength));
-            }
-        }
-
-        @Override
-        public void compute() {
-            SizedCollectorTask<P_IN, P_OUT, T_SINK, K> task = this;
-            while (true) {
-                Spliterator<P_IN> leftSplit;
-                if (!AbstractTask.suggestSplit(task.spliterator, task.targetSize)
-                    || ((leftSplit = task.spliterator.trySplit()) == null)) {
-                    if (task.offset + task.length >= Streams.MAX_ARRAY_SIZE)
-                        throw new IllegalArgumentException("Stream size exceeds max array size");
-                    T_SINK sink = (T_SINK) task;
-                    task.helper.wrapAndCopyInto(sink, task.spliterator);
-                    task.propagateCompletion();
-                    return;
-                }
-                else {
-                    task.setPendingCount(1);
-                    long leftSplitSize = leftSplit.estimateSize();
-                    task.makeChild(leftSplit, task.offset, leftSplitSize).fork();
-                    task = task.makeChild(task.spliterator, task.offset + leftSplitSize,
-                                          task.length - leftSplitSize);
-                }
-            }
-        }
-
-        abstract K makeChild(Spliterator<P_IN> spliterator, long offset, long size);
-
-        @Override
-        public void begin(long size) {
-            if(size > length)
-                throw new IllegalStateException("size passed to Sink.begin exceeds array length");
-            index = (int) offset;
-            fence = (int) offset + (int) length;
-        }
-
-        static final class OfRef<P_IN, P_OUT>
-                extends SizedCollectorTask<P_IN, P_OUT, Sink<P_OUT>, OfRef<P_IN, P_OUT>>
-                implements Sink<P_OUT> {
-            private final P_OUT[] array;
-
-            OfRef(Spliterator<P_IN> spliterator, PipelineHelper<P_OUT> helper, P_OUT[] array) {
-                super(spliterator, helper, array.length);
-                this.array = array;
-            }
-
-            OfRef(OfRef<P_IN, P_OUT> parent, Spliterator<P_IN> spliterator, long offset, long length) {
-                super(parent, spliterator, offset, length, parent.array.length);
-                this.array = parent.array;
-            }
-
-            @Override
-            OfRef<P_IN, P_OUT> makeChild(Spliterator<P_IN> spliterator, long offset, long size) {
-                return new OfRef<>(this, spliterator, offset, size);
-            }
-
-            @Override
-            public void accept(P_OUT value) {
-                if (index >= fence) {
-                    throw new IndexOutOfBoundsException(Integer.toString(index));
-                }
-                array[index++] = value;
-            }
-        }
-
-        static final class OfInt<P_IN>
-                extends SizedCollectorTask<P_IN, Integer, Sink.OfInt, OfInt<P_IN>>
-                implements Sink.OfInt {
-            private final int[] array;
-
-            OfInt(Spliterator<P_IN> spliterator, PipelineHelper<Integer> helper, int[] array) {
-                super(spliterator, helper, array.length);
-                this.array = array;
-            }
-
-            OfInt(SizedCollectorTask.OfInt<P_IN> parent, Spliterator<P_IN> spliterator, long offset, long length) {
-                super(parent, spliterator, offset, length, parent.array.length);
-                this.array = parent.array;
-            }
-
-            @Override
-            SizedCollectorTask.OfInt<P_IN> makeChild(Spliterator<P_IN> spliterator, long offset, long size) {
-                return new SizedCollectorTask.OfInt<>(this, spliterator, offset, size);
-            }
-
-            @Override
-            public void accept(int value) {
-                if (index >= fence) {
-                    throw new IndexOutOfBoundsException(Integer.toString(index));
-                }
-                array[index++] = value;
-            }
-        }
-
-        static final class OfLong<P_IN>
-                extends SizedCollectorTask<P_IN, Long, Sink.OfLong, OfLong<P_IN>>
-                implements Sink.OfLong {
-            private final long[] array;
-
-            OfLong(Spliterator<P_IN> spliterator, PipelineHelper<Long> helper, long[] array) {
-                super(spliterator, helper, array.length);
-                this.array = array;
-            }
-
-            OfLong(SizedCollectorTask.OfLong<P_IN> parent, Spliterator<P_IN> spliterator, long offset, long length) {
-                super(parent, spliterator, offset, length, parent.array.length);
-                this.array = parent.array;
-            }
-
-            @Override
-            SizedCollectorTask.OfLong<P_IN> makeChild(Spliterator<P_IN> spliterator, long offset, long size) {
-                return new SizedCollectorTask.OfLong<>(this, spliterator, offset, size);
-            }
-
-            @Override
-            public void accept(long value) {
-                if (index >= fence) {
-                    throw new IndexOutOfBoundsException(Integer.toString(index));
-                }
-                array[index++] = value;
-            }
-        }
-
-        static final class OfDouble<P_IN>
-                extends SizedCollectorTask<P_IN, Double, Sink.OfDouble, OfDouble<P_IN>>
-                implements Sink.OfDouble {
-            private final double[] array;
-
-            OfDouble(Spliterator<P_IN> spliterator, PipelineHelper<Double> helper, double[] array) {
-                super(spliterator, helper, array.length);
-                this.array = array;
-            }
-
-            OfDouble(SizedCollectorTask.OfDouble<P_IN> parent, Spliterator<P_IN> spliterator, long offset, long length) {
-                super(parent, spliterator, offset, length, parent.array.length);
-                this.array = parent.array;
-            }
-
-            @Override
-            SizedCollectorTask.OfDouble<P_IN> makeChild(Spliterator<P_IN> spliterator, long offset, long size) {
-                return new SizedCollectorTask.OfDouble<>(this, spliterator, offset, size);
-            }
-
-            @Override
-            public void accept(double value) {
-                if (index >= fence) {
-                    throw new IndexOutOfBoundsException(Integer.toString(index));
-                }
-                array[index++] = value;
-            }
-        }
-    }
-
-    private static abstract class ToArrayTask<T, T_NODE extends Node<T>,
-                                              K extends ToArrayTask<T, T_NODE, K>>
-            extends CountedCompleter<Void> {
-        protected final T_NODE node;
-        protected final int offset;
-
-        ToArrayTask(T_NODE node, int offset) {
-            this.node = node;
-            this.offset = offset;
-        }
-
-        ToArrayTask(K parent, T_NODE node, int offset) {
-            super(parent);
-            this.node = node;
-            this.offset = offset;
-        }
-
-        abstract void copyNodeToArray();
-
-        abstract K makeChild(int childIndex, int offset);
-
-        @Override
-        public void compute() {
-            ToArrayTask<T, T_NODE, K> task = this;
-            while (true) {
-                if (task.node.getChildCount() == 0) {
-                    task.copyNodeToArray();
-                    task.propagateCompletion();
-                    return;
-                }
-                else {
-                    task.setPendingCount(task.node.getChildCount() - 1);
-
-                    int size = 0;
-                    int i = 0;
-                    for (;i < task.node.getChildCount() - 1; i++) {
-                        K leftTask = task.makeChild(i, task.offset + size);
-                        size += leftTask.node.count();
-                        leftTask.fork();
-                    }
-                    task = task.makeChild(i, task.offset + size);
-                }
-            }
-        }
-
-        private static final class OfRef<T> extends ToArrayTask<T, Node<T>, OfRef<T>> {
-            private final T[] array;
-
-            private OfRef(Node<T> node, T[] array, int offset) {
-                super(node, offset);
-                this.array = array;
-            }
-
-            private OfRef(OfRef<T> parent, Node<T> node, int offset) {
-                super(parent, node, offset);
-                this.array = parent.array;
-            }
-
-            @Override
-            OfRef<T> makeChild(int childIndex, int offset) {
-                return new OfRef<>(this, node.getChild(childIndex), offset);
-            }
-
-            @Override
-            void copyNodeToArray() {
-                node.copyInto(array, offset);
-            }
-        }
-
-        private static final class OfInt extends ToArrayTask<Integer, Node.OfInt, OfInt> {
-            private final int[] array;
-
-            private OfInt(Node.OfInt node, int[] array, int offset) {
-                super(node, offset);
-                this.array = array;
-            }
-
-            private OfInt(OfInt parent, Node.OfInt node, int offset) {
-                super(parent, node, offset);
-                this.array = parent.array;
-            }
-
-            @Override
-            OfInt makeChild(int childIndex, int offset) {
-                return new OfInt(this, node.getChild(childIndex), offset);
-            }
-
-            @Override
-            void copyNodeToArray() {
-                node.copyInto(array, offset);
-            }
-        }
-
-        private static final class OfLong extends ToArrayTask<Long, Node.OfLong, OfLong> {
-            private final long[] array;
-
-            private OfLong(Node.OfLong node, long[] array, int offset) {
-                super(node, offset);
-                this.array = array;
-            }
-
-            private OfLong(OfLong parent, Node.OfLong node, int offset) {
-                super(parent, node, offset);
-                this.array = parent.array;
-            }
-
-            @Override
-            OfLong makeChild(int childIndex, int offset) {
-                return new OfLong(this, node.getChild(childIndex), offset);
-            }
-
-            @Override
-            void copyNodeToArray() {
-                node.copyInto(array, offset);
-            }
-        }
-
-        private static final class OfDouble extends ToArrayTask<Double, Node.OfDouble, OfDouble> {
-            private final double[] array;
-
-            private OfDouble(Node.OfDouble node, double[] array, int offset) {
-                super(node, offset);
-                this.array = array;
-            }
-
-            private OfDouble(OfDouble parent, Node.OfDouble node, int offset) {
-                super(parent, node, offset);
-                this.array = parent.array;
-            }
-
-            @Override
-            OfDouble makeChild(int childIndex, int offset) {
-                return new OfDouble(this, node.getChild(childIndex), offset);
-            }
-
-            @Override
-            void copyNodeToArray() {
-                node.copyInto(array, offset);
-            }
-        }
-    }
-
-    // Int value implementations
-
-    private static final class IntCollectorTask<T> extends AbstractTask<T, Integer, Node.OfInt, IntCollectorTask<T>> {
-        private final PipelineHelper<Integer> helper;
-
-        IntCollectorTask(PipelineHelper<Integer> helper, Spliterator<T> spliterator) {
-            super(helper, spliterator);
-            this.helper = helper;
-        }
-
-        IntCollectorTask(IntCollectorTask<T> parent, Spliterator<T> spliterator) {
-            super(parent, spliterator);
-            helper = parent.helper;
-        }
-
-        @Override
-        protected IntCollectorTask<T> makeChild(Spliterator<T> spliterator) {
-            return new IntCollectorTask<>(this, spliterator);
-        }
-
-        @Override
-        protected Node.OfInt doLeaf() {
-            Node.Builder.OfInt builder = Nodes.intBuilder(helper.exactOutputSizeIfKnown(spliterator));
-            return helper.wrapAndCopyInto(builder, spliterator).build();
-        }
-
-        @Override
-        public void onCompletion(CountedCompleter caller) {
-            if (!isLeaf()) {
-                int numChildren = 0;
-                for (IntCollectorTask<T> cur = children; cur != null; cur = cur.nextSibling)
-                    ++numChildren;
-                Node.OfInt[] nodes = new Node.OfInt[numChildren];
-                int idx = 0;
-                for (IntCollectorTask<T> cur = children; cur != null; cur = cur.nextSibling)
-                    nodes[idx++] = cur.getLocalResult();
-
-                setLocalResult(Nodes.conc(nodes));
-            }
-            super.onCompletion(caller);
-        }
-    }
-
-    // Long value implementations
-
-    private static final class LongCollectorTask<T> extends AbstractTask<T, Long, Node.OfLong, LongCollectorTask<T>> {
-        private final PipelineHelper<Long> helper;
-
-        LongCollectorTask(PipelineHelper<Long> helper, Spliterator<T> spliterator) {
-            super(helper, spliterator);
-            this.helper = helper;
-        }
-
-        LongCollectorTask(LongCollectorTask<T> parent, Spliterator<T> spliterator) {
-            super(parent, spliterator);
-            helper = parent.helper;
-        }
-
-        @Override
-        protected LongCollectorTask<T> makeChild(Spliterator<T> spliterator) {
-            return new LongCollectorTask<>(this, spliterator);
-        }
-
-        @Override
-        protected Node.OfLong doLeaf() {
-            Node.Builder.OfLong builder = Nodes.longBuilder(helper.exactOutputSizeIfKnown(spliterator));
-            return helper.wrapAndCopyInto(builder, spliterator).build();
-        }
-
-        @Override
-        public void onCompletion(CountedCompleter caller) {
-            if (!isLeaf()) {
-                int numChildren = 0;
-                for (LongCollectorTask<T> cur = children; cur != null; cur = cur.nextSibling)
-                    ++numChildren;
-                Node.OfLong[] nodes = new Node.OfLong[numChildren];
-                int idx = 0;
-                for (LongCollectorTask<T> cur = children; cur != null; cur = cur.nextSibling)
-                    nodes[idx++] = cur.getLocalResult();
-
-                setLocalResult(Nodes.conc(nodes));
-            }
-            super.onCompletion(caller);
-        }
-    }
-
-    // Double value implementations
-
-    private static final class DoubleCollectorTask<T> extends AbstractTask<T, Double, Node.OfDouble, DoubleCollectorTask<T>> {
-        private final PipelineHelper<Double> helper;
-
-        DoubleCollectorTask(PipelineHelper<Double> helper, Spliterator<T> spliterator) {
-            super(helper, spliterator);
-            this.helper = helper;
-        }
-
-        DoubleCollectorTask(DoubleCollectorTask<T> parent, Spliterator<T> spliterator) {
-            super(parent, spliterator);
-            helper = parent.helper;
-        }
-
-        @Override
-        protected DoubleCollectorTask<T> makeChild(Spliterator<T> spliterator) {
-            return new DoubleCollectorTask<>(this, spliterator);
-        }
-
-        @Override
-        protected Node.OfDouble doLeaf() {
-            Node.Builder.OfDouble builder = Nodes.doubleBuilder(helper.exactOutputSizeIfKnown(spliterator));
-            return helper.wrapAndCopyInto(builder, spliterator).build();
-        }
-
-        @Override
-        public void onCompletion(CountedCompleter caller) {
-            if (!isLeaf()) {
-                int numChildren = 0;
-                for (DoubleCollectorTask<T> cur = children; cur != null; cur = cur.nextSibling)
-                    ++numChildren;
-                Node.OfDouble[] nodes = new Node.OfDouble[numChildren];
-                int idx = 0;
-                for (DoubleCollectorTask<T> cur = children; cur != null; cur = cur.nextSibling)
-                    nodes[idx++] = cur.getLocalResult();
-
-                setLocalResult(Nodes.conc(nodes));
-            }
-            super.onCompletion(caller);
-        }
-    }
-
-}
--- a/src/share/classes/java/util/stream/Nodes.java	Fri Apr 12 10:12:54 2013 -0700
+++ b/src/share/classes/java/util/stream/Nodes.java	Fri Apr 12 14:24:35 2013 -0400
@@ -26,6 +26,7 @@
 
 import java.util.*;
 import java.util.Spliterators;
+import java.util.concurrent.CountedCompleter;
 import java.util.function.Consumer;
 import java.util.function.DoubleConsumer;
 import java.util.function.IntConsumer;
@@ -36,7 +37,9 @@
 
 /**
  * Factory methods for constructing implementations of {@link Node} and
- * {@link Node.Builder} and their primitive specializations.
+ * {@link Node.Builder} and their primitive specializations.  Fork/Join tasks
+ * for collecting output from a {@link PipelineHelper} to a {@link Node} and
+ * flattening {@link Node}s.
  *
  * @since 1.8
  */
@@ -92,23 +95,30 @@
      */
     @SuppressWarnings("unchecked")
     static <T> Node<T> conc(StreamShape shape, List<Node<T>> nodes) {
-        try {
-            switch (shape) {
-                case REFERENCE:
-                    return node(nodes.toArray(new Node[nodes.size()]));
-                case INT_VALUE:
-                    return (Node<T>) conc(nodes.toArray(new Node.OfInt[nodes.size()]));
-                case LONG_VALUE:
-                    return (Node<T>) conc(nodes.toArray(new Node.OfLong[nodes.size()]));
-                case DOUBLE_VALUE:
-                    return (Node<T>) conc(nodes.toArray(new Node.OfDouble[nodes.size()]));
-                default:
-                    throw new IllegalStateException("Unknown shape " + shape);
+        int size = nodes.size();
+        if (size == 0)
+            return emptyNode(shape);
+        else if (size == 1)
+            return nodes.get(0);
+        else {
+            try {
+                switch (shape) {
+                    case REFERENCE:
+                        return new ConcNode<T>(nodes.toArray(new Node[nodes.size()]));
+                    case INT_VALUE:
+                        return (Node<T>) new IntConcNode(nodes.toArray(new Node.OfInt[nodes.size()]));
+                    case LONG_VALUE:
+                        return (Node<T>) new LongConcNode(nodes.toArray(new Node.OfLong[nodes.size()]));
+                    case DOUBLE_VALUE:
+                        return (Node<T>) new DoubleConcNode(nodes.toArray(new Node.OfDouble[nodes.size()]));
+                    default:
+                        throw new IllegalStateException("Unknown shape " + shape);
+                }
             }
-        }
-        catch (ArrayStoreException e) {
-            throw new IllegalStateException("Error creating Conc node for shape " + shape +
-                                            "; the input contained at least one Node instance of the wrong shape", e);
+            catch (ArrayStoreException e) {
+                throw new IllegalStateException("Error creating Conc node for shape " + shape +
+                                                "; the input contained at least one Node instance of the wrong shape", e);
+            }
         }
     }
 
@@ -212,35 +222,6 @@
     }
 
     /**
-     * Produces a concatenated {@link Node} that has two or more children.
-     * <p>The count of the concatenated node is equal to the sum of the count
-     * of each child. Traversal of the concatenated node traverses the content
-     * of each child in encounter order of the list of children. Splitting a
-     * spliterator obtained from the concatenated node preserves the encounter
-     * order of the list of children.
-     *
-     * <p>The result may be a concatenated node, the input sole node if the size
-     * of the list is 1, or an empty node.
-     *
-     * @param nodes the input nodes
-     * @param <T> the type of elements of the concatenated node
-     * @return a {@code Node} covering the elements of the input nodes
-     */
-    @SafeVarargs
-    static<T> Node<T> node(Node<T>... nodes) {
-        Objects.requireNonNull(nodes);
-        if (nodes.length > 1) {
-            return new ConcNode<>(nodes);
-        }
-        else if (nodes.length == 1) {
-            return nodes[0];
-        }
-        else {
-            return (Node<T>) EMPTY_NODE;
-        }
-    }
-
-    /**
      * Produces a {@link Node.Builder}.
      *
      * @param exactSizeIfKnown -1 if a variable size builder is requested,
@@ -281,34 +262,6 @@
     }
 
     /**
-     * Produces a concatenated {@link Node.OfInt} that has two or more children.
-     * <p>The count of the concatenated node is equal to the sum of the count
-     * of each child. Traversal of the concatenated node traverses the content
-     * of each child in encounter order of the list of children. Splitting a
-     * spliterator obtained from the concatenated node preserves the encounter
-     * order of the list of children.
-     *
-     * <p>The result may be a concatenated node, the input sole node if the size
-     * of the list is 1, or an empty node.
-     *
-     * @param nodes the input nodes
-     * @return a {@code Node.OfInt} covering the elements of the input nodes
-     */
-    @SafeVarargs
-    static Node.OfInt conc(Node.OfInt... nodes) {
-        Objects.requireNonNull(nodes);
-        if (nodes.length > 1) {
-            return new IntConcNode(nodes);
-        }
-        else if (nodes.length == 1) {
-            return nodes[0];
-        }
-        else {
-            return EMPTY_INT_NODE;
-        }
-    }
-
-    /**
      * Produces a {@link Node.Builder.OfInt}.
      *
      * @param exactSizeIfKnown -1 if a variable size builder is requested,
@@ -346,34 +299,6 @@
     }
 
     /**
-     * Produces a concatenated {@link Node.OfLong} that has two or more children.
-     * <p>The count of the concatenated node is equal to the sum of the count
-     * of each child. Traversal of the concatenated node traverses the content
-     * of each child in encounter order of the list of children. Splitting a
-     * spliterator obtained from the concatenated node preserves the encounter
-     * order of the list of children.
-     *
-     * <p>The result may be a concatenated node, the input sole node if the size
-     * of the list is 1, or an empty node.
-     *
-     * @param nodes the input nodes
-     * @return a {@code Node.OfLong} covering the elements of the input nodes
-     */
-    @SafeVarargs
-    static Node.OfLong conc(Node.OfLong... nodes) {
-        Objects.requireNonNull(nodes);
-        if (nodes.length > 1) {
-            return new LongConcNode(nodes);
-        }
-        else if (nodes.length == 1) {
-            return nodes[0];
-        }
-        else {
-            return EMPTY_LONG_NODE;
-        }
-    }
-
-    /**
      * Produces a {@link Node.Builder.OfLong}.
      *
      * @param exactSizeIfKnown -1 if a variable size builder is requested,
@@ -411,34 +336,6 @@
     }
 
     /**
-     * Produces a concatenated {@link Node.OfDouble} that has two or more children.
-     * <p>The count of the concatenated node is equal to the sum of the count
-     * of each child. Traversal of the concatenated node traverses the content
-     * of each child in encounter order of the list of children. Splitting a
-     * spliterator obtained from the concatenated node preserves the encounter
-     * order of the list of children.
-     *
-     * <p>The result may be a concatenated node, the input sole node if the size
-     * of the list is 1, or an empty node.
-     *
-     * @param nodes the input nodes
-     * @return a {@code Node.ofDouble} covering the elements of the input nodes
-     */
-    @SafeVarargs
-    static Node.OfDouble conc(Node.OfDouble... nodes) {
-        Objects.requireNonNull(nodes);
-        if (nodes.length > 1) {
-            return new DoubleConcNode(nodes);
-        }
-        else if (nodes.length == 1) {
-            return nodes[0];
-        }
-        else {
-            return EMPTY_DOUBLE_NODE;
-        }
-    }
-
-    /**
      * Produces a {@link Node.Builder.OfDouble}.
      *
      * @param exactSizeIfKnown -1 if a variable size builder is requested,
@@ -461,6 +358,253 @@
         return new DoubleSpinedNodeBuilder();
     }
 
+    // Parallel evaluation of pipelines to nodes
+
+    /**
+     * Collect, in parallel, elements output from a pipeline and describe those
+     * elements with a {@link Node}.
+     *
+     * @implSpec
+     * If the exact size of the output from the pipeline is known and the source
+     * {@link Spliterator} has the {@link Spliterator#SUBSIZED} characteristic,
+     * then a flat {@link Node} will be returned whose content is an array,
+     * since the size is known the array can be constructed in advance and
+     * output elements can be placed into the array concurrently by leaf
+     * tasks at the correct offsets.  If the exact size is not known, output
+     * elements are collected into a conc-node whose shape mirrors that
+     * of the computation. This conc-node can then be flattened in
+     * parallel to produce a flat {@code Node} if desired.
+     *
+     * @param helper the pipeline helper describing the pipeline
+     * @param flattenTree whether a conc node should be flattened into a node
+     *                    describing an array before returning
+     * @param generator the array generator
+     * @return a {@link Node} describing the output elements
+     */
+    public static <P_IN, P_OUT> Node<P_OUT> collect(PipelineHelper<P_OUT> helper,
+                                                    Spliterator<P_IN> spliterator,
+                                                    boolean flattenTree,
+                                                    IntFunction<P_OUT[]> generator) {
+        long size = helper.exactOutputSizeIfKnown(spliterator);
+        if (size >= 0 && spliterator.hasCharacteristics(Spliterator.SUBSIZED)) {
+            if (size >= Streams.MAX_ARRAY_SIZE)
+                throw new IllegalArgumentException("Stream size exceeds max array size");
+            P_OUT[] array = generator.apply((int) size);
+            new SizedCollectorTask.OfRef<>(spliterator, helper, array).invoke();
+            return node(array);
+        } else {
+            Node<P_OUT> node = new CollectorTask<>(helper, generator, spliterator).invoke();
+            return flattenTree ? flatten(node, generator) : node;
+        }
+    }
+
+    /**
+     * Collect, in parallel, elements output from an int-valued pipeline and
+     * describe those elements with a {@link Node.OfInt}.
+     *
+     * @implSpec
+     * If the exact size of the output from the pipeline is known and the source
+     * {@link Spliterator} has the {@link Spliterator#SUBSIZED} characteristic,
+     * then a flat {@link Node} will be returned whose content is an array,
+     * since the size is known the array can be constructed in advance and
+     * output elements can be placed into the array concurrently by leaf
+     * tasks at the correct offsets.  If the exact size is not known, output
+     * elements are collected into a conc-node whose shape mirrors that
+     * of the computation. This conc-node can then be flattened in
+     * parallel to produce a flat {@code Node.OfInt} if desired.
+     *
+     * @param helper the pipeline helper describing the pipeline
+     * @param flattenTree whether a conc node should be flattened into a node
+     *                    describing an array before returning
+     * @return a {@link Node.OfInt} describing the output elements
+     */
+    public static <P_IN> Node.OfInt collectInt(PipelineHelper<Integer> helper,
+                                               Spliterator<P_IN> spliterator,
+                                               boolean flattenTree) {
+        long size = helper.exactOutputSizeIfKnown(spliterator);
+        if (size >= 0 && spliterator.hasCharacteristics(Spliterator.SUBSIZED)) {
+            if (size >= Streams.MAX_ARRAY_SIZE)
+                throw new IllegalArgumentException("Stream size exceeds max array size");
+            int[] array = new int[(int) size];
+            new SizedCollectorTask.OfInt<>(spliterator, helper, array).invoke();
+            return node(array);
+        }
+        else {
+            Node.OfInt node = new IntCollectorTask<>(helper, spliterator).invoke();
+            return flattenTree ? flattenInt(node) : node;
+        }
+    }
+
+    /**
+     * Collect, in parallel, elements output from a long-valued pipeline and
+     * describe those elements with a {@link Node.OfLong}.
+     *
+     * @implSpec
+     * If the exact size of the output from the pipeline is known and the source
+     * {@link Spliterator} has the {@link Spliterator#SUBSIZED} characteristic,
+     * then a flat {@link Node} will be returned whose content is an array,
+     * since the size is known the array can be constructed in advance and
+     * output elements can be placed into the array concurrently by leaf
+     * tasks at the correct offsets.  If the exact size is not known, output
+     * elements are collected into a conc-node whose shape mirrors that
+     * of the computation. This conc-node can then be flattened in
+     * parallel to produce a flat {@code Node.OfLong} if desired.
+     *
+     * @param helper the pipeline helper describing the pipeline
+     * @param flattenTree whether a conc node should be flattened into a node
+     *                    describing an array before returning
+     * @return a {@link Node.OfLong} describing the output elements
+     */
+    public static <P_IN> Node.OfLong collectLong(PipelineHelper<Long> helper,
+                                                 Spliterator<P_IN> spliterator,
+                                                 boolean flattenTree) {
+        long size = helper.exactOutputSizeIfKnown(spliterator);
+        if (size >= 0 && spliterator.hasCharacteristics(Spliterator.SUBSIZED)) {
+            if (size >= Streams.MAX_ARRAY_SIZE)
+                throw new IllegalArgumentException("Stream size exceeds max array size");
+            long[] array = new long[(int) size];
+            new SizedCollectorTask.OfLong<>(spliterator, helper, array).invoke();
+            return node(array);
+        }
+        else {
+            Node.OfLong node = new LongCollectorTask<>(helper, spliterator).invoke();
+            return flattenTree ? flattenLong(node) : node;
+        }
+    }
+
+    /**
+     * Collect, in parallel, elements output from n double-valued pipeline and
+     * describe those elements with a {@link Node.OfDouble}.
+     *
+     * @implSpec
+     * If the exact size of the output from the pipeline is known and the source
+     * {@link Spliterator} has the {@link Spliterator#SUBSIZED} characteristic,
+     * then a flat {@link Node} will be returned whose content is an array,
+     * since the size is known the array can be constructed in advance and
+     * output elements can be placed into the array concurrently by leaf
+     * tasks at the correct offsets.  If the exact size is not known, output
+     * elements are collected into a conc-node whose shape mirrors that
+     * of the computation. This conc-node can then be flattened in
+     * parallel to produce a flat {@code Node.OfDouble} if desired.
+     *
+     * @param helper the pipeline helper describing the pipeline
+     * @param flattenTree whether a conc node should be flattened into a node
+     *                    describing an array before returning
+     * @return a {@link Node.OfDouble} describing the output elements
+     */
+    public static <P_IN> Node.OfDouble collectDouble(PipelineHelper<Double> helper,
+                                                     Spliterator<P_IN> spliterator,
+                                                     boolean flattenTree) {
+        long size = helper.exactOutputSizeIfKnown(spliterator);
+        if (size >= 0 && spliterator.hasCharacteristics(Spliterator.SUBSIZED)) {
+            if (size >= Streams.MAX_ARRAY_SIZE)
+                throw new IllegalArgumentException("Stream size exceeds max array size");
+            double[] array = new double[(int) size];
+            new SizedCollectorTask.OfDouble<>(spliterator, helper, array).invoke();
+            return node(array);
+        }
+        else {
+            Node.OfDouble node = new DoubleCollectorTask<>(helper, spliterator).invoke();
+            return flattenTree ? flattenDouble(node) : node;
+        }
+    }
+
+    // Parallel flattening of nodes
+
+    /**
+     * Flatten, in parallel, a {@link Node}.  A flattened node is one that has
+     * no children.  If the node is already flat, it is simply returned.
+     *
+     * @implSpec
+     * If a new node is to be created, the generator is used to create an array
+     * whose length is {@link Node#count()}.  Then the node tree is traversed
+     * and leaf node elements are placed in the array concurrently by leaf tasks
+     * at the correct offsets.
+     *
+     * @param node the node to flatten
+     * @param generator the array factory used to create array instances
+     * @param <T> type of elements contained by the node
+     * @return a flat {@code Node}
+     */
+    public static <T> Node<T> flatten(Node<T> node, IntFunction<T[]> generator) {
+        if (node.getChildCount() > 0) {
+            T[] array = generator.apply((int) node.count());
+            new ToArrayTask.OfRef<>(node, array, 0).invoke();
+            return node(array);
+        } else {
+            return node;
+        }
+    }
+
+    /**
+     * Flatten, in parallel, a {@link Node.OfInt}.  A flattened node is one that
+     * has no children.  If the node is already flat, it is simply returned.
+     *
+     * @implSpec
+     * If a new node is to be created, a new int[] array is created whose length
+     * is {@link Node#count()}.  Then the node tree is traversed and leaf node
+     * elements are placed in the array concurrently by leaf tasks at the
+     * correct offsets.
+     *
+     * @param node the node to flatten
+     * @return a flat {@code Node.OfInt}
+     */
+    public static Node.OfInt flattenInt(Node.OfInt node) {
+        if (node.getChildCount() > 0) {
+            int[] array = new int[(int) node.count()];
+            new ToArrayTask.OfInt(node, array, 0).invoke();
+            return node(array);
+        } else {
+            return node;
+        }
+    }
+
+    /**
+     * Flatten, in parallel, a {@link Node.OfLong}.  A flattened node is one that
+     * has no children.  If the node is already flat, it is simply returned.
+     *
+     * @implSpec
+     * If a new node is to be created, a new long[] array is created whose length
+     * is {@link Node#count()}.  Then the node tree is traversed and leaf node
+     * elements are placed in the array concurrently by leaf tasks at the
+     * correct offsets.
+     *
+     * @param node the node to flatten
+     * @return a flat {@code Node.OfLong}
+     */
+    public static Node.OfLong flattenLong(Node.OfLong node) {
+        if (node.getChildCount() > 0) {
+            long[] array = new long[(int) node.count()];
+            new ToArrayTask.OfLong(node, array, 0).invoke();
+            return node(array);
+        } else {
+            return node;
+        }
+    }
+
+    /**
+     * Flatten, in parallel, a {@link Node.OfDouble}.  A flattened node is one that
+     * has no children.  If the node is already flat, it is simply returned.
+     *
+     * @implSpec
+     * If a new node is to be created, a new double[] array is created whose length
+     * is {@link Node#count()}.  Then the node tree is traversed and leaf node
+     * elements are placed in the array concurrently by leaf tasks at the
+     * correct offsets.
+     *
+     * @param node the node to flatten
+     * @return a flat {@code Node.OfDouble}
+     */
+    public static Node.OfDouble flattenDouble(Node.OfDouble node) {
+        if (node.getChildCount() > 0) {
+            double[] array = new double[(int) node.count()];
+            new ToArrayTask.OfDouble(node, array, 0).invoke();
+            return node(array);
+        } else {
+            return node;
+        }
+    }
+
     // Implementations
 
     private static abstract class EmptyNode<T, T_ARR, T_CONS> implements Node<T> {
@@ -651,14 +795,15 @@
     }
 
     /** Node class for an internal node with two or more children */
-    private static final class ConcNode<T> implements Node<T> {
+    static final class ConcNode<T> implements Node<T> {
         private final Node<T>[] nodes;
         private final long size;
 
         ConcNode(Node<T>[] nodes) {
             this.nodes = nodes;
-            // The Node count will be required when the Node spliterator is obtained and it is cheaper
-            // to aggressively calculate bottom up as the tree is built rather than later on from the top down
+            // The Node count will be required when the Node spliterator is
+            // obtained and it is cheaper to aggressively calculate bottom up
+            // as the tree is built rather than later on from the top down
             // traversing the tree
             long count = 0;
             for (Node<T> n : nodes)
@@ -725,7 +870,9 @@
     }
 
     /** Abstract class for spliterator for all internal node classes */
-    private static abstract class InternalNodeSpliterator<T, S extends Spliterator<T>, N extends Node<T>, C>
+    private static abstract class InternalNodeSpliterator<T,
+                                                          S extends Spliterator<T>,
+                                                          N extends Node<T>, C>
             implements Spliterator<T> {
         // Node we are pointing to
         // null if full traversal has occurred
@@ -734,7 +881,7 @@
         // next child of curNode to consume
         int curChildIndex;
 
-        // The spliterator of the curNode if that node is the last node and has no children.
+        // The spliterator of the curNode if that node is last and has no children.
         // This spliterator will be delegated to for splitting and traversing.
         // null if curNode has children
         S lastNodeSpliterator;
@@ -752,7 +899,8 @@
         }
 
         /**
-         * Initiate a stack containing, in left-to-right order, the child nodes covered by this spliterator
+         * Initiate a stack containing, in left-to-right order, the child nodes
+         * covered by this spliterator
          */
         protected final Deque<N> initStack() {
             // Bias size to the case where leaf nodes are close to this node
@@ -764,8 +912,8 @@
         }
 
         /**
-         * Depth first search, in left-to-right order, of the node tree, using an explicit stack, to find
-         * the next non-empty leaf node.
+         * Depth first search, in left-to-right order, of the node tree, using
+         * an explicit stack, to find the next non-empty leaf node.
          */
         protected final N findNextLeafNode(Deque<N> stack) {
             N n = null;
@@ -1155,9 +1303,9 @@
 
         AbstractPrimitiveConcNode(N[] nodes) {
             this.nodes = nodes;
-            // The Node count will be required when the Node spliterator is obtained and it is cheaper
-            // to aggressively calculate bottom up as the tree is built rather than later on from the top down
-            // traversing the tree
+            // The Node count will be required when the Node spliterator is
+            // obtained and it is cheaper to aggressively calculate bottom up as
+            // the tree is built rather than later on by traversing the tree
             long count = 0;
             for (N n : nodes)
                 count += n.count();
@@ -1356,7 +1504,7 @@
         }
     }
 
-    private static final class IntConcNode
+    static final class IntConcNode
             extends AbstractPrimitiveConcNode<Integer, Node.OfInt>
             implements Node.OfInt {
 
@@ -1391,7 +1539,7 @@
         }
     }
 
-    private static final class LongConcNode
+    static final class LongConcNode
             extends AbstractPrimitiveConcNode<Long, Node.OfLong>
             implements Node.OfLong {
 
@@ -1426,7 +1574,7 @@
         }
     }
 
-    private static final class DoubleConcNode
+    static final class DoubleConcNode
             extends AbstractPrimitiveConcNode<Double, Node.OfDouble>
             implements Node.OfDouble {
 
@@ -1802,4 +1950,525 @@
             return this;
         }
     }
+
+    private static abstract class SizedCollectorTask<P_IN, P_OUT, T_SINK extends Sink<P_OUT>,
+                                                     K extends SizedCollectorTask<P_IN, P_OUT, T_SINK, K>>
+            extends CountedCompleter<Void>
+            implements Sink<P_OUT> {
+        protected final Spliterator<P_IN> spliterator;
+        protected final PipelineHelper<P_OUT> helper;
+        protected final long targetSize;
+        protected long offset;
+        protected long length;
+        // For Sink implementation
+        protected int index, fence;
+
+        SizedCollectorTask(Spliterator<P_IN> spliterator,
+                           PipelineHelper<P_OUT> helper,
+                           int arrayLength) {
+            assert spliterator.hasCharacteristics(Spliterator.SUBSIZED);
+            this.spliterator = spliterator;
+            this.helper = helper;
+            this.targetSize = AbstractTask.suggestTargetSize(spliterator.estimateSize());
+            this.offset = 0;
+            this.length = arrayLength;
+        }
+
+        SizedCollectorTask(K parent, Spliterator<P_IN> spliterator,
+                           long offset, long length, int arrayLength) {
+            super(parent);
+            assert spliterator.hasCharacteristics(Spliterator.SUBSIZED);
+            this.spliterator = spliterator;
+            this.helper = parent.helper;
+            this.targetSize = parent.targetSize;
+            this.offset = offset;
+            this.length = length;
+
+            if (offset < 0 || length < 0 || (offset + length - 1 >= arrayLength)) {
+                throw new IllegalArgumentException(
+                        String.format("offset and length interval [%d, %d + %d) is not within array size interval [0, %d)",
+                                      offset, offset, length, arrayLength));
+            }
+        }
+
+        @Override
+        public void compute() {
+            SizedCollectorTask<P_IN, P_OUT, T_SINK, K> task = this;
+            while (true) {
+                Spliterator<P_IN> leftSplit;
+                if (!AbstractTask.suggestSplit(task.spliterator, task.targetSize)
+                    || ((leftSplit = task.spliterator.trySplit()) == null)) {
+                    if (task.offset + task.length >= Streams.MAX_ARRAY_SIZE)
+                        throw new IllegalArgumentException("Stream size exceeds max array size");
+                    T_SINK sink = (T_SINK) task;
+                    task.helper.wrapAndCopyInto(sink, task.spliterator);
+                    task.propagateCompletion();
+                    return;
+                }
+                else {
+                    task.setPendingCount(1);
+                    long leftSplitSize = leftSplit.estimateSize();
+                    task.makeChild(leftSplit, task.offset, leftSplitSize).fork();
+                    task = task.makeChild(task.spliterator, task.offset + leftSplitSize,
+                                          task.length - leftSplitSize);
+                }
+            }
+        }
+
+        abstract K makeChild(Spliterator<P_IN> spliterator, long offset, long size);
+
+        @Override
+        public void begin(long size) {
+            if(size > length)
+                throw new IllegalStateException("size passed to Sink.begin exceeds array length");
+            index = (int) offset;
+            fence = (int) offset + (int) length;
+        }
+
+        static final class OfRef<P_IN, P_OUT>
+                extends SizedCollectorTask<P_IN, P_OUT, Sink<P_OUT>, OfRef<P_IN, P_OUT>>
+                implements Sink<P_OUT> {
+            private final P_OUT[] array;
+
+            OfRef(Spliterator<P_IN> spliterator, PipelineHelper<P_OUT> helper, P_OUT[] array) {
+                super(spliterator, helper, array.length);
+                this.array = array;
+            }
+
+            OfRef(OfRef<P_IN, P_OUT> parent, Spliterator<P_IN> spliterator,
+                  long offset, long length) {
+                super(parent, spliterator, offset, length, parent.array.length);
+                this.array = parent.array;
+            }
+
+            @Override
+            OfRef<P_IN, P_OUT> makeChild(Spliterator<P_IN> spliterator,
+                                         long offset, long size) {
+                return new OfRef<>(this, spliterator, offset, size);
+            }
+
+            @Override
+            public void accept(P_OUT value) {
+                if (index >= fence) {
+                    throw new IndexOutOfBoundsException(Integer.toString(index));
+                }
+                array[index++] = value;
+            }
+        }
+
+        static final class OfInt<P_IN>
+                extends SizedCollectorTask<P_IN, Integer, Sink.OfInt, OfInt<P_IN>>
+                implements Sink.OfInt {
+            private final int[] array;
+
+            OfInt(Spliterator<P_IN> spliterator, PipelineHelper<Integer> helper, int[] array) {
+                super(spliterator, helper, array.length);
+                this.array = array;
+            }
+
+            OfInt(SizedCollectorTask.OfInt<P_IN> parent, Spliterator<P_IN> spliterator,
+                  long offset, long length) {
+                super(parent, spliterator, offset, length, parent.array.length);
+                this.array = parent.array;
+            }
+
+            @Override
+            SizedCollectorTask.OfInt<P_IN> makeChild(Spliterator<P_IN> spliterator,
+                                                     long offset, long size) {
+                return new SizedCollectorTask.OfInt<>(this, spliterator, offset, size);
+            }
+
+            @Override
+            public void accept(int value) {
+                if (index >= fence) {
+                    throw new IndexOutOfBoundsException(Integer.toString(index));
+                }
+                array[index++] = value;
+            }
+        }
+
+        static final class OfLong<P_IN>
+                extends SizedCollectorTask<P_IN, Long, Sink.OfLong, OfLong<P_IN>>
+                implements Sink.OfLong {
+            private final long[] array;
+
+            OfLong(Spliterator<P_IN> spliterator, PipelineHelper<Long> helper, long[] array) {
+                super(spliterator, helper, array.length);
+                this.array = array;
+            }
+
+            OfLong(SizedCollectorTask.OfLong<P_IN> parent, Spliterator<P_IN> spliterator,
+                   long offset, long length) {
+                super(parent, spliterator, offset, length, parent.array.length);
+                this.array = parent.array;
+            }
+
+            @Override
+            SizedCollectorTask.OfLong<P_IN> makeChild(Spliterator<P_IN> spliterator,
+                                                      long offset, long size) {
+                return new SizedCollectorTask.OfLong<>(this, spliterator, offset, size);
+            }
+
+            @Override
+            public void accept(long value) {
+                if (index >= fence) {
+                    throw new IndexOutOfBoundsException(Integer.toString(index));
+                }
+                array[index++] = value;
+            }
+        }
+
+        static final class OfDouble<P_IN>
+                extends SizedCollectorTask<P_IN, Double, Sink.OfDouble, OfDouble<P_IN>>
+                implements Sink.OfDouble {
+            private final double[] array;
+
+            OfDouble(Spliterator<P_IN> spliterator, PipelineHelper<Double> helper, double[] array) {
+                super(spliterator, helper, array.length);
+                this.array = array;
+            }
+
+            OfDouble(SizedCollectorTask.OfDouble<P_IN> parent, Spliterator<P_IN> spliterator,
+                     long offset, long length) {
+                super(parent, spliterator, offset, length, parent.array.length);
+                this.array = parent.array;
+            }
+
+            @Override
+            SizedCollectorTask.OfDouble<P_IN> makeChild(Spliterator<P_IN> spliterator,
+                                                        long offset, long size) {
+                return new SizedCollectorTask.OfDouble<>(this, spliterator, offset, size);
+            }
+
+            @Override
+            public void accept(double value) {
+                if (index >= fence) {
+                    throw new IndexOutOfBoundsException(Integer.toString(index));
+                }
+                array[index++] = value;
+            }
+        }
+    }
+
+    private static abstract class ToArrayTask<T, T_NODE extends Node<T>,
+                                              K extends ToArrayTask<T, T_NODE, K>>
+            extends CountedCompleter<Void> {
+        protected final T_NODE node;
+        protected final int offset;
+
+        ToArrayTask(T_NODE node, int offset) {
+            this.node = node;
+            this.offset = offset;
+        }
+
+        ToArrayTask(K parent, T_NODE node, int offset) {
+            super(parent);
+            this.node = node;
+            this.offset = offset;
+        }
+
+        abstract void copyNodeToArray();
+
+        abstract K makeChild(int childIndex, int offset);
+
+        @Override
+        public void compute() {
+            ToArrayTask<T, T_NODE, K> task = this;
+            while (true) {
+                if (task.node.getChildCount() == 0) {
+                    task.copyNodeToArray();
+                    task.propagateCompletion();
+                    return;
+                }
+                else {
+                    task.setPendingCount(task.node.getChildCount() - 1);
+
+                    int size = 0;
+                    int i = 0;
+                    for (;i < task.node.getChildCount() - 1; i++) {
+                        K leftTask = task.makeChild(i, task.offset + size);
+                        size += leftTask.node.count();
+                        leftTask.fork();
+                    }
+                    task = task.makeChild(i, task.offset + size);
+                }
+            }
+        }
+
+        private static final class OfRef<T>
+                extends ToArrayTask<T, Node<T>, OfRef<T>> {
+            private final T[] array;
+
+            private OfRef(Node<T> node, T[] array, int offset) {
+                super(node, offset);
+                this.array = array;
+            }
+
+            private OfRef(OfRef<T> parent, Node<T> node, int offset) {
+                super(parent, node, offset);
+                this.array = parent.array;
+            }
+
+            @Override
+            OfRef<T> makeChild(int childIndex, int offset) {
+                return new OfRef<>(this, node.getChild(childIndex), offset);
+            }
+
+            @Override
+            void copyNodeToArray() {
+                node.copyInto(array, offset);
+            }
+        }
+
+        private static final class OfInt
+                extends ToArrayTask<Integer, Node.OfInt, OfInt> {
+            private final int[] array;
+
+            private OfInt(Node.OfInt node, int[] array, int offset) {
+                super(node, offset);
+                this.array = array;
+            }
+
+            private OfInt(OfInt parent, Node.OfInt node, int offset) {
+                super(parent, node, offset);
+                this.array = parent.array;
+            }
+
+            @Override
+            OfInt makeChild(int childIndex, int offset) {
+                return new OfInt(this, node.getChild(childIndex), offset);
+            }
+
+            @Override
+            void copyNodeToArray() {
+                node.copyInto(array, offset);
+            }
+        }
+
+        private static final class OfLong
+                extends ToArrayTask<Long, Node.OfLong, OfLong> {
+            private final long[] array;
+
+            private OfLong(Node.OfLong node, long[] array, int offset) {
+                super(node, offset);
+                this.array = array;
+            }
+
+            private OfLong(OfLong parent, Node.OfLong node, int offset) {
+                super(parent, node, offset);
+                this.array = parent.array;
+            }
+
+            @Override
+            OfLong makeChild(int childIndex, int offset) {
+                return new OfLong(this, node.getChild(childIndex), offset);
+            }
+
+            @Override
+            void copyNodeToArray() {
+                node.copyInto(array, offset);
+            }
+        }
+
+        private static final class OfDouble
+                extends ToArrayTask<Double, Node.OfDouble, OfDouble> {
+            private final double[] array;
+
+            private OfDouble(Node.OfDouble node, double[] array, int offset) {
+                super(node, offset);
+                this.array = array;
+            }
+
+            private OfDouble(OfDouble parent, Node.OfDouble node, int offset) {
+                super(parent, node, offset);
+                this.array = parent.array;
+            }
+
+            @Override
+            OfDouble makeChild(int childIndex, int offset) {
+                return new OfDouble(this, node.getChild(childIndex), offset);
+            }
+
+            @Override
+            void copyNodeToArray() {
+                node.copyInto(array, offset);
+            }
+        }
+    }
+
+    private static final class CollectorTask<P_IN, P_OUT>
+            extends AbstractTask<P_IN, P_OUT, Node<P_OUT>, CollectorTask<P_IN, P_OUT>> {
+        private final PipelineHelper<P_OUT> helper;
+        private final IntFunction<P_OUT[]> generator;
+
+        CollectorTask(PipelineHelper<P_OUT> helper,
+                      IntFunction<P_OUT[]> generator,
+                      Spliterator<P_IN> spliterator) {
+            super(helper, spliterator);
+            this.helper = helper;
+            this.generator = generator;
+        }
+
+        CollectorTask(CollectorTask<P_IN, P_OUT> parent, Spliterator<P_IN> spliterator) {
+            super(parent, spliterator);
+            helper = parent.helper;
+            generator = parent.generator;
+        }
+
+        @Override
+        protected CollectorTask<P_IN, P_OUT> makeChild(Spliterator<P_IN> spliterator) {
+            return new CollectorTask<>(this, spliterator);
+        }
+
+        @Override
+        protected Node<P_OUT> doLeaf() {
+            Node.Builder<P_OUT> builder
+                    = builder(helper.exactOutputSizeIfKnown(spliterator),
+                                    generator);
+            return helper.wrapAndCopyInto(builder, spliterator).build();
+        }
+
+        @Override
+        public void onCompletion(CountedCompleter caller) {
+            if (!isLeaf()) {
+                int numChildren = 0;
+                for (CollectorTask<P_IN, P_OUT> cur = children; cur != null; cur = cur.nextSibling)
+                    ++numChildren;
+                @SuppressWarnings("unchecked")
+                Node<P_OUT>[] nodes = (Node<P_OUT>[]) new Node[numChildren];
+                int idx = 0;
+                for (CollectorTask<P_IN, P_OUT> cur = children; cur != null; cur = cur.nextSibling)
+                    nodes[idx++] = cur.getLocalResult();
+                setLocalResult(new ConcNode<>(nodes));
+            }
+            super.onCompletion(caller);
+        }
+    }
+
+    private static final class IntCollectorTask<P_IN>
+            extends AbstractTask<P_IN, Integer, Node.OfInt, IntCollectorTask<P_IN>> {
+        private final PipelineHelper<Integer> helper;
+
+        IntCollectorTask(PipelineHelper<Integer> helper, Spliterator<P_IN> spliterator) {
+            super(helper, spliterator);
+            this.helper = helper;
+        }
+
+        IntCollectorTask(IntCollectorTask<P_IN> parent, Spliterator<P_IN> spliterator) {
+            super(parent, spliterator);
+            helper = parent.helper;
+        }
+
+        @Override
+        protected IntCollectorTask<P_IN> makeChild(Spliterator<P_IN> spliterator) {
+            return new IntCollectorTask<>(this, spliterator);
+        }
+
+        @Override
+        protected Node.OfInt doLeaf() {
+            Node.Builder.OfInt builder = intBuilder(helper.exactOutputSizeIfKnown(spliterator));
+            return helper.wrapAndCopyInto(builder, spliterator).build();
+        }
+
+        @Override
+        public void onCompletion(CountedCompleter caller) {
+            if (!isLeaf()) {
+                int numChildren = 0;
+                for (IntCollectorTask<P_IN> cur = children; cur != null; cur = cur.nextSibling)
+                    ++numChildren;
+                Node.OfInt[] nodes = new Node.OfInt[numChildren];
+                int idx = 0;
+                for (IntCollectorTask<P_IN> cur = children; cur != null; cur = cur.nextSibling)
+                    nodes[idx++] = cur.getLocalResult();
+
+                setLocalResult(new IntConcNode(nodes));
+            }
+            super.onCompletion(caller);
+        }
+    }
+
+    private static final class LongCollectorTask<P_IN>
+            extends AbstractTask<P_IN, Long, Node.OfLong, LongCollectorTask<P_IN>> {
+        private final PipelineHelper<Long> helper;
+
+        LongCollectorTask(PipelineHelper<Long> helper, Spliterator<P_IN> spliterator) {
+            super(helper, spliterator);
+            this.helper = helper;
+        }
+
+        LongCollectorTask(LongCollectorTask<P_IN> parent, Spliterator<P_IN> spliterator) {
+            super(parent, spliterator);
+            helper = parent.helper;
+        }
+
+        @Override
+        protected LongCollectorTask<P_IN> makeChild(Spliterator<P_IN> spliterator) {
+            return new LongCollectorTask<>(this, spliterator);
+        }
+
+        @Override
+        protected Node.OfLong doLeaf() {
+            Node.Builder.OfLong builder = longBuilder(helper.exactOutputSizeIfKnown(spliterator));
+            return helper.wrapAndCopyInto(builder, spliterator).build();
+        }
+
+        @Override
+        public void onCompletion(CountedCompleter caller) {
+            if (!isLeaf()) {
+                int numChildren = 0;
+                for (LongCollectorTask<P_IN> cur = children; cur != null; cur = cur.nextSibling)
+                    ++numChildren;
+                Node.OfLong[] nodes = new Node.OfLong[numChildren];
+                int idx = 0;
+                for (LongCollectorTask<P_IN> cur = children; cur != null; cur = cur.nextSibling)
+                    nodes[idx++] = cur.getLocalResult();
+
+                setLocalResult(new LongConcNode(nodes));
+            }
+            super.onCompletion(caller);
+        }
+    }
+
+    private static final class DoubleCollectorTask<P_IN>
+            extends AbstractTask<P_IN, Double, Node.OfDouble, DoubleCollectorTask<P_IN>> {
+        private final PipelineHelper<Double> helper;
+
+        DoubleCollectorTask(PipelineHelper<Double> helper, Spliterator<P_IN> spliterator) {
+            super(helper, spliterator);
+            this.helper = helper;
+        }
+
+        DoubleCollectorTask(DoubleCollectorTask<P_IN> parent, Spliterator<P_IN> spliterator) {
+            super(parent, spliterator);
+            helper = parent.helper;
+        }
+
+        @Override
+        protected DoubleCollectorTask<P_IN> makeChild(Spliterator<P_IN> spliterator) {
+            return new DoubleCollectorTask<>(this, spliterator);
+        }
+
+        @Override
+        protected Node.OfDouble doLeaf() {
+            Node.Builder.OfDouble builder
+                    = doubleBuilder(helper.exactOutputSizeIfKnown(spliterator));
+            return helper.wrapAndCopyInto(builder, spliterator).build();
+        }
+
+        @Override
+        public void onCompletion(CountedCompleter caller) {
+            if (!isLeaf()) {
+                int numChildren = 0;
+                for (DoubleCollectorTask<P_IN> cur = children; cur != null; cur = cur.nextSibling)
+                    ++numChildren;
+                Node.OfDouble[] nodes = new Node.OfDouble[numChildren];
+                int idx = 0;
+                for (DoubleCollectorTask<P_IN> cur = children; cur != null; cur = cur.nextSibling)
+                    nodes[idx++] = cur.getLocalResult();
+
+                setLocalResult(new DoubleConcNode(nodes));
+            }
+            super.onCompletion(caller);
+        }
+    }
 }
--- a/src/share/classes/java/util/stream/ReferencePipeline.java	Fri Apr 12 10:12:54 2013 -0700
+++ b/src/share/classes/java/util/stream/ReferencePipeline.java	Fri Apr 12 14:24:35 2013 -0400
@@ -105,7 +105,7 @@
                                         Spliterator<P_IN> spliterator,
                                         boolean flattenTree,
                                         IntFunction<U[]> generator) {
-        return NodeUtils.collect(helper, spliterator, flattenTree, generator);
+        return Nodes.collect(helper, spliterator, flattenTree, generator);
     }
 
     @Override
@@ -410,7 +410,7 @@
         // Runtime checking will be performed when an element is stored in A[], thus if A is not a
         // super type of U an ArrayStoreException will be thrown.
         IntFunction rawGenerator = (IntFunction) generator;
-        return (A[]) NodeUtils.flatten(evaluateToArrayNode(rawGenerator), rawGenerator)
+        return (A[]) Nodes.flatten(evaluateToArrayNode(rawGenerator), rawGenerator)
                               .asArray(rawGenerator);
     }
 
--- a/test-ng/boottests/java/util/stream/DoubleNodeTest.java	Fri Apr 12 10:12:54 2013 -0700
+++ b/test-ng/boottests/java/util/stream/DoubleNodeTest.java	Fri Apr 12 14:24:35 2013 -0400
@@ -104,7 +104,7 @@
 
         double i = it.nextDouble();
         if (it.hasNext()) {
-            return Nodes.conc(Nodes.node(new double[] {i}), degenerateTree(it));
+            return new Nodes.DoubleConcNode(new Node.OfDouble[] { Nodes.node(new double[] {i}), degenerateTree(it) });
         }
         else {
             return Nodes.node(new double[] {i});
@@ -116,7 +116,10 @@
             return m.apply(l);
         }
         else {
-            return Nodes.conc(tree(l.subList(0, l.size() / 2), m), tree(l.subList(l.size() / 2, l.size()), m));
+            return new Nodes.DoubleConcNode(new Node.OfDouble[] {
+                    tree(l.subList(0, l.size() / 2), m),
+                    tree(l.subList(l.size() / 2, l.size()), m)
+            });
         }
     }
 
@@ -127,7 +130,7 @@
 
     @Test(dataProvider = "nodes")
     public void testFlattenAsArray(double[] array, Node.OfDouble n) {
-        assertEquals(NodeUtils.flattenDouble(n).asDoubleArray(), array);
+        assertEquals(Nodes.flattenDouble(n).asDoubleArray(), array);
     }
 
     @Test(dataProvider = "nodes")
--- a/test-ng/boottests/java/util/stream/IntNodeTest.java	Fri Apr 12 10:12:54 2013 -0700
+++ b/test-ng/boottests/java/util/stream/IntNodeTest.java	Fri Apr 12 14:24:35 2013 -0400
@@ -104,7 +104,7 @@
 
         int i = it.nextInt();
         if (it.hasNext()) {
-            return Nodes.conc(Nodes.node(new int[] {i}), degenerateTree(it));
+            return new Nodes.IntConcNode(new Node.OfInt[] { Nodes.node(new int[] {i}), degenerateTree(it) });
         }
         else {
             return Nodes.node(new int[] {i});
@@ -116,7 +116,7 @@
             return m.apply(l);
         }
         else {
-            return Nodes.conc(tree(l.subList(0, l.size() / 2), m), tree(l.subList(l.size() / 2, l.size()), m));
+            return new Nodes.IntConcNode(new Node.OfInt[] { tree(l.subList(0, l.size() / 2), m), tree(l.subList(l.size() / 2, l.size()), m) });
         }
     }
 
@@ -127,7 +127,7 @@
 
     @Test(dataProvider = "nodes")
     public void testFlattenAsArray(int[] array, Node.OfInt n) {
-        assertEquals(NodeUtils.flattenInt(n).asIntArray(), array);
+        assertEquals(Nodes.flattenInt(n).asIntArray(), array);
     }
 
     @Test(dataProvider = "nodes")
--- a/test-ng/boottests/java/util/stream/LongNodeTest.java	Fri Apr 12 10:12:54 2013 -0700
+++ b/test-ng/boottests/java/util/stream/LongNodeTest.java	Fri Apr 12 14:24:35 2013 -0400
@@ -104,7 +104,7 @@
 
         long i = it.nextLong();
         if (it.hasNext()) {
-            return Nodes.conc(Nodes.node(new long[] {i}), degenerateTree(it));
+            return new Nodes.LongConcNode(new Node.OfLong[] { Nodes.node(new long[] {i}), degenerateTree(it) });
         }
         else {
             return Nodes.node(new long[] {i});
@@ -116,7 +116,10 @@
             return m.apply(l);
         }
         else {
-            return Nodes.conc(tree(l.subList(0, l.size() / 2), m), tree(l.subList(l.size() / 2, l.size()), m));
+            return new Nodes.LongConcNode(new Node.OfLong[] {
+                    tree(l.subList(0, l.size() / 2), m),
+                    tree(l.subList(l.size() / 2, l.size()), m)
+            });
         }
     }
 
@@ -127,7 +130,7 @@
 
     @Test(dataProvider = "nodes")
     public void testFlattenAsArray(long[] array, Node.OfLong n) {
-        assertEquals(NodeUtils.flattenLong(n).asLongArray(), array);
+        assertEquals(Nodes.flattenLong(n).asLongArray(), array);
     }
 
     @Test(dataProvider = "nodes")
--- a/test-ng/boottests/java/util/stream/NodeTest.java	Fri Apr 12 10:12:54 2013 -0700
+++ b/test-ng/boottests/java/util/stream/NodeTest.java	Fri Apr 12 14:24:35 2013 -0400
@@ -24,16 +24,16 @@
  */
 package java.util.stream;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.function.Function;
+
 import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
 
-import java.util.*;
-import java.util.function.Function;
-
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertFalse;
-import static org.testng.Assert.assertTrue;
-
 @Test
 public class NodeTest extends OpTestCase {
 
@@ -81,7 +81,7 @@
 
         Integer i = it.next();
         if (it.hasNext()) {
-            return Nodes.node(Nodes.node(new Integer[] {i}), degenerateTree(it));
+            return new Nodes.ConcNode<Integer>(new Node[] { Nodes.node(new Integer[] {i}), degenerateTree(it) });
         }
         else {
             return Nodes.node(new Integer[]{i});
@@ -93,7 +93,10 @@
             return m.apply(l);
         }
         else {
-            return Nodes.node(tree(l.subList(0, l.size() / 2), m), tree(l.subList(l.size() / 2, l.size()), m));
+            return new Nodes.ConcNode<Integer>(new Node[] {
+                    tree(l.subList(0, l.size() / 2), m),
+                    tree(l.subList(l.size() / 2, l.size()), m )
+            });
         }
     }
 
@@ -104,7 +107,7 @@
 
     @Test(dataProvider = "nodes")
     public void testFlattenAsArray(Integer[] array, Node<Integer> n) {
-        assertEquals(NodeUtils.flatten(n, LambdaTestHelpers.integerArrayGenerator).asArray(LambdaTestHelpers.integerArrayGenerator), array);
+        assertEquals(Nodes.flatten(n, LambdaTestHelpers.integerArrayGenerator).asArray(LambdaTestHelpers.integerArrayGenerator), array);
     }
 
     @Test(dataProvider = "nodes")