All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sf.saxon.s9api.streams.XdmStream Maven / Gradle / Ivy

There is a newer version: 12.5
Show newest version
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2018-2023 Saxonica Limited
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

package net.sf.saxon.s9api.streams;

import net.sf.saxon.s9api.*;

import java.util.*;
import java.util.function.*;
import java.util.stream.*;

/**
 * XdmStream extends the capabilities of the standard JDK {@link Stream} class.
 *
 * 

The extensions are:

*
    *
  • Additional terminal operations are provided, allowing the results of the * stream to be delivered for example as a List<XdmNode> or * an Optional<XdmNode> more conveniently than using the general-purpose * {@link Collector} interface.
  • *
  • Many of these terminal operations are short-circuiting, that is, they * stop processing input when no further input is required.
  • *
  • The additional terminal operations throw a checked exception if a dynamic * error occurs while generating the content of the stream.
  • *
* *

The implementation is customized to streams of {@link XdmItem}s.

*

Note: This class is implemented by wrapping a base stream. Generally, the * methods on this class delegate to the base stream; those methods that * return a stream wrap the stream returned by the base class. The context object can be used by a terminal * method on the XdmStream to signal to the originator of the stream * that no further input is required.

* * @param The type of items delivered by the stream. */ public class XdmStream implements Stream { Stream base; /** * Create an {@link XdmStream} from a general {@link Stream} that returns XDM items. * @param base the stream of items */ public XdmStream(Stream base) { this.base = base; } /** * Create an {@link XdmStream} consisting of zero or one items, supplied in the form * of an Optional<XdmItem> * * @param input the optional item */ @SuppressWarnings("OptionalUsedAsFieldOrParameterType") public XdmStream(Optional input) { this.base = input.map(Stream::of).orElseGet(Stream::empty); } /** * Filter a stream of items, to create a new stream containing only those items * that satisfy a supplied condition. * *

For example, {@code body.select(child("*")).filter(n -> n.getNodeName().getLocalName().startsWith("h"))} * returns a stream of all the child elements of body whose local name starts with "h".

*

Note: an alternative to filtering a stream is to use a {@link Step} that incorporates * a {@link Predicate}, for example {@code body.select(child("*").where(n -> n.getNodeName().getLocalName().startsWith("h")))}

* @param predicate the supplied condition. Any Predicate can be supplied, * but some particularly useful predicates are available by calling * static methods on the {@link Predicates} class, for example * Predicates.empty(Steps.child("author")), which is true * for a node that has no child elements with local name "author". * @return the filtered stream */ @Override public XdmStream filter(Predicate predicate) { return new XdmStream<>(base.filter(predicate)); } /** * Returns a stream consisting of the results of applying the given * function to the elements of this stream. * *

This is an intermediate * operation. * *

For example, n.select(child(*)).map(c -> c.getNodeName().getLocalName()) * returns a stream delivering the local names of the element children of n, * as instances of java.lang.String. * Note the result is a {@link Stream}, not an {@link XdmStream}.

* * @param The element type of the new stream * @param mapper a non-interfering, * stateless * function to apply to each element * @return the new stream */ @Override public Stream map(Function mapper) { return base.map(mapper); } /** * Returns an {@code IntStream} consisting of the results of applying the * given function to the elements of this stream. * *

For example, n.select(child(*)).map(c -> c.getStringValue().length()) * returns a stream delivering the lengths of the string-values of the element children of n. * Note the result is a {@link Stream}, not an {@link XdmStream}.

* *

This is an * intermediate operation. * * @param mapper a non-interfering, * stateless * function to apply to each element * @return the new stream */ @Override public IntStream mapToInt(ToIntFunction mapper) { return base.mapToInt(mapper); } /** * Returns a {@code LongStream} consisting of the results of applying the * given function to the elements of this stream. * *

This is an intermediate * operation. * * @param mapper a non-interfering, * stateless * function to apply to each element * @return the new stream */ @Override public LongStream mapToLong(ToLongFunction mapper) { return base.mapToLong(mapper); } /** * Returns a {@code DoubleStream} consisting of the results of applying the * given function to the elements of this stream. * *

This is an intermediate * operation. * * @param mapper a non-interfering, * stateless * function to apply to each element * @return the new stream */ @Override public DoubleStream mapToDouble(ToDoubleFunction mapper) { return base.mapToDouble(mapper); } /** * Returns a stream consisting of the results of replacing each element of * this stream with the contents of a mapped stream produced by applying * the provided mapping function to each element. Each mapped stream is * {@link java.util.stream.BaseStream#close() closed} after its contents * have been placed into this stream. (If a mapped stream is {@code null} * an empty stream is used, instead.) * *

This is an intermediate * operation.

* *

Note: The {@code flatMap()} operation has the effect of applying a one-to-many * transformation to the elements of the stream, and then flattening the * resulting elements into a new stream. This corresponds to the action * of the "!" operator in XPath.

* * @param The element type of the new stream * @param mapper a non-interfering, * stateless * function to apply to each element which produces a stream * of new values * @return the new stream */ @Override public Stream flatMap(Function> mapper) { return base.flatMap(mapper); } /** * Create a new {@link XdmStream} by applying a mapping function (specifically, a {@link Step}) * to each item in the stream. The {@link Step} returns a sequence of items, which are inserted * into the result sequence in place of the original item. *

This method is similar to {@link #flatMap}, but differs in that it returns an {@link XdmStream}, * making additional methods available.

*

Note: {@link XdmValue#select} is implemented using this method, and in practice it is * usually clearer to use that method directly. For example {@code node.select(child("*")).flatMapToXdm(child(*))} * can be written as {@code node.select(child("*").then(child("*"))}. Both expressions return a stream containing * all the grandchildren elements of {@code node}. The same result can be achieved more concisely by writing * {@code node.select(path("*", "*"))}

* @param mapper the mapping function * @param the type of items returned by the mapping function * @return a new stream of items */ public XdmStream flatMapToXdm(Step mapper) { return new XdmStream<>(base.flatMap(mapper)); } /** * Returns an {@code IntStream} consisting of the results of replacing each * element of this stream with the contents of a mapped stream produced by * applying the provided mapping function to each element. Each mapped * stream is {@link java.util.stream.BaseStream#close() closed} after its * contents have been placed into this stream. (If a mapped stream is * {@code null} an empty stream is used, instead.) * *

This is an intermediate * operation. * * @param mapper a non-interfering, * stateless * function to apply to each element which produces a stream * of new values * @return the new stream * @see #flatMap(Function) */ @Override public IntStream flatMapToInt(Function mapper) { return base.flatMapToInt(mapper); } /** * Returns an {@code LongStream} consisting of the results of replacing each * element of this stream with the contents of a mapped stream produced by * applying the provided mapping function to each element. Each mapped * stream is {@link java.util.stream.BaseStream#close() closed} after its * contents have been placed into this stream. (If a mapped stream is * {@code null} an empty stream is used, instead.) * *

This is an intermediate * operation. * * @param mapper a non-interfering, * stateless * function to apply to each element which produces a stream * of new values * @return the new stream * @see #flatMap(Function) */ @Override public LongStream flatMapToLong(Function mapper) { return base.flatMapToLong(mapper); } /** * Returns an {@code DoubleStream} consisting of the results of replacing * each element of this stream with the contents of a mapped stream produced * by applying the provided mapping function to each element. Each mapped * stream is {@link java.util.stream.BaseStream#close() closed} after its * contents have placed been into this stream. (If a mapped stream is * {@code null} an empty stream is used, instead.) * *

This is an intermediate * operation. * * @param mapper a non-interfering, * stateless * function to apply to each element which produces a stream * of new values * @return the new stream * @see #flatMap(Function) */ @Override public DoubleStream flatMapToDouble(Function mapper) { return base.flatMapToDouble(mapper); } /** * Returns a stream consisting of the distinct items present in this stream. Items are compared * using {@link XdmItem#equals}. This means that two XdmNode objects * are compared by node identity (so two separate nodes are distinct even if they have the same * name and the same content). * @return the new stream, obtained by applying {@link Stream#distinct()} to the underlying stream */ @Override public XdmStream distinct() { return new XdmStream<>(base.distinct()); } /** * Returns a stream consisting of the elements of this stream, in sorted order. *

Note, this method is unlikely to be useful, because most XdmItem values do * not implement {@link Comparable}.

* @return the new stream, obtained by applying {@link Stream#sorted()} to the underlying stream */ @Override public XdmStream sorted() { return new XdmStream<>(base.sorted()); } /** * Returns a stream consisting of the elements of this stream, in sorted order using a supplied {@link Comparator}. * @return the new stream, obtained by applying {@link Stream#sorted(Comparator)} to the underlying stream */ @Override public XdmStream sorted(Comparator comparator) { return new XdmStream<>(base.sorted(comparator)); } /** * Returns the supplied stream, while invoking a supplied action on each element of the stream as it is * processed. *

This method is designed primarily for debugging, to allow the contents of a stream to be monitored.

* @param action the (non-interfering) action to be performed on each element of the stream * @return the supplied stream, unchanged. */ @Override public XdmStream peek(Consumer action) { return new XdmStream<>(base.peek(action)); } /** * Returns a stream containing the initial items of this stream, up to a maximum size * @param maxSize the maximum number of items to be included in the returned stream * @return a stream containing the initial items of this stream, up to the specified limit */ @Override public XdmStream limit(long maxSize) { return new XdmStream<>(base.limit(maxSize)); } /** * Returns a stream containing the items of this stream after skipping a specified * number of items. * @param n the number of items at the start of the stream to be omitted from the result stream * @return the result stream, comprising the input stream except for its first n items. */ @Override public XdmStream skip(long n) { return new XdmStream<>(base.skip(n)); } /** * Performs a given action once for each item of the stream, in non-deterministic order * @param action the action to be performed on each item */ @Override public void forEach(Consumer action) { base.forEach(action); } /** * Performs a given action once for each item of the stream, in the order in which the items appear * @param action the action to be performed on each item */ @Override public void forEachOrdered(Consumer action) { base.forEachOrdered(action); } /** * Returns an array containing the items in this stream * @return an array containing all the items in the stream */ @Override public Object[] toArray() { return base.toArray(); } /** * Returns an array containing the items in this stream, using a supplied function * to generate the array; this allows the type of the returned array to be controlled. * @param generator a function that takes an integer as argument and returns an array * of the given length * @return an array containing all the items in the stream; the type of the array is determined * by the generator function */ @Override public A[] toArray(IntFunction generator) { return base.toArray(generator); } /** * Performs a reduction or fold operation on the items in the stream. *

For example, given a sequence of elements of the form {@code } * the accumulated rise over a number of years may be computed as

*
{@code
     *    changes.stream().select(attribute("rise"))
     *      .map(a->a.getTypedValue().getDoubleValue())
     *      .reduce(1, (x, y) -> x*y)
     * }
* @param identity an initial value of an accumulator variable. This must be an identity value * for the supplied accumulator function (so if F is the accumulator function, * F(identity, V) returns V for any value of V). * @param accumulator the accumulator function: this takes the old value of the accumulator variable * and the current item from the stream as arguments, and returns a * new value for the accumulator variable. This function must be associative, that is, * F(A, F(B, C)) must always give the same result as F(F(A, B), C)) * @return the final value of the accumulator variable after all items have been processed. */ @Override public T reduce(T identity, BinaryOperator accumulator) { return base.reduce(identity, accumulator); } /** * Calls {@link Stream#reduce(BinaryOperator) on the underlying stream} * @param accumulator an associative, stateless, non-interfering value operating * on two items in the stream * @return the result of the delegated call */ @Override public Optional reduce(BinaryOperator accumulator) { return base.reduce(accumulator); } @Override public U reduce(U identity, BiFunction accumulator, BinaryOperator combiner) { return base.reduce(identity, accumulator, combiner); } @Override public R collect(Supplier supplier, BiConsumer accumulator, BiConsumer combiner) { return base.collect(supplier, accumulator, combiner); } @Override public R collect(Collector collector) { return base.collect(collector); } /** * Returns the minimum item in the stream of items, comparing them using the supplied {@link Comparator}. * @param comparator the comparator to be used for comparing items * @return the minimum value, or {@link Optional#empty()} if the stream is empty. */ @Override public Optional min(Comparator comparator) { return base.min(comparator); } /** * Returns the maximum item in the stream of items, comparing them using the supplied {@link Comparator}. * * @param comparator the comparator to be used for comparing items * @return the maximum value, or {@link Optional#empty()} if the stream is empty. */ @Override public Optional max(Comparator comparator) { return base.max(comparator); } /** * Returns the number of items in the stream * @return the length of the stream */ @Override public long count() { return base.count(); } /** * Returns true if any item in the stream matches a supplied predicate * @param predicate the predicate to be applied to each item * @return true if an item is found for which the predicate function returns true */ @Override public boolean anyMatch(Predicate predicate) { return base.anyMatch(predicate); } /** * Returns true if every item in the stream matches a supplied predicate * * @param predicate the predicate to be applied to each item * @return true if the predicate function returns true for every item in the stream */ @Override public boolean allMatch(Predicate predicate) { return base.allMatch(predicate); } /** * Returns true if no item in the stream matches a supplied predicate * * @param predicate the predicate to be applied to each item * @return true if the predicate function returns false for every item in the stream */ @Override public boolean noneMatch(Predicate predicate) { return base.noneMatch(predicate); } /** * Returns the first item in the stream, or {@link Optional#empty()} if the stream is empty * @return the first item in the stream */ @Override public Optional findFirst() { return base.findFirst(); } /** * Returns an item in the stream, chosen arbitrarily, or {@link Optional#empty()} if the stream is empty * * @return an arbitrary item from the stream */ @Override public Optional findAny() { return base.findAny(); } /** * Get an iterator over the items in the stream * @return an iterator over all the items, in order */ @Override public Iterator iterator() { return base.iterator(); } /** * Get a Spliterator over the items in the stream * * @return a Spliterator over all the items, in order */ @Override public Spliterator spliterator() { return base.spliterator(); } /** * Ask whether this stream will be evaluated in parallel * @return true if execution is in parallel */ @Override public boolean isParallel() { return base.isParallel(); } /** * Returns an equivalent stream that will be evaluated sequentially * * @return an equivalent sequential stream */ @Override public Stream sequential() { return new XdmStream<>(base.sequential()); } /** * Returns an equivalent stream that will (where possible and appropriate) be evaluated in parallel * * @return an equivalent parallel stream */ @Override public Stream parallel() { return new XdmStream<>(base.parallel()); } /** * Returns an equivalent stream that offers no guarantee of retaining the order of items * @return an equivalent stream without guaranteed order */ @Override public Stream unordered() { return new XdmStream<>(base.unordered()); } /** * Returns an equivalent stream with a specified handler to be called when the stream is exhausted * @param closeHandler the code to be executed when the stream is closed * @return a stream that returns the same items, augmented with a close handler */ @Override public Stream onClose(Runnable closeHandler) { return new XdmStream<>(base.onClose(closeHandler)); } /** * Close the stream */ @Override public void close() { base.close(); } /** * Return the contents of the stream as an XdmValue. This is a terminal operation. * @return the contents of the stream, as an XdmValue. */ public XdmValue asXdmValue() { return base.collect(XdmCollectors.asXdmValue()); } /** * Return the contents of the stream as a List<XdmItem>. This is a terminal operation. * @return the contents of the stream, as a List<XdmItem>. */ public List asList() { return base.collect(Collectors.toList()); } /** * Return the result of the stream as a {@code List}. This is a terminal operation. *

Node: the method makes it convenient to process the contents of a stream using a for-each * loop, for example:

*
{@code
     *     for (XdmNode n : start.select(child().where(isText())).asList()) {
     *          process(n)
     *     }
     * }
*

A more idiomatic style, however, is to use the {@link #forEach} method:

*

{@code start.select(child().where(isText())).forEach(n -> process(n))}

* @return the list of nodes delivered by the stream * @throws ClassCastException if the stream contains an item that is not a node */ public List asListOfNodes() { return base.collect(XdmCollectors.asListOfNodes()); } /** * Return the result of the stream as an Optional<XdmNode>. This is a terminal operation. * @return the single node delivered by the stream, or absent if the stream is empty * @throws XdmCollectors.MultipleItemException if the stream contains more than one node * @throws ClassCastException if the stream contains an item that is not a node */ public Optional asOptionalNode() { return base.collect(XdmCollectors.asOptionalNode()); } /** * Return the result of the stream as an {@link XdmNode}. This is a terminal operation. * @return the single node delivered by the stream * @throws ClassCastException if the stream contains an item that is not a node * @throws XdmCollectors.MultipleItemException if the stream contains * more than one item * @throws NoSuchElementException if the stream is empty */ public XdmNode asNode() { return base.collect(XdmCollectors.asNode()); } /** * Return the result of the stream as a List<XdmAtomicValue>. This is a terminal operation. * * @return the list of atomic values delivered by the stream * @throws ClassCastException if the stream contains an item that is not an atomic value */ public List asListOfAtomic() { return base.collect(XdmCollectors.asListOfAtomic()); } /** * Return the result of the stream as an Optional<XdmAtomicValue>. This is a terminal operation. * * @return the string value of the single item delivered by the stream, or absent if the stream is empty * @throws XdmCollectors.MultipleItemException if the stream contains more than one item * @throws ClassCastException if the stream contains an item that is not an atomic value */ public Optional asOptionalAtomic() { return base.collect(XdmCollectors.asOptionalAtomic()); } /** * Return the result of the stream as an {@link XdmAtomicValue}. This is a terminal operation. * * @return the string value of the single item delivered by the stream, or a zero-length string * if the stream is empty * @throws ClassCastException if the stream contains an item that is not atomic * @throws XdmCollectors.MultipleItemException if the stream contains more than one item * @throws NoSuchElementException if the stream is empty */ public XdmAtomicValue asAtomic() { return base.collect(XdmCollectors.asAtomic()); } /** * Return the result of the stream as an Optional<String>. This is a terminal operation. * * @return the string value of the single item delivered by the stream, or absent if the stream is empty * @throws XdmCollectors.MultipleItemException if the stream contains more than one item * @throws UnsupportedOperationException if the stream contains an item that has no string value, * for example a function item */ public Optional asOptionalString() { return base.collect(XdmCollectors.asOptionalString()); } /** * Return the result of the stream as an {@link String}. This is a terminal operation. * * @return the string value of the single item delivered by the stream * @throws UnsupportedOperationException if the stream contains an item that has no string value, * for example a function item * @throws XdmCollectors.MultipleItemException if the stream contains more than one item * @throws NoSuchElementException if the stream is empty */ public String asString() { return base.collect(XdmCollectors.asString()); } /** * Return the first item of this stream, if there is one, discarding the remainder. * This is a short-circuiting operation similar to {@link #findFirst}, but it returns * XdmStream<T> rather than Optional<T> so that further operations such * as {@code atomize()} can be applied, and so that a typed result can be returned * using a method such as {@link #asOptionalNode()} or {@link #asOptionalString()} * @return a stream containing the first item in this stream */ public XdmStream first() { Optional result = base.findFirst(); return new XdmStream<>(result); } /** * Return the first item of this stream, if there is one, discarding the remainder; * return null if the stream is empty. * This is a short-circuiting operation similar to {@link #findFirst} * @return the first item in this stream, or null if the stream is empty. */ public T firstItem() { return base.findFirst().orElse(null); } /** * Return true if the stream is non-empty. * This is a short-circuiting terminal operation. * @return true if at least one item is present in the stream. */ public boolean exists() { Optional result = base.findFirst(); return result.isPresent(); } /** * Return the last item of this stream, if there is one, discarding the remainder. * This is a short-circuiting operation similar to {@link #first}; it returns * XdmStream<T> rather than Optional<T> so that further operations such * {@code atomize()} can be applied, and so that a typed result can be returned * using a method such as {@link #asOptionalNode()} or {@link #asOptionalString()} * @return a stream containing only the last item in the stream, or an empty stream * if the input is empty. */ public XdmStream last() { Optional result = base.reduce((first, second) -> second); return new XdmStream<>(result); } /** * Return the last item of this stream, if there is one, discarding the remainder; * return null if the stream is empty. * This is a short-circuiting operation similar to {@link #lastItem()}. * * @return the last item in the stream, or null if the input is empty. */ public T lastItem() { return base.reduce((first, second) -> second).orElse(null); } /** * Return the item at a given position in the stream. This is a short-circuiting terminal operation. * @param position the required position; items in the stream are numbered from zero. * @return the item at the given position if there is one; otherwise, Optional.empty() */ public Optional at(int position) { return base.skip(position).findFirst(); } /** * Return the items at a given range of positions in the stream. For example, subStream(0, 3) returns * the first three items in the stream. * This is a short-circuiting terminal operation. * * @param start the position of the first required item; items in the stream are numbered from zero. * @param end the position immediately after the last required item. * @return a stream containing those items whose zero-based position is greater-than-or-equal-to start, and * less-than end. No error occurs if either start or end is out of range, or if end is less than start. */ public XdmStream subStream(int start, int end) { if (start < 0) { start = 0; } if (end <= start) { return new XdmStream<>(Stream.empty()); } return new XdmStream<>(base.skip(start).limit(end - start)); } /** * Experimental method to return the content of a stream up to the first item * that satisfies a given predicate, including that item * @param predicate a condition that determines when the stream should stop * @return a stream containing all items in the base stream up to and including * the first item that satisfies a given predicate. */ public XdmStream untilFirstInclusive(Predicate predicate) { Stream stoppable = base.peek(item -> { if (predicate.test(item)) { base.close(); } }); return new XdmStream<>(stoppable); } /** * Experimental method to return the content of a stream up to the first item * that satisfies a given predicate, excluding that item * * @param predicate a condition that determines when the stream should stop * @return a stream containing all items in the base stream up to the item immediately before * the first item that satisfies a given predicate. */ public XdmStream untilFirstExclusive(Predicate predicate) { Stream stoppable = base.peek(item -> { if (predicate.test(item)) { base.close(); } }); return new XdmStream<>(stoppable.filter(predicate.negate())); } }