...
| Code Block |
|---|
import org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation; public class SyncIterator implements Iterator, Iterable{ private theobjects SyncIterator(Object[] objects){ theobjects=objects.collect{ if (it instanceof Iterator) return /*from closure*/ it else return /*from closure*/ DefaultTypeTransformation.asCollection(it).iterator() } } boolean hasNext(){ return theobjects.any{it.hasNext()} } Object next(){ if (!hasNext()) throw new java.util.NoSuchElementException() return theobjects.collect{ try{ return /*from closure*/ it.next() }catch(NoSuchElementException e){ return /*from closure*/ null } } } Iterator iterator(){ return this; } void remove(){ throw new UnsupportedOperationException("remove() not supported") } } |
...
| Code Block |
|---|
//JAVA CODE
import java.util.concurrent.*;
import java.lang.ref.*;
import groovy.lang.Closure;
import java.util.*;
public class Generator<T> implements Iterator<T>, Iterable<T>{
Semaphore availSemaphore=new Semaphore(0);
Semaphore emptySemaphore=new Semaphore(1);
//the thread can push one value at at time into pushedValue
T pushedValue=null;
//pull value moves it from pushedValue to pulledValue
//until it is released by next()
T pulledValue=null;
boolean hasPulledValue=false;
Thread internalThread;
Generator(Closure closure){
internalThread=new GeneratorThread<T>(this,closure);
internalThread.setDaemon(true);
internalThread.start();
}
private void pullValue(){
availSemaphore.acquireUninterruptibly();
pulledValue=pushedValue;
pushedValue=null;
hasPulledValue=true;
emptySemaphore.release();
}
public boolean hasNext(){
if (!hasPulledValue)
pullValue();
return emptySemaphore.availablePermits() != 2;
}
public T next(){
if (!hasNext())
throw new NoSuchElementException("Closure has no more values");
T retval=pulledValue;
hasPulledValue=false;
return retval;
}
public void remove(){
throw new UnsupportedOperationException(
"Remove is not supported on generators");
}
public Iterator<T> iterator(){
return this;
}
public void finalize(){
internalThread.interrupt();
}
static class GeneratorThread<T> extends Thread{
WeakReference<Generator<T>> generatorRef;
Closure closure;
public GeneratorThread(Generator<T> generator, Closure cl){
generatorRef=new WeakReference<Generator<T>>(generator);
closure=cl;
}
public void run(){
closure.call(new SaveClosure<T>(this));
Generator generator=generatorRef.get();
//NOTE: when the closure completes, pullValue() will block forever
//waiting for more available data. This release() allows it to
//get in one last time, and read a variable indicating that the
//thread has died and isn't producing any more data. one final
//pullValue() run will have emptySemaphore==1 and
//availSemaphore==1, and it will make emptySemaphore==2 thus
//indicating that the thread has died
if (generator!=null){
generator.availSemaphore.release();
}
//NOTE: if the generator has been garbage collected, we don't care
//about letting the generator pull a termination condition.
}
}
static class SaveClosure<T> extends Closure{
WeakReference<Generator<T>> generatorRef;
Semaphore emptySemaphore;
Semaphore availSemaphore;
public SaveClosure(GeneratorThread<T> gt){
super(gt,null);
generatorRef=gt.generatorRef;
Generator<T> super(gt,nullgenerator=generatorRef.get();
if (generator!=null){
generatorRef emptySemaphore=gtgenerator.generatorRefemptySemaphore;
availSemaphore=generator.availSemaphore;
}else{
throw new GeneratorDisposedException();
}
}
public void doCall(T value){
try{
emptySemaphore.acquire();
}catch(InterruptedException e){
throw new GeneratorDisposedException();
}
Generator<T> generator=generatorRef.get();
if (generator!=null){
generator.emptySemaphore.acquireUninterruptibly(); generator.pushedValue=value;
generator.availSemaphore.release();
}else{
throw }else{new GeneratorDisposedException();
throw}
new GeneratorDisposedExceptionavailSemaphore.release();
}
}
}
/**
* A GeneratorDisposedException is used to terminate the thread
* that was generating values, once the Generator has been garbage
* collected.
*/
static public class GeneratorDisposedException extends RuntimeException{
}
}
|
Note that Groovy doesn't have this concept built in. Its MethodClosure to Iterator conversion loads everything into an array all at once, which isn't a particularly good idea you may be running find() on it, and only need to generate the first item to find what you're looking for.
...
| Code Block |
|---|
//generates an infinite sequence of Fibonacci numbers
def fibonacci(Closure yield){
def a=0
def b=1
def temp
while(true){
yield(b)
temp=a
a=b
b=a+temp
}
}
//find the first Fibonacci number that's evenly divisible by 20
println(new Generator(this.&fibonacci).find{ it % 20 == 0})
//BROKEN: the groovy runtime wants to run fibonacci to termination loading values into an array.
//this generates an out of memory error.
this this.&fibonacci.find{it % 20 == 0}
|
...