groovy-stream is a library to simplify the generation of lazy Iterators across a mutitude of input data types, and the construction of finite Lazy Generators from a collection of inputs.

An instance of groovy.stream.Stream is an immutable wrapper around a chain of iterators.

Each iterator in the chain takes the previous iterator’s output as it’s input, performs some task to it and passes this to the next Iterator on request.

The head iterator of the chain is wrapped in a Stream class which allows simple creation of new iterators in the chain.

When a Stream method is called to add a new step to the Stream, a new Stream is created containing the specified iterator which in turn is set to get its input from the head iterator of the previous Stream.

It should be noted that Stream (as with most if not all Iterators) is not thread-safe in itself, and any synchronization on reads should be done by the user should multiple read threads be required (including threads reading from intermediate Streams).

Getting started

Using Maven

<dependency>
    <groupId>com.bloidonia</groupId>
    <artifactId>groovy-stream</artifactId>
    <version>0.8.0</version>
</dependency>

Using Gradle

compile "com.bloidonia:groovy-stream:0.8.0"

Using Grab

@Grab( "com.bloidonia:groovy-stream:0.8.0" )
import groovy.stream.*

Downloading

Jar files can be directly downloaded from the Files tab on Bintray by following this link, and clicking on the version you require.

Dependencies

The only runtime dependency of groovy-stream is Groovy. I track the current Groovy version, but it should work with all versions from 2.0+.

Quick Example

As a simple example, lets create a Stream representing all positive integers.

Firstly, we need to import the Stream class:

import groovy.stream.Stream

Then, we can call the Closure form of the static Stream.from method to generate our infinite Stream:

def x = 1
def integers = Stream.from { x++ }

We can then create a Stream that is fed from integers and returns a Stream of squares:

def squares = Stream.from integers map { it * it }

And we can then take the first 5 elements from this stream with:

def first5 = squares.take( 5 ).collect()
assert first5 == [ 1, 4, 9, 16, 25 ]

It is only at the point where we call collect() that the original integers stream is executed five times, to produce five values which are squared in turn and passed to the result first5.

Lazy Generators

We can use the Stream to mimic the behaviour of a list comprehension. Lets say we want to do the following:

For all values of x from 1 to 5 and all values of y from 1 to 3; return x + y if ( x + y ) % ( x + 2 ) == 0

def s = Stream.from( x:1..5, y:1..3 )
              .filter { ( x + y ) % ( x + 2 ) == 0 }
              .map { x + y }

// Returns results for:
//  3 - when - x:1, y:2 as (1+2)%(1+2) == 0
//  4 - when - x:2, y:2 as (2+2)%(2+2) == 0
//  5 - when - x:3, y:2 as (3+2)%(3+2) == 0
//  6 - when - x:4, y:2 as (4+2)%(4+2) == 0
//  7 - when - x:5, y:2 as (5+2)%(5+2) == 0
assert s.collect() == [ 3, 4, 5, 6, 7 ]

Anatomy of a Stream

Inputs

To create a Stream class you can either use one of the many static from methods:

    public static <K,V> Stream<Map<K,V>>  from( Map<K,? extends Iterable<V>> map )
    public static <T>   Stream<T>         from( Stream<T> stream                 )
    public static <T>   Stream<T>         from( Iterable<T> iterable             )
    public static <T>   Stream<T>         from( Iterator<T> iterator             )
    public static       Stream<String>    from( BufferedReader reader            )
    public static <T>   Stream<T>         from( Closure<T> closure               )
    public static <T>   Stream<T>         from( T[] array                        )
    public static       Stream<Byte>      from( byte[] array                     )
    public static       Stream<Character> from( char[] array                     )
    public static       Stream<Short>     from( short[] array                    )
    public static       Stream<Integer>   from( int[] array                      )
    public static       Stream<Long>      from( long[] array                     )
    public static       Stream<Float>     from( float[] array                    )
    public static       Stream<Double>    from( double[] array                   )
    public static       Stream<Boolean>   from( boolean[] array                  )
    public static       Stream<ZipEntry>  from( ZipFile file                     )
    public static       Stream<JarEntry>  from( JarFile file                     )

Or, you can make use of the Groovy Extension class which adds the toStream() method to:

  • Closure

  • Iterator

  • Iterable<T>

  • BufferedReader

  • Map<K,? extends Iterable<V>>

  • Object[]

  • byte[]

  • char[]

  • short[]

  • int[]

  • long[]

  • float[]

  • double[]

  • boolean[]

  • ZipFile

  • JarFile

Once you have this single Iterator Stream, you can then perform different operations to create a new filtered, transformed, limited or conjoined Stream

Filters

TBD

Mappings

TBD

Inspectors

TBD

Java integration

As of version 0.8.0, you can use Java friendly versions of all of the above Closure taking methods to access groovy-stream functionality from native Java.