diff --git a/libraries/stdlib/api/js-v1/kotlin.random.kt b/libraries/stdlib/api/js-v1/kotlin.random.kt index d5c24341773..2a2d05a2de9 100644 --- a/libraries/stdlib/api/js-v1/kotlin.random.kt +++ b/libraries/stdlib/api/js-v1/kotlin.random.kt @@ -88,7 +88,7 @@ public abstract class Random { public open fun nextLong(from: kotlin.Long, until: kotlin.Long): kotlin.Long - public companion object of Random Default : kotlin.random.Random { + public companion object of Random Default : kotlin.random.Random, kotlin.io.Serializable { public open override fun nextBits(bitCount: kotlin.Int): kotlin.Int public open override fun nextBoolean(): kotlin.Boolean diff --git a/libraries/stdlib/api/js/kotlin.random.kt b/libraries/stdlib/api/js/kotlin.random.kt index d5c24341773..2a2d05a2de9 100644 --- a/libraries/stdlib/api/js/kotlin.random.kt +++ b/libraries/stdlib/api/js/kotlin.random.kt @@ -88,7 +88,7 @@ public abstract class Random { public open fun nextLong(from: kotlin.Long, until: kotlin.Long): kotlin.Long - public companion object of Random Default : kotlin.random.Random { + public companion object of Random Default : kotlin.random.Random, kotlin.io.Serializable { public open override fun nextBits(bitCount: kotlin.Int): kotlin.Int public open override fun nextBoolean(): kotlin.Boolean diff --git a/libraries/stdlib/jvm/src/kotlin/random/PlatformRandom.kt b/libraries/stdlib/jvm/src/kotlin/random/PlatformRandom.kt index fcdf9455f3a..c9cb14f6a96 100644 --- a/libraries/stdlib/jvm/src/kotlin/random/PlatformRandom.kt +++ b/libraries/stdlib/jvm/src/kotlin/random/PlatformRandom.kt @@ -5,8 +5,8 @@ package kotlin.random -import kotlin.UnsupportedOperationException -import kotlin.internal.* +import kotlin.internal.IMPLEMENTATIONS +import kotlin.internal.InlineOnly /** * Creates a [java.util.Random][java.util.Random] instance that uses the specified Kotlin [Random] generator as a randomness source. @@ -49,15 +49,18 @@ internal abstract class AbstractPlatformRandom : Random() { internal class FallbackThreadLocalRandom : AbstractPlatformRandom() { private val implStorage = object : ThreadLocal() { - override fun initialValue(): java.util.Random { - return java.util.Random() - } + override fun initialValue(): java.util.Random = java.util.Random() } + override val impl: java.util.Random get() = implStorage.get() } -private class PlatformRandom(override val impl: java.util.Random) : AbstractPlatformRandom() +private class PlatformRandom(override val impl: java.util.Random) : AbstractPlatformRandom(), Serializable { + private companion object { + private const val serialVersionUID: Long = 0L + } +} private class KotlinRandom(val impl: Random) : java.util.Random() { override fun next(bits: Int): Int = impl.nextBits(bits) @@ -73,6 +76,7 @@ private class KotlinRandom(val impl: Random) : java.util.Random() { } private var seedInitialized: Boolean = false + override fun setSeed(seed: Long) { if (!seedInitialized) { // ignore seed value from constructor @@ -81,4 +85,8 @@ private class KotlinRandom(val impl: Random) : java.util.Random() { throw UnsupportedOperationException("Setting seed is not supported.") } } + + private companion object { + private const val serialVersionUID: Long = 0L + } } diff --git a/libraries/stdlib/jvm/test/random/RandomSerializationTest.kt b/libraries/stdlib/jvm/test/random/RandomSerializationTest.kt new file mode 100644 index 00000000000..ec848727555 --- /dev/null +++ b/libraries/stdlib/jvm/test/random/RandomSerializationTest.kt @@ -0,0 +1,82 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package random + +import test.io.deserializeFromHex +import test.io.serializeAndDeserialize +import kotlin.random.Random +import kotlin.random.asJavaRandom +import kotlin.random.asKotlinRandom +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertSame +import kotlin.test.assertTrue + +class RandomSerializationTest { + @Test + fun defaultIsSerializable() { + val instance = Random + discardSomeValues(instance) + assertSame(instance, serializeAndDeserialize(instance)) + } + + private fun discardSomeValues(instance: Random) { + instance.nextInt() + instance.nextDouble() + instance.nextLong() + instance.nextBytes(64) + } + + private fun testRandomsHaveSameState(first: Random, second: Random) { + assertEquals(first.nextInt(), second.nextInt()) + assertEquals(first.nextDouble(), second.nextDouble()) + assertEquals(first.nextLong(), second.nextLong()) + assertTrue(first.nextBytes(64).contentEquals(second.nextBytes(64))) + } + + @Test + fun deserializeDefault() = assertSame( + expected = Random, + actual = deserializeFromHex("ac ed 00 05 73 72 00 22 6b 6f 74 6c 69 6e 2e 72 61 6e 64 6f 6d 2e 52 61 6e 64 6f 6d 24 44 65 66 61 75 6c 74 24 44 75 6d 6d 79 00 00 00 00 00 00 00 00 02 00 00 78 70"), + ) + + @Test + fun deserializeXorWow() { + val instance = Random(0) + + val deserialized = + deserializeFromHex("ac ed 00 05 73 72 00 1a 6b 6f 74 6c 69 6e 2e 72 61 6e 64 6f 6d 2e 58 6f 72 57 6f 77 52 61 6e 64 6f 6d 00 00 00 00 00 00 00 00 02 00 06 49 00 06 61 64 64 65 6e 64 49 00 01 76 49 00 01 77 49 00 01 78 49 00 01 79 49 00 01 7a 78 70 01 61 f1 40 23 9d 3d b3 c9 07 82 1d 6a 67 48 b0 e3 f9 bd 0c b6 5b de 32") + + testRandomsHaveSameState(instance, deserialized) + } + + @Test + fun xorwowIsSerializable() { + val instance = Random(0) + discardSomeValues(instance) + val deserialized = serializeAndDeserialize(instance) + testRandomsHaveSameState(instance, deserialized) + } + + @Test + fun wrapperOfKotlinRandomIsSerializable() { + val java = Random(0).asJavaRandom() + java.nextInt() + java.nextDouble() + java.nextLong() + java.nextBytes(ByteArray(64) { 0 }) + val deserialized = serializeAndDeserialize(java) + testRandomsHaveSameState(java.asKotlinRandom(), deserialized.asKotlinRandom()) + } + + @Test + fun wrapperOfJavaRandomIsSerializable() { + val kotlin = java.util.Random(0).asKotlinRandom() + discardSomeValues(kotlin) + val deserialized = serializeAndDeserialize(kotlin) + testRandomsHaveSameState(kotlin, deserialized) + } +} diff --git a/libraries/stdlib/src/kotlin/random/Random.kt b/libraries/stdlib/src/kotlin/random/Random.kt index 2532e2fd8f4..78c9ad50f27 100644 --- a/libraries/stdlib/src/kotlin/random/Random.kt +++ b/libraries/stdlib/src/kotlin/random/Random.kt @@ -267,10 +267,17 @@ public abstract class Random { * * @sample samples.random.Randoms.defaultRandom */ - companion object Default : Random() { - + companion object Default : Random(), Serializable { private val defaultRandom: Random = defaultPlatformRandom() + private object Dummy : Serializable { + private const val serialVersionUID = 0L + + private fun readResolve(): Any = Random + } + + private fun writeReplace(): Any = Dummy + override fun nextBits(bitCount: Int): Int = defaultRandom.nextBits(bitCount) override fun nextInt(): Int = defaultRandom.nextInt() override fun nextInt(until: Int): Int = defaultRandom.nextInt(until) @@ -290,7 +297,8 @@ public abstract class Random { override fun nextBytes(array: ByteArray): ByteArray = defaultRandom.nextBytes(array) override fun nextBytes(size: Int): ByteArray = defaultRandom.nextBytes(size) - override fun nextBytes(array: ByteArray, fromIndex: Int, toIndex: Int): ByteArray = defaultRandom.nextBytes(array, fromIndex, toIndex) + override fun nextBytes(array: ByteArray, fromIndex: Int, toIndex: Int): ByteArray = + defaultRandom.nextBytes(array, fromIndex, toIndex) } } @@ -325,7 +333,6 @@ public fun Random(seed: Int): Random = XorWowRandom(seed, seed.shr(31)) public fun Random(seed: Long): Random = XorWowRandom(seed.toInt(), seed.shr(32).toInt()) - /** * Gets the next random `Int` from the random number generator in the specified [range]. * diff --git a/libraries/stdlib/src/kotlin/random/XorWowRandom.kt b/libraries/stdlib/src/kotlin/random/XorWowRandom.kt index 592745ebb71..d70a3c73914 100644 --- a/libraries/stdlib/src/kotlin/random/XorWowRandom.kt +++ b/libraries/stdlib/src/kotlin/random/XorWowRandom.kt @@ -15,15 +15,14 @@ package kotlin.random * Available at https://www.jstatsoft.org/v08/i14/paper * */ -internal class XorWowRandom -internal constructor( +internal class XorWowRandom internal constructor( private var x: Int, private var y: Int, private var z: Int, private var w: Int, private var v: Int, private var addend: Int -) : Random() { +) : Random(), Serializable { internal constructor(seed1: Int, seed2: Int) : this(seed1, seed2, 0, 0, seed1.inv(), (seed1 shl 10) xor (seed2 ushr 4)) @@ -53,4 +52,8 @@ internal constructor( override fun nextBits(bitCount: Int): Int = nextInt().takeUpperBits(bitCount) + + private companion object { + private const val serialVersionUID: Long = 0L + } } diff --git a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt index e31d9550e55..ea7934ff5cb 100644 --- a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt +++ b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt @@ -4232,7 +4232,7 @@ public abstract class kotlin/random/Random { public fun nextLong (JJ)J } -public final class kotlin/random/Random$Default : kotlin/random/Random { +public final class kotlin/random/Random$Default : kotlin/random/Random, java/io/Serializable { public fun nextBits (I)I public fun nextBoolean ()Z public fun nextBytes (I)[B