From 536e669023cb3da0172db80f10633a295b0ae78d Mon Sep 17 00:00:00 2001 From: Ilya Gorbunov Date: Thu, 11 Jun 2015 23:29:33 +0300 Subject: [PATCH] =?UTF-8?q?Provide=20Lazy=20in=20kotlin=20package=20?= =?UTF-8?q?=E2=80=94=20an=20interface=20that=20represents=20lazily=20compu?= =?UTF-8?q?ted=20value.=20Read-only=20properties=20can=20be=20delegated=20?= =?UTF-8?q?to=20lazy=20with=20the=20extension=20getter.=20Delegates.lazy?= =?UTF-8?q?=20and=20blockingLazy=20are=20deprecated.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- js/js.libraries/src/core/kotlin.kt | 6 +- .../src/kotlin/properties/Delegation.kt | 2 + libraries/stdlib/src/kotlin/util/Lazy.kt | 79 ++++++++ libraries/stdlib/src/kotlin/util/LazyJVM.kt | 9 + .../delegation/lazy/LazyValuesTest.kt | 169 +++++++++++------- 5 files changed, 203 insertions(+), 62 deletions(-) create mode 100644 libraries/stdlib/src/kotlin/util/Lazy.kt create mode 100644 libraries/stdlib/src/kotlin/util/LazyJVM.kt diff --git a/js/js.libraries/src/core/kotlin.kt b/js/js.libraries/src/core/kotlin.kt index 96643f62da5..b3445718a37 100644 --- a/js/js.libraries/src/core/kotlin.kt +++ b/js/js.libraries/src/core/kotlin.kt @@ -49,4 +49,8 @@ public fun setOf(value: T): Set = hashSetOf(value) * Returns an immutable map, mapping only the specified key to the * specified value. */ -public fun mapOf(keyValuePair: Pair): Map = hashMapOf(keyValuePair) \ No newline at end of file +public fun mapOf(keyValuePair: Pair): Map = hashMapOf(keyValuePair) + + +public fun lazy(initializer: () -> T): Lazy = UnsafeLazyImpl(initializer) +public fun lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy = UnsafeLazyImpl(initializer) diff --git a/libraries/stdlib/src/kotlin/properties/Delegation.kt b/libraries/stdlib/src/kotlin/properties/Delegation.kt index 7c96cd7f950..445108cb1dc 100644 --- a/libraries/stdlib/src/kotlin/properties/Delegation.kt +++ b/libraries/stdlib/src/kotlin/properties/Delegation.kt @@ -17,6 +17,7 @@ public object Delegates { * specified block of code. Supports lazy initialization semantics for properties. * @param initializer the function that returns the value of the property. */ + deprecated("Use lazy {} instead in kotlin package", ReplaceWith("kotlin.lazy(initializer)")) public fun lazy(initializer: () -> T): ReadOnlyProperty = LazyVal(initializer) /** @@ -27,6 +28,7 @@ public object Delegates { * the property delegate object itself is used as a lock. * @param initializer the function that returns the value of the property. */ + deprecated("Use lazy {} instead in kotlin package", ReplaceWith("kotlin.lazy(initializer)")) public fun blockingLazy(lock: Any? = null, initializer: () -> T): ReadOnlyProperty = BlockingLazyVal(lock, initializer) /** diff --git a/libraries/stdlib/src/kotlin/util/Lazy.kt b/libraries/stdlib/src/kotlin/util/Lazy.kt new file mode 100644 index 00000000000..4ab30c48870 --- /dev/null +++ b/libraries/stdlib/src/kotlin/util/Lazy.kt @@ -0,0 +1,79 @@ +package kotlin + + + +public interface Lazy { + public val value: T + public val valueCreated: Boolean +} + +public fun Lazy.get(thisRef: Any?, property: PropertyMetadata): T = value + + +public enum class LazyThreadSafetyMode { + SYNCHRONIZED, + NONE, +} + + +private object NULL_VALUE {} + +private fun escape(value: Any?): Any { + return value ?: NULL_VALUE +} + +private fun unescape(value: Any?): Any? { + return if (value === NULL_VALUE) null else value +} + + +private class LazyImpl(initializer: () -> T, private val lockObj: Any) : Lazy { + private var initializer: (() -> T)? = initializer + private volatile var _value: Any? = null + + public override val value: T + get() { + val _v1 = _value + if (_v1 != null) { + return unescape(_v1) as T + } + + return synchronized(lockObj) { + val _v2 = _value + if (_v2 != null) { + unescape(_v2) as T + } + else { + val typedValue = initializer!!() + _value = escape(typedValue) + initializer = null + typedValue + } + } + } + + override val valueCreated: Boolean = _value == null + + override fun toString(): String = if (valueCreated) value.toString() else "Lazy value not created yet." +} + + +private class UnsafeLazyImpl(initializer: () -> T) : Lazy { + private var initializer: (() -> T)? = initializer + + + private var _value: Any? = null + + override val value: T + get() { + if (_value == null) { + _value = escape(initializer!!()) + initializer == null + } + return unescape(_value) as T + } + + override val valueCreated: Boolean = _value == null + + override fun toString(): String = if (valueCreated) value.toString() else "Lazy value not created yet." +} diff --git a/libraries/stdlib/src/kotlin/util/LazyJVM.kt b/libraries/stdlib/src/kotlin/util/LazyJVM.kt new file mode 100644 index 00000000000..ca138db7bc6 --- /dev/null +++ b/libraries/stdlib/src/kotlin/util/LazyJVM.kt @@ -0,0 +1,9 @@ +package kotlin + + +public fun lazy(initializer: () -> T): Lazy = LazyImpl(initializer, Any()) +public fun lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy = + when (mode) { + LazyThreadSafetyMode.SYNCHRONIZED -> LazyImpl(initializer, Any()) + LazyThreadSafetyMode.NONE -> UnsafeLazyImpl(initializer) + } diff --git a/libraries/stdlib/test/properties/delegation/lazy/LazyValuesTest.kt b/libraries/stdlib/test/properties/delegation/lazy/LazyValuesTest.kt index 6bec3bd385f..eb931740286 100644 --- a/libraries/stdlib/test/properties/delegation/lazy/LazyValuesTest.kt +++ b/libraries/stdlib/test/properties/delegation/lazy/LazyValuesTest.kt @@ -11,24 +11,34 @@ class LazyValuesTest(): DelegationTestBase() { doTest(TestLazyVal()) } + test fun testUnsafeLazyVal() { + doTest(TestUnsafeLazyVal()) + } + test fun testNullableLazyVal() { doTest(TestNullableLazyVal()) } - test fun testAtomicNullableLazyVal() { - doTest(TestAtomicNullableLazyVal()) + test fun testUnsafeNullableLazyVal() { + doTest(TestUnsafeNullableLazyVal()) } - test fun testAtomicLazyVal() { - doTest(TestAtomicLazyVal()) + // deprecated lazy tests + + test fun testUnsafeLazyValDeprecated() { + doTest(TestUnsafeLazyValDeprecated()) } - test fun testVolatileNullableLazyVal() { - doTest(TestVolatileNullableLazyVal()) + test fun testBlockingLazyValDeprecated() { + doTest(TestBlockingLazyValDeprecated()) } - test fun testVolatileLazyVal() { - doTest(TestVolatileLazyVal()) + test fun testUnsafeNullableLazyValDeprecated() { + doTest(TestUnsafeNullableLazyValDeprecated()) + } + + test fun testBlockingNullableLazyValDeprecated() { + doTest(TestBlockingNullableLazyValDeprecated()) } test fun testIdentityEqualsIsUsedToUnescapeLazyVal() { @@ -37,6 +47,80 @@ class LazyValuesTest(): DelegationTestBase() { } class TestLazyVal: WithBox { + var result = 0 + val a by lazy { + ++result + } + + override fun box(): String { + a + if (a != 1) return "fail: initializer should be invoked only once" + return "OK" + } +} + +class TestUnsafeLazyVal: WithBox { + var result = 0 + val a by lazy(LazyThreadSafetyMode.NONE) { + ++result + } + + override fun box(): String { + a + if (a != 1) return "fail: initializer should be invoked only once" + return "OK" + } +} + +class TestNullableLazyVal: WithBox { + var resultA = 0 + var resultB = 0 + + val a: Int? by lazy { resultA++; null} + val b by lazy { foo() } + + override fun box(): String { + a + b + + if (a != null) return "fail: a should be null" + if (b != null) return "fail: a should be null" + if (resultA != 1) return "fail: initializer for a should be invoked only once" + if (resultB != 1) return "fail: initializer for b should be invoked only once" + return "OK" + } + + fun foo(): String? { + resultB++ + return null + } +} + +class TestUnsafeNullableLazyVal: WithBox { + var resultA = 0 + var resultB = 0 + + val a: Int? by lazy(LazyThreadSafetyMode.NONE) { resultA++; null} + val b by lazy(LazyThreadSafetyMode.NONE) { foo() } + + override fun box(): String { + a + b + + if (a != null) return "fail: a should be null" + if (b != null) return "fail: a should be null" + if (resultA != 1) return "fail: initializer for a should be invoked only once" + if (resultB != 1) return "fail: initializer for b should be invoked only once" + return "OK" + } + + fun foo(): String? { + resultB++ + return null + } +} + +class TestUnsafeLazyValDeprecated: WithBox { var result = 0 val a by Delegates.lazy { ++result @@ -49,7 +133,20 @@ class TestLazyVal: WithBox { } } -class TestNullableLazyVal: WithBox { +class TestBlockingLazyValDeprecated: WithBox { + var result = 0 + val a by Delegates.blockingLazy { + ++result + } + + override fun box(): String { + a + if (a != 1) return "fail: initializer should be invoked only once" + return "OK" + } +} + +class TestUnsafeNullableLazyValDeprecated : WithBox { var resultA = 0 var resultB = 0 @@ -73,57 +170,7 @@ class TestNullableLazyVal: WithBox { } } -class TestAtomicLazyVal: WithBox { - var result = 0 - val a by Delegates.blockingLazy { - ++result - } - - override fun box(): String { - a - if (a != 1) return "fail: initializer should be invoked only once" - return "OK" - } -} - -class TestVolatileNullableLazyVal: WithBox { - var resultA = 0 - var resultB = 0 - - val a: Int? by Delegates.blockingLazy { resultA++; null} - val b by Delegates.blockingLazy { foo() } - - override fun box(): String { - a - b - - if (a != null) return "fail: a should be null" - if (b != null) return "fail: a should be null" - if (resultA != 1) return "fail: initializer for a should be invoked only once" - if (resultB != 1) return "fail: initializer for b should be invoked only once" - return "OK" - } - - fun foo(): String? { - resultB++ - return null - } -} - -class TestVolatileLazyVal: WithBox { - var result = 0 - val a by Delegates.blockingLazy { - ++result - } - - override fun box(): String { - a - if (a != 1) return "fail: initializer should be invoked only once" - return "OK" - } -} - -class TestAtomicNullableLazyVal: WithBox { +class TestBlockingNullableLazyValDeprecated: WithBox { var resultA = 0 var resultB = 0 @@ -149,7 +196,7 @@ class TestAtomicNullableLazyVal: WithBox { class TestIdentityEqualsIsUsedToUnescapeLazyVal: WithBox { var equalsCalled = 0 - val a by Delegates.lazy { ClassWithCustomEquality { equalsCalled++ } } + val a by lazy { ClassWithCustomEquality { equalsCalled++ } } override fun box(): String { a