├── LICENSE ├── pom.xml ├── README.md └── src ├── test └── java │ └── io │ └── herrmann │ └── generator │ └── GeneratorTest.java └── main └── java └── io └── herrmann └── generator └── Generator.java /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Michael Herrmann. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | io.herrmann 6 | generator 7 | 1.0-SNAPSHOT 8 | jar 9 | 10 | java-generator-functions 11 | An implementation of a Python-like yield(...) method in Java. 12 | https://github.com/mherrmann/java-generator-functions 13 | 14 | 15 | 16 | MIT License 17 | http://www.opensource.org/licenses/mit-license.php 18 | repo 19 | 20 | 21 | 22 | 23 | 24 | Michael Herrmann 25 | michael@herrmann.io 26 | 27 | 28 | 29 | 30 | scm:git:git@github.com:mherrmann/java-generator-functions.git 31 | scm:git:git@github.com:mherrmann/java-generator-functions.git 32 | scm:git:git@github.com:mherrmann/java-generator-functions.git 33 | 34 | 35 | 36 | UTF-8 37 | 38 | 39 | 40 | 41 | junit 42 | junit 43 | 4.11 44 | test 45 | 46 | 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | java-generator-functions 2 | ======================== 3 | 4 | An implementation of Python-like generator functions in Java. This repository contains a single class, `Generator` with a method `yield(...)` which can be used to mimic the behaviour of the `yield` keyword in Python. 5 | 6 | Examples 7 | -------- 8 | The following is a simple generator that yields `1` and then `2`: 9 | 10 | ```java 11 | Generator simpleGenerator = new Generator() { 12 | public void run() throws InterruptedException { 13 | yield(1); 14 | // Some logic here... 15 | yield(2); 16 | } 17 | }; 18 | for (Integer element : simpleGenerator) 19 | System.out.println(element); 20 | // Prints "1", then "2". 21 | ``` 22 | 23 | Infinite generators are also possible: 24 | 25 | ```java 26 | Generator infiniteGenerator = new Generator() { 27 | public void run() throws InterruptedException { 28 | while (true) 29 | yield(1); 30 | } 31 | }; 32 | ``` 33 | 34 | The `Generator` class lies in package `io.herrmann.generator`. So you need to `import io.herrmann.generator.Generator;` in order for the above examples to work. 35 | 36 | Usage 37 | ----- 38 | 39 | This package is hosted as a Maven repository with the following url: 40 | 41 | http://dl.bintray.com/filipmalczak/maven 42 | 43 | To use it from Maven, add the following to your `pom.xml`: 44 | 45 | ```xml 46 | 47 | ... 48 | 49 | ... 50 | 51 | java-generator-functions 52 | http://dl.bintray.com/filipmalczak/maven 53 | 54 | 55 | ... 56 | 57 | 58 | io.herrmann 59 | java-generator-functions 60 | 1.0 61 | 62 | 63 | 64 | ``` 65 | 66 | For Gradle: 67 | 68 | ```gradle 69 | compile(group: 'io.herrmann', name: 'java-generator-functions', version: '1.0') 70 | ``` 71 | 72 | Caveats and Performance 73 | ----------------------- 74 | The `Generator` class internally works with a Thread to produce the items. It does ensure that no Threads stay around if the corresponding Generator is no longer used. However: 75 | 76 | **If too many `Generator`s are created before the JVM gets a chance to garbage collect the old ones, you may encounter `OutOfMemoryError`s. This problem most strongly presents itself on OS X where the maximum number of Threads is significantly lower than on other OSs (around 2000).** 77 | 78 | The performance is obviously not great but not too shabby either. On my machine with a dual core i5 CPU @ 2.67 GHz, 1000 items can be produced in < 0.03s. 79 | 80 | Contributing 81 | ------------ 82 | Contributions and pull requests are welcome. Please ensure that `mvn test` still passes and add any unit tests as you see fit. Please also follow the same coding conventions, in particular the line limit of 80 characters and the use of tabs instead of spaces. 83 | -------------------------------------------------------------------------------- /src/test/java/io/herrmann/generator/GeneratorTest.java: -------------------------------------------------------------------------------- 1 | package io.herrmann.generator; 2 | 3 | import org.junit.Test; 4 | 5 | import java.util.ArrayList; 6 | import java.util.Arrays; 7 | import java.util.Iterator; 8 | import java.util.List; 9 | 10 | import static org.junit.Assert.assertEquals; 11 | import static org.junit.Assert.assertTrue; 12 | 13 | public class GeneratorTest { 14 | @Test 15 | public void testEmptyGenerator() { 16 | assertEquals(new ArrayList(), list(new EmptyGenerator())); 17 | } 18 | private class EmptyGenerator extends Generator { 19 | @Override 20 | protected void run() { 21 | } 22 | } 23 | public static List list(Iterable iterable) { 24 | List result = new ArrayList(); 25 | for (T item : iterable) 26 | result.add(item); 27 | return result; 28 | } 29 | @Test 30 | public void testOneEltGenerator() { 31 | List oneEltList = Arrays.asList(1); 32 | assertEquals(oneEltList, list(new ListGenerator(oneEltList))); 33 | } 34 | private class ListGenerator extends Generator { 35 | private final List elements; 36 | public ListGenerator(List elements) { 37 | this.elements = elements; 38 | } 39 | protected void run() throws InterruptedException { 40 | for (T element : elements) 41 | yield(element); 42 | } 43 | } 44 | @Test 45 | public void testTwoEltGenerator() { 46 | List twoEltList = Arrays.asList(1, 2); 47 | assertEquals(twoEltList, list(new ListGenerator(twoEltList))); 48 | } 49 | @Test 50 | public void testInfiniteGenerator() { 51 | InfiniteGenerator generator = new InfiniteGenerator(); 52 | testInfiniteGenerator(generator); 53 | } 54 | public void testInfiniteGenerator(InfiniteGenerator generator) { 55 | int NUM_ELTS_TO_INSPECT = 1000; 56 | Iterator generatorIterator = generator.iterator(); 57 | for (int i=0; i < NUM_ELTS_TO_INSPECT; i++) { 58 | assertTrue(generatorIterator.hasNext()); 59 | assertEquals(1, (int) generatorIterator.next()); 60 | } 61 | } 62 | private class InfiniteGenerator extends Generator { 63 | @Override 64 | protected void run() throws InterruptedException { 65 | while (true) 66 | yield(1); 67 | } 68 | } 69 | @Test 70 | public void testInfiniteGeneratorLeavesNoRunningThreads() throws Throwable { 71 | InfiniteGenerator generator = new InfiniteGenerator(); 72 | testInfiniteGenerator(generator); 73 | generator.finalize(); 74 | assertEquals(Thread.State.TERMINATED, generator.producer.getState()); 75 | } 76 | 77 | private class CustomRuntimeException extends RuntimeException {} 78 | 79 | private class GeneratorRaisingException extends Generator { 80 | @Override 81 | protected void run() throws InterruptedException { 82 | throw new CustomRuntimeException(); 83 | } 84 | } 85 | 86 | @Test(expected = CustomRuntimeException.class) 87 | public void testGeneratorRaisingExceptionHasNext() { 88 | GeneratorRaisingException generator = new GeneratorRaisingException(); 89 | Iterator iterator = generator.iterator(); 90 | iterator.hasNext(); 91 | } 92 | 93 | @Test(expected = CustomRuntimeException.class) 94 | public void testGeneratorRaisingExceptionNext() { 95 | GeneratorRaisingException generator = new GeneratorRaisingException(); 96 | Iterator iterator = generator.iterator(); 97 | iterator.next(); 98 | } 99 | 100 | } -------------------------------------------------------------------------------- /src/main/java/io/herrmann/generator/Generator.java: -------------------------------------------------------------------------------- 1 | package io.herrmann.generator; 2 | 3 | import java.util.Iterator; 4 | import java.util.NoSuchElementException; 5 | 6 | /** 7 | * This class allows specifying Python generator-like sequences. For examples, 8 | * see the JUnit test case. 9 | * 10 | * The implementation uses a separate Thread to produce the sequence items. This 11 | * is certainly not as fast as eg. a for-loop, but not horribly slow either. On 12 | * a machine with a dual core i5 CPU @ 2.67 GHz, 1000 items can be produced in 13 | * < 0.03s. 14 | * 15 | * By overriding finalize(), the class takes care not to leave any Threads 16 | * running longer than necessary. 17 | */ 18 | public abstract class Generator implements Iterable { 19 | 20 | private class Condition { 21 | private boolean isSet; 22 | public synchronized void set() { 23 | isSet = true; 24 | notify(); 25 | } 26 | public synchronized void await() throws InterruptedException { 27 | try { 28 | if (isSet) 29 | return; 30 | wait(); 31 | } finally { 32 | isSet = false; 33 | } 34 | } 35 | } 36 | 37 | static ThreadGroup THREAD_GROUP; 38 | 39 | Thread producer; 40 | private boolean hasFinished; 41 | private final Condition itemAvailableOrHasFinished = new Condition(); 42 | private final Condition itemRequested = new Condition(); 43 | private T nextItem; 44 | private boolean nextItemAvailable; 45 | private RuntimeException exceptionRaisedByProducer; 46 | 47 | @Override 48 | public Iterator iterator() { 49 | return new Iterator() { 50 | @Override 51 | public boolean hasNext() { 52 | return waitForNext(); 53 | } 54 | @Override 55 | public T next() { 56 | if (!waitForNext()) 57 | throw new NoSuchElementException(); 58 | nextItemAvailable = false; 59 | return nextItem; 60 | } 61 | @Override 62 | public void remove() { 63 | throw new UnsupportedOperationException(); 64 | } 65 | private boolean waitForNext() { 66 | if (nextItemAvailable) 67 | return true; 68 | if (hasFinished) 69 | return false; 70 | if (producer == null) 71 | startProducer(); 72 | itemRequested.set(); 73 | try { 74 | itemAvailableOrHasFinished.await(); 75 | } catch (InterruptedException e) { 76 | hasFinished = true; 77 | } 78 | if (exceptionRaisedByProducer != null) 79 | throw exceptionRaisedByProducer; 80 | return !hasFinished; 81 | } 82 | }; 83 | } 84 | 85 | protected abstract void run() throws InterruptedException; 86 | 87 | protected void yield(T element) throws InterruptedException { 88 | nextItem = element; 89 | nextItemAvailable = true; 90 | itemAvailableOrHasFinished.set(); 91 | itemRequested.await(); 92 | } 93 | 94 | private void startProducer() { 95 | assert producer == null; 96 | if (THREAD_GROUP == null) 97 | THREAD_GROUP = new ThreadGroup("generatorfunctions"); 98 | producer = new Thread(THREAD_GROUP, new Runnable() { 99 | @Override 100 | public void run() { 101 | try { 102 | itemRequested.await(); 103 | Generator.this.run(); 104 | } catch (InterruptedException e) { 105 | // No need to do anything here; Remaining steps in run() 106 | // will cleanly shut down the thread. 107 | } catch (RuntimeException e) { 108 | exceptionRaisedByProducer = e; 109 | } 110 | hasFinished = true; 111 | itemAvailableOrHasFinished.set(); 112 | } 113 | }); 114 | producer.setDaemon(true); 115 | producer.start(); 116 | } 117 | 118 | @Override 119 | protected void finalize() throws Throwable { 120 | producer.interrupt(); 121 | producer.join(); 122 | super.finalize(); 123 | } 124 | } 125 | --------------------------------------------------------------------------------