Support memoizing lazy iterable
diff --git a/compiler/frontend/src/org/jetbrains/jet/storage/LockBasedLazyResolveStorageManager.java b/compiler/frontend/src/org/jetbrains/jet/storage/LockBasedLazyResolveStorageManager.java
index 91084ed..c1efbdd 100644
--- a/compiler/frontend/src/org/jetbrains/jet/storage/LockBasedLazyResolveStorageManager.java
+++ b/compiler/frontend/src/org/jetbrains/jet/storage/LockBasedLazyResolveStorageManager.java
@@ -32,6 +32,7 @@
import org.jetbrains.jet.util.slicedmap.WritableSlice;
import java.util.Collection;
+import java.util.Iterator;
import java.util.concurrent.locks.Lock;
// This class is kept under the same package as LockBasedStorageManager to get access to its protected members
@@ -134,6 +135,12 @@
return storageManager.compute(computable);
}
+ @NotNull
+ @Override
+ public <T, D> Iterable<T> createLazyIterable(Iterator<? extends D> data, Function1<? super D, ? extends T> compute) {
+ return storageManager.createLazyIterable(data, compute);
+ }
+
private static class LockProtectedContext implements BindingContext {
private final Lock lock;
private final BindingContext context;
diff --git a/compiler/tests/org/jetbrains/jet/storage/StorageManagerTest.java b/compiler/tests/org/jetbrains/jet/storage/StorageManagerTest.java
index db4c727..e8de4de 100644
--- a/compiler/tests/org/jetbrains/jet/storage/StorageManagerTest.java
+++ b/compiler/tests/org/jetbrains/jet/storage/StorageManagerTest.java
@@ -6,10 +6,7 @@
import junit.framework.TestCase;
import org.jetbrains.jet.util.ReenteringLazyValueComputationException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
+import java.util.*;
public class StorageManagerTest extends TestCase {
@@ -426,6 +423,64 @@
assertEquals(2, c.getCount());
}
+ // Lazy iterable
+
+ public void testEmpty() throws Exception {
+ Iterable<Object> iterable = m.createLazyIterable(
+ Collections.emptyList().iterator(),
+ new Function1<Object, Object>() {
+ @Override
+ public Object invoke(Object o) {
+ fail("Should not be called for empty data. Argument: " + o);
+ return null;
+ }
+ }
+ );
+ for (Object o : iterable) {
+ fail("Should be empty. Found: " + o);
+ }
+ }
+
+ public void testOne() throws Exception {
+ doTestMutableIterable("a");
+ }
+
+ public void testTwo() throws Exception {
+ doTestMutableIterable("a", "b");
+ }
+
+ public void testThree() throws Exception {
+ doTestMutableIterable("a", "b", "c");
+ }
+
+ private void doTestMutableIterable(String... items) {
+ List<String> data = Arrays.asList(items);
+ final CounterImpl c = new CounterImpl();
+ Iterable<String> iterable = m.createLazyIterable(
+ data.iterator(),
+ new Function1<String, String>() {
+ @Override
+ public String invoke(String s) {
+ c.inc();
+ return s;
+ }
+ }
+ );
+
+ for (int i = 0; i < data.size(); i++) {
+ // iterate up to i
+ int j = 0;
+ for (String s : iterable) {
+ assertEquals("Iterating up to " + i + " at index " + j, data.get(j), s);
+ if (j == i) break;
+ j++;
+ }
+ assertEquals("Too few iterations", i, j);
+ }
+
+ assertEquals(data.size(), c.getCount());
+ }
+
// Utilities
private static <K, V> Function0<V> apply(final Function1<K, V> f, final K x) {
diff --git a/core/util.runtime/src/org/jetbrains/jet/storage/LockBasedStorageManager.java b/core/util.runtime/src/org/jetbrains/jet/storage/LockBasedStorageManager.java
index e1b43ca..ed97614 100644
--- a/core/util.runtime/src/org/jetbrains/jet/storage/LockBasedStorageManager.java
+++ b/core/util.runtime/src/org/jetbrains/jet/storage/LockBasedStorageManager.java
@@ -24,6 +24,7 @@
import org.jetbrains.jet.utils.ExceptionUtils;
import org.jetbrains.jet.utils.WrappedValues;
+import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.locks.Lock;
@@ -169,6 +170,12 @@
}
@NotNull
+ @Override
+ public <T, D> Iterable<T> createLazyIterable(Iterator<? extends D> data, Function1<? super D, ? extends T> compute) {
+ return new MemoizingLazyIterable<T, D>(lock, data, compute);
+ }
+
+ @NotNull
protected <T> RecursionDetectedResult<T> recursionDetectedDefault() {
throw new IllegalStateException("Recursive call in a lazy value");
}
diff --git a/core/util.runtime/src/org/jetbrains/jet/storage/MemoizingLazyIterable.kt b/core/util.runtime/src/org/jetbrains/jet/storage/MemoizingLazyIterable.kt
new file mode 100644
index 0000000..50dd80a
--- /dev/null
+++ b/core/util.runtime/src/org/jetbrains/jet/storage/MemoizingLazyIterable.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2010-2014 JetBrains s.r.o.
+ *
+ * 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 org.jetbrains.jet.storage
+
+import java.util.concurrent.locks.Lock
+import java.util.ArrayList
+import java.util.NoSuchElementException
+import org.jetbrains.kotlin.util.printAndReturn
+
+class MemoizingLazyIterable<T, D>(
+ private val lock: Lock,
+ // all access to this iterator is guarded by the lock
+ private val data: Iterator<D>,
+ // all calls to this function are guarded by the lock
+ private val compute : (D) -> T
+) : Iterable<T> {
+
+ // this list contains results of applying compute() to items in data
+ // data is traversed once, the size of this list reflects how many data items were seen
+ private val storedValues = ArrayList<T>()
+
+ private fun isElementAvailableAt(index: Int): Boolean {
+ lock.lock()
+ try {
+ val size = storedValues.size()
+ assert(index <= size) {"Trying to look too far ahead: $index, only ${size} elements are computed"}
+ return index < size || data.hasNext()
+ }
+ finally {
+ lock.unlock()
+ }
+ }
+
+ private fun getByIndex(index: Int): T {
+ lock.lock()
+ try {
+ if (storedValues.size() > index) return storedValues[index]
+
+ for (i in storedValues.size()..index) {
+ if (!data.hasNext()) throw NoSuchElementException("Index: $index")
+
+ storedValues.add(compute(data.next()))
+ }
+
+ if (!data.hasNext()) storedValues.trimToSize()
+
+ return storedValues[index]
+ }
+ finally {
+ lock.unlock()
+ }
+ }
+
+ // Accessing the same iterator from different threads is NOT safe
+ // Having many iterators, each of which is accessed from only one thread, is safe
+ override fun iterator(): Iterator<T> {
+ return object : Iterator<T> {
+ private var index = 0
+
+ override fun next() = getByIndex(index++)
+
+ override fun hasNext() = isElementAvailableAt(index)
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/core/util.runtime/src/org/jetbrains/jet/storage/StorageManager.kt b/core/util.runtime/src/org/jetbrains/jet/storage/StorageManager.kt
index 9a93217..db9bd23 100644
--- a/core/util.runtime/src/org/jetbrains/jet/storage/StorageManager.kt
+++ b/core/util.runtime/src/org/jetbrains/jet/storage/StorageManager.kt
@@ -54,4 +54,7 @@
fun createNullableLazyValueWithPostCompute<T: Any>(computable: () -> T?, postCompute: (T?) -> Unit): NullableLazyValue<T>
fun compute<T>(computable: () -> T): T
+
+ // data and computable are accessed only synchronously
+ fun <T, D> createLazyIterable(data: Iterator<D>, compute: (D) -> T): Iterable<T>
}