Created
March 3, 2014 14:36
-
-
Save komiya-atsushi/9326277 to your computer and use it in GitHub Desktop.
Java で yield return ぽいことを実現しよう! ref: http://qiita.com/komiya-atsushi@github/items/3f1145eb1964efef2f50
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.util.Iterator; | |
import java.util.NoSuchElementException; | |
import java.util.concurrent.ArrayBlockingQueue; | |
import java.util.concurrent.BlockingQueue; | |
import java.util.concurrent.TimeUnit; | |
/** | |
* Java で yield return っぽいことを実現する機能を提供します。 | |
* | |
* 以下は使い方の一例です。 | |
* <pre> | |
* class YielderDemo { | |
* public static void main(String[] args) { | |
* for (Integer integer : Yielder.newIterable(new Yielder<Integer>() { | |
* public void run() { | |
* for (int i = 0; i < 10; i++) { | |
* yield(i); | |
* } | |
* for (int i = 30; i < 50; i++) { | |
* yield(i); | |
* } | |
* } | |
* })) { | |
* System.out.println(integer); | |
* } | |
* } | |
* } | |
* </pre> | |
* | |
* @author KOMIYA Atsushi | |
*/ | |
public abstract class Yielder<T> { | |
private static class Item<T> { | |
T object; | |
boolean ended = false; | |
private Item<T> set(T object) { | |
this.object = object; | |
return this; | |
} | |
private Item<T> end() { | |
this.ended = true; | |
return this; | |
} | |
} | |
private static class Context<T> { | |
private final BlockingQueue<Item<T>> queue; | |
private final Item<T>[] items; | |
private int index; | |
private boolean endReceived; | |
private boolean ended; | |
private Exception thrownException; | |
Context(int queueSize) { | |
queue = new ArrayBlockingQueue<>(queueSize); | |
items = new Item[queueSize + 2]; | |
for (int i = 0; i < items.length; i++) { | |
items[i] = new Item<>(); | |
} | |
} | |
synchronized boolean yield(T returnValue) { | |
if (endReceived) { | |
throw new IllegalStateException("#yieldEnd() has been called."); | |
} | |
try { | |
Item<T> item = items[index++].set(returnValue); | |
while (!queue.offer(item, 1000, TimeUnit.SECONDS)) { | |
if (ended) { | |
throw new IllegalStateException("iteration has been ended."); | |
} | |
} | |
if (index == items.length) { | |
index = 0; | |
} | |
return true; | |
} catch (Exception e) { | |
thrownException = e; | |
return false; | |
} | |
} | |
synchronized boolean yieldEnd() { | |
if (endReceived) { | |
throw new IllegalStateException("#yieldEnd() has been called."); | |
} | |
endReceived = true; | |
try { | |
Item<T> item = items[index++].end(); | |
while (!queue.offer(item, 1000, TimeUnit.SECONDS)) { | |
if (ended) { | |
throw new IllegalStateException("iteration has been ended."); | |
} | |
} | |
if (index == items.length) { | |
index = 0; | |
} | |
return true; | |
} catch (InterruptedException e) { | |
thrownException = e; | |
return false; | |
} | |
} | |
synchronized Exception thrownException() { | |
return thrownException; | |
} | |
Item<T> pop() throws InterruptedException { | |
Item<T> result = queue.take(); | |
if (result.ended) { | |
this.ended = true; | |
} | |
return result; | |
} | |
} | |
public static class YielderIterable<T> implements Iterable<T>, AutoCloseable { | |
private final Context<T> context; | |
private final Thread thread; | |
private boolean iteratorCalled; | |
private boolean closed; | |
private YielderIterable(int queueSize, Yielder<T> yielder) { | |
this.context = new Context<>(queueSize); | |
this.thread = new Thread() { | |
@Override | |
public void run() { | |
try { | |
yielder.context.set(YielderIterable.this.context); | |
yielder.run(); | |
context.yieldEnd(); | |
} catch (Exception e) { | |
e.printStackTrace(); | |
} finally { | |
yielder.context.remove(); | |
} | |
} | |
}; | |
this.thread.start(); | |
} | |
@Override | |
public Iterator<T> iterator() { | |
if (iteratorCalled) { | |
throw new IllegalStateException("#iterator() has been called"); | |
} | |
iteratorCalled = true; | |
return new Iterator<T>() { | |
Item<T> nextItem; | |
@Override | |
public boolean hasNext() { | |
if (nextItem != null) { | |
return !nextItem.ended; | |
} | |
try { | |
nextItem = context.pop(); | |
} catch (InterruptedException e) { | |
throw new IllegalStateException(e); | |
} | |
return !nextItem.ended; | |
} | |
@Override | |
public T next() { | |
if (!hasNext()) { | |
throw new NoSuchElementException(); | |
} | |
Item<T> result = nextItem; | |
nextItem = null; | |
return result.object; | |
} | |
}; | |
} | |
@Override | |
public void close() throws Exception { | |
if (closed) { | |
return; | |
} | |
closed = true; | |
context.endReceived = true; | |
context.ended = true; | |
thread.interrupt(); | |
} | |
} | |
private static final int DEFAULT_QUEUE_SIZE = 1; | |
private ThreadLocal<Context<T>> context = new ThreadLocal<>(); | |
public static <T> Iterable<T> newIterable(final Yielder<T> yielder) { | |
return new YielderIterable<>(DEFAULT_QUEUE_SIZE, yielder); | |
} | |
protected boolean yield(T returnValue) { | |
return context.get().yield(returnValue); | |
} | |
protected Exception thrownException() { | |
return context.get().thrownException(); | |
} | |
public abstract void run(); | |
} | |
class YielderDemo { | |
public static void main(String[] args) { | |
for (Integer integer : Yielder.newIterable(new Yielder<Integer>() { | |
@Override | |
public void run() { | |
for (int i = 0; i < 10; i++) { | |
yield(i); | |
} | |
for (int i = 30; i < 50; i++) { | |
yield(i); | |
} | |
} | |
})) { | |
System.out.println(integer); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment