groovy.stream.Stream.groovy Maven / Gradle / Ivy
/*
* Copyright 2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package groovy.stream
/**
* The Stream class is the entry point for Lazy sequence generation.
*
* A Stream may be constructed with either an Iterator:
*
*
* List a = [ 1, 2, 3 ]
* Stream.from a.iterator()
*
*
* or an Iterable:
*
*
* def a = 1..4
* Stream.from a
*
*
* or a closure
*
*
* Stream.from { 1 } // Infinite Stream!
*
*
* or finally, a Map of Iterables:
*
*
* Stream.from x:1..4, y:'a'..'z'
*
*
* Any of these return a Stream which is a implementation of Iterator, and so
* may be treated as such:
*
*
* def stream = Stream.from 1..3
* assert [ 1, 2, 3 ] == stream.collect()
*
* stream = Stream.from x:1..3, y:'a'..'b'
* assert [ [x:1,y:'a'], [x:1,y:'b'],
* [x:2,y:'a'], [x:2,y:'b'],
* [x:3,y:'a'], [x:3,y:'b'] ] == stream.collect()
*
*
* Streams may be filtered using the filter
method which takes a Closure
* returning Groovy Truth:
*
*
* def stream = Stream.from x:1..3, y:'a'..'b' filter { x % 2 == 0 }
* assert [ [x:2,y:'a'], [x:2,y:'b'] ] == stream.collect()
*
*
* It is possible to use the where block to stop a Stream by returning the
* magic variable STOP
:
*
*
* Stream s = Stream.from { 1 } filter { idx < 5 ?: STOP } map { idx++ ; it } using idx:0
* // The length of this is 1 greater than expected as transform is executed
* // (and so idx is updated) AFTER the where condition has already passed the
* // last element.
* assert s.collect().size() == 6
*
*
* It is also possible to transform the returned values by using a map
Closure:
*
*
* def stream = Stream.from x:1..3, y:'a'..'b' map { [ x:x*2, y:"letter $y" ] }
* assert [ [x:2,y:'letter a'], [x:2,y:'letter b'],
* [x:4,y:'letter a'], [x:4,y:'letter b'],
* [x:6,y:'letter a'], [x:6,y:'letter b'] ] == stream.collect()
*
*
* Finally, it is possible to supply a map of variables to the using
Closure
* which may then be used in any of the components of the Stream setup (though
* may only be modified in the map
closure above):
*
*
* Stream s = Stream.from 'a'..'c' map { [ idx++, it ] } using idx:0
* assert s.collect() == [ [0,'a'], [1,'b'], [2,'c'] ]
*
*
* Streams may only have one using
, map
, or
* where
block. Calling these methods multiple times with result
* in the original blocks getting overwritten by the new ones.
*
* @author Tim Yates
*/
public class Stream implements StreamInterface {
private static enum StreamType { MAP, OTHER }
private StreamInterface wrapped
private StreamType type
/**
* @see java.util.Iterator#hasNext()
*/
public boolean hasNext() { wrapped.hasNext() }
/**
* @see java.util.Iterator#next()
*/
public T next() { wrapped.next() }
/**
* Unavailable, this will throw an UnsupportedOperationException
* @see java.util.Iterator#remove()
* @see java.lang.UnsupportedOperationException
*/
public void remove() { wrapped.remove() }
/**
* Has the Stream been completely exhausted?
* @return true if the Stream is at an end
*/
public boolean isExhausted() { wrapped.exhausted }
/**
* Get the current index of the Stream (starting from 0 for the first element)
* @return the current index
*/
public int getStreamIndex() { wrapped.streamIndex }
/**
* The starting point for a Stream taking a Map of Iterables to
* lazily return. The Stream will return all combinations of this map,
* incrementing the right-hand Map entry first (until exhausted) then
* advancing the next entry and starting again (until all Iterables are
* exhausted)
*
* @param iterables The Map of Iterables
* @return A Stream that will iterate over the iterables, with
* filter
set to {true}
,
* map
set to {it}
and
* using
set to the empty Map.
*/
public static Stream from( Map iterables ) {
new Stream( type:StreamType.MAP, wrapped:new MapStream( { iterables }, { true }, { it }, [:] ) )
}
/**
* The starting point for a Stream taking a Closure. The result of
* calling the Closure will be returned for each iteration. This may
* result in an Infinite Stream which can be stopped by returning STOP from
* a additional where clause (see example in main documentation for Stream)
*
* @param closure The Closure to call for each returned element
* @return A Stream that will iterate and return the result of Closure.call()
* each step, with filter
set to {true}
,
* map
set to {it}
and
* using
set to the empty Map.
*/
public static Stream from( Closure closure ) {
new Stream( type:StreamType.OTHER, wrapped:new StreamImpl( closure, { true }, { it }, [:] ) )
}
/**
* The starting point for a Stream taking an Iterator or Iterable object.
*
* @param other The Iterable or Iterator to use for the Stream
* @return A Stream that will iterate over the passed object, with
* filter
set to {true}
,
* map
set to {it}
and
* using
set to the empty Map.
*/
public static Stream from( other ) {
new Stream( type:StreamType.OTHER, wrapped:new StreamImpl( { other }, { true }, { it }, [:] ) )
}
/**
* The starting point for a Stream taking an array. This will make a copy of the array
* (into a new List instance), and then generate the Stream from this new List.
*
* @param array The The array to use for the stream
* @return A Stream that will iterate over the passed array, with
* filter
set to {true}
,
* map
set to {it}
and
* using
set to the empty Map.
*/
@SuppressWarnings(["unchecked", "varargs"])
public static Stream from( T[] array ) { fromArray( array ) }
public static Stream from( byte[] array ) { fromArray( array ) }
public static Stream from( char[] array ) { fromArray( array ) }
public static Stream from( short[] array ) { fromArray( array ) }
public static Stream from( int[] array ) { fromArray( array ) }
public static Stream from( long[] array ) { fromArray( array ) }
public static Stream from( float[] array ) { fromArray( array ) }
public static Stream from( double[] array ) { fromArray( array ) }
public static Stream from( boolean[] array ) { fromArray( array ) }
private static Stream fromArray( array ) {
new Stream( type:StreamType.OTHER, wrapped:new StreamImpl( { array.toList() }, { true }, { it }, [:] ) )
}
/**
* A basic filter for the Stream. Elements in the Stream are passed to this
* Closure, and only those that cause this closure to return something
* passing Groovy Truth will be returned from the Stream. For Map based
* Streams, the current map is set as the Delegate for the closure, so the
* keys may be accessed directly, but (as with non-map based streams), it is
* also passed as a parameter to the closure so the map values may be
* accessed where they are hidden by variables in the static scope.
*
* @param predicate The closure to filter the Stream
* @return A new Stream with this filter assigned.
*/
public Stream filter( Closure predicate ) {
wrapped = type == StreamType.MAP ?
new MapStream( wrapped.definition, predicate, wrapped.transform, wrapped.using ) :
new StreamImpl( wrapped.definition, predicate, wrapped.transform, wrapped.using )
this
}
/**
* Set the default transformation for this Stream. Values are passed
* through this closure before being returned. It is also possible to
* alter the values in the using
Map from within the closure.
* For Map based Streams, the current map is set as the Delegate for the
* closure, so the keys may be accessed directly, but (as with non-map based
* streams), it is also passed as a parameter to the closure so the map
* values may be accessed where they are hidden by variables in the static
* scope.
*
* @param transform The closure to manipulate the current stream value.
* @return A new Stream with this transform assigned.
*/
public Stream map( Closure transform ) {
wrapped = type == StreamType.MAP ?
new MapStream( wrapped.definition, wrapped.condition, transform, wrapped.using ) :
new StreamImpl( wrapped.definition, wrapped.condition, transform, wrapped.using )
this
}
/**
* Set the map of values that is accessible to the component parts of the
* Stream. This map is passed to all of the other Closures used by the Stream
* and may be modified by any transform Closure that has been set. When
* using a Stream over a Map of values, these values will overwrite any
* values of the initial map with the same key.
*
* @param using The map of values.
* @return A new Stream with this using Map assigned.
*/
public Stream using( Map using ) {
wrapped = type == StreamType.MAP ?
new MapStream( wrapped.definition, wrapped.condition, wrapped.transform, using ) :
new StreamImpl( wrapped.definition, wrapped.condition, wrapped.transform, using )
this
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy