diff --git a/js/js.libraries/src/core/sequence.kt b/js/js.libraries/src/core/sequence.kt new file mode 100644 index 00000000000..27d083d7f01 --- /dev/null +++ b/js/js.libraries/src/core/sequence.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2010-2015 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 kotlin + +private class ConstrainedOnceSequence(sequence: Sequence) : Sequence { + private volatile var sequenceRef: Sequence? = sequence + //private val lock = Any() + + //TODO: Synchronize with the synchonized() method + override fun iterator(): Iterator { + val sequence = sequenceRef ?: throw IllegalStateException("This sequence can be consumed only once.") + sequenceRef = null + return sequence.iterator() + } +} \ No newline at end of file diff --git a/libraries/stdlib/src/kotlin/collections/Sequence.kt b/libraries/stdlib/src/kotlin/collections/Sequence.kt index b559f80147e..23874680e53 100644 --- a/libraries/stdlib/src/kotlin/collections/Sequence.kt +++ b/libraries/stdlib/src/kotlin/collections/Sequence.kt @@ -280,8 +280,7 @@ public class TakeSequence(private val sequence: Sequence, private val count: Int ) : Sequence { init { - if (count < 0) - throw IllegalArgumentException("count should be non-negative, but is $count") + require (count >= 0) { throw IllegalArgumentException("count should be non-negative, but is $count") } } override fun iterator(): Iterator = object : Iterator { @@ -361,8 +360,7 @@ public class DropSequence(private val sequence: Sequence, private val count: Int ) : Sequence { init { - if (count < 0) - throw IllegalArgumentException("count should be non-negative, but is $count") + require (count >= 0) { throw IllegalArgumentException("count should be non-negative, but is $count") } } override fun iterator(): Iterator = object : Iterator { @@ -510,11 +508,26 @@ private class GeneratorSequence(private val getInitialValue: () -> T?, p } } +/** + * Returns a wrapper sequence that provides values of this sequence, but ensures it can be iterated only one time. + * + * [IllegalStateException] is thrown on iterating the returned sequence from the second time. + */ +public fun Sequence.constrainOnce(): Sequence { + // as? does not work in js + //return this as? ConstrainedOnceSequence ?: ConstrainedOnceSequence(this) + return if (this is ConstrainedOnceSequence) this else ConstrainedOnceSequence(this) +} + /** * Returns a sequence which invokes the function to calculate the next value on each iteration until the function returns `null`. + * + * Returned sequence is constrained to be iterated only once. + * + * @see constrainOnce */ public fun sequence(nextFunction: () -> T?): Sequence { - return GeneratorSequence(nextFunction, { nextFunction() }) + return GeneratorSequence(nextFunction, { nextFunction() }).constrainOnce() } deprecated("Use sequence() instead") diff --git a/libraries/stdlib/src/kotlin/collections/SequenceJVM.kt b/libraries/stdlib/src/kotlin/collections/SequenceJVM.kt new file mode 100644 index 00000000000..460b3acfbae --- /dev/null +++ b/libraries/stdlib/src/kotlin/collections/SequenceJVM.kt @@ -0,0 +1,15 @@ + +package kotlin + +import java.util.concurrent.atomic.AtomicReference + + +private class ConstrainedOnceSequence(sequence: Sequence) : Sequence { + private val sequenceRef = AtomicReference(sequence) + + override fun iterator(): Iterator { + val sequence = sequenceRef.getAndSet(null) ?: throw IllegalStateException("This sequence can be consumed only once.") + return sequence.iterator() + } +} + diff --git a/libraries/stdlib/src/kotlin/io/ReadWrite.kt b/libraries/stdlib/src/kotlin/io/ReadWrite.kt index d29e71d14b9..3cb40161d7c 100644 --- a/libraries/stdlib/src/kotlin/io/ReadWrite.kt +++ b/libraries/stdlib/src/kotlin/io/ReadWrite.kt @@ -246,9 +246,9 @@ public inline fun Reader.useLines(block: (Sequence) -> T): T = * * We suggest you try the method [useLines] instead which closes the stream when the processing is complete. * - * @return a sequence of corresponding file lines + * @return a sequence of corresponding file lines. The sequence returned can be iterated only once. */ -public fun BufferedReader.lines(): Sequence = LinesSequence(this) +public fun BufferedReader.lines(): Sequence = LinesSequence(this).constrainOnce() deprecated("Use lines() function which returns Sequence") public fun BufferedReader.lineIterator(): Iterator = lines().iterator() diff --git a/libraries/stdlib/test/collections/SequenceTest.kt b/libraries/stdlib/test/collections/SequenceTest.kt index f8265fa1a5c..f0c2fa31afe 100644 --- a/libraries/stdlib/test/collections/SequenceTest.kt +++ b/libraries/stdlib/test/collections/SequenceTest.kt @@ -20,24 +20,24 @@ fun fibonacci(): Sequence { public class SequenceTest { test fun filterEmptySequence() { - val stream = sequenceOf() - assertEquals(0, stream.filter { false }.count()) - assertEquals(0, stream.filter { true }.count()) + val sequence = sequenceOf() + assertEquals(0, sequence.filter { false }.count()) + assertEquals(0, sequence.filter { true }.count()) } - test fun mapEmptyStream() { - val stream = sequenceOf() - assertEquals(0, stream.map { false }.count()) - assertEquals(0, stream.map { true }.count()) + test fun mapEmptySequence() { + val sequence = sequenceOf() + assertEquals(0, sequence.map { false }.count()) + assertEquals(0, sequence.map { true }.count()) } test fun requireNoNulls() { - val stream = sequenceOf("foo", "bar") - val notNull = stream.requireNoNulls() + val sequence = sequenceOf("foo", "bar") + val notNull = sequence.requireNoNulls() assertEquals(listOf("foo", "bar"), notNull.toList()) - val streamWithNulls = sequenceOf("foo", null, "bar") - val notNull2 = streamWithNulls.requireNoNulls() // shouldn't fail yet + val sequenceWithNulls = sequenceOf("foo", null, "bar") + val notNull2 = sequenceWithNulls.requireNoNulls() // shouldn't fail yet fails { // should throw an exception as we have a null notNull2.toList() @@ -128,9 +128,9 @@ public class SequenceTest { } test fun plus() { - val stream = sequenceOf("foo", "bar") - val streamCheese = stream + "cheese" - assertEquals(listOf("foo", "bar", "cheese"), streamCheese.toList()) + val sequence = sequenceOf("foo", "bar") + val sequenceCheese = sequence + "cheese" + assertEquals(listOf("foo", "bar", "cheese"), sequenceCheese.toList()) // lets use a mutable variable var mi = sequenceOf("a", "b") @@ -141,8 +141,8 @@ public class SequenceTest { test fun plusCollection() { val a = sequenceOf("foo", "bar") val b = listOf("cheese", "wine") - val stream = a + b - assertEquals(listOf("foo", "bar", "cheese", "wine"), stream.toList()) + val sequence = a + b + assertEquals(listOf("foo", "bar", "cheese", "wine"), sequence.toList()) // lets use a mutable variable var ml = sequenceOf("a") @@ -154,7 +154,7 @@ public class SequenceTest { } - test fun iterationOverStream() { + test fun iterationOverSequence() { var s = "" for (i in sequenceOf(0, 1, 2, 3, 4, 5)) { s = s + i.toString() @@ -162,31 +162,51 @@ public class SequenceTest { assertEquals("012345", s) } - test fun streamFromFunction() { + test fun sequenceFromFunction() { var count = 3 - val stream = sequence { + val sequence = sequence { count-- if (count >= 0) count else null } - val list = stream.toList() + val list = sequence.toList() assertEquals(listOf(2, 1, 0), list) + + fails { + sequence.toList() + } } - test fun streamFromFunctionWithInitialValue() { + test fun sequenceFromFunctionWithInitialValue() { val values = sequence(3) { n -> if (n > 0) n - 1 else null } val expected = listOf(3, 2, 1, 0) assertEquals(expected, values.toList()) assertEquals(expected, values.toList(), "Iterating sequence second time yields the same result") } + + test fun makeSequenceOneTimeConstrained() { + val sequence = sequenceOf(1, 2, 3, 4) + sequence.toList() + sequence.toList() + + val oneTime = sequence.constrainOnce() + oneTime.toList() + assertTrue("should fail with IllegalStateException") { + fails { + oneTime.toList() + } is IllegalStateException + } + + } + private fun > Sequence.takeWhileTo(result: C, predicate: (T) -> Boolean): C { for (element in this) if (predicate(element)) result.add(element) else break return result } - test fun streamExtensions() { + test fun sequenceExtensions() { val d = ArrayList() sequenceOf(0, 1, 2, 3, 4, 5).takeWhileTo(d, { i -> i < 4 }) assertEquals(4, d.size())