Make builder collection implementations serializable KT-39328
Collections returned by collection builders are now serializable in their read-only state. The builder mutable collections inside collection builder lambda, however, are not.
This commit is contained in:
@@ -1,10 +1,14 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Copyright 2010-2021 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 kotlin.collections.builders
|
||||
|
||||
import java.io.Externalizable
|
||||
import java.io.InvalidObjectException
|
||||
import java.io.NotSerializableException
|
||||
|
||||
internal class ListBuilder<E> private constructor(
|
||||
private var array: Array<E>,
|
||||
private var offset: Int,
|
||||
@@ -12,7 +16,7 @@ internal class ListBuilder<E> private constructor(
|
||||
private var isReadOnly: Boolean,
|
||||
private val backing: ListBuilder<E>?,
|
||||
private val root: ListBuilder<E>?
|
||||
) : MutableList<E>, RandomAccess, AbstractMutableList<E>() {
|
||||
) : MutableList<E>, RandomAccess, AbstractMutableList<E>(), Serializable {
|
||||
|
||||
constructor() : this(10)
|
||||
|
||||
@@ -26,6 +30,12 @@ internal class ListBuilder<E> private constructor(
|
||||
return this
|
||||
}
|
||||
|
||||
private fun writeReplace(): Any =
|
||||
if (isEffectivelyReadOnly)
|
||||
SerializedCollection(this, SerializedCollection.tagList)
|
||||
else
|
||||
throw NotSerializableException("The list cannot be serialized while it is being built.")
|
||||
|
||||
override val size: Int
|
||||
get() = length
|
||||
|
||||
@@ -175,9 +185,12 @@ internal class ListBuilder<E> private constructor(
|
||||
}
|
||||
|
||||
private fun checkIsMutable() {
|
||||
if (isReadOnly || root != null && root.isReadOnly) throw UnsupportedOperationException()
|
||||
if (isEffectivelyReadOnly) throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
private val isEffectivelyReadOnly: Boolean
|
||||
get() = isReadOnly || root != null && root.isReadOnly
|
||||
|
||||
private fun ensureExtraCapacity(n: Int) {
|
||||
ensureCapacity(length + n)
|
||||
}
|
||||
@@ -367,4 +380,50 @@ internal fun <E> Array<E>.resetAt(index: Int) {
|
||||
|
||||
internal fun <E> Array<E>.resetRange(fromIndex: Int, toIndex: Int) {
|
||||
for (index in fromIndex until toIndex) resetAt(index)
|
||||
}
|
||||
|
||||
internal class SerializedCollection(
|
||||
private var collection: Collection<*>,
|
||||
private val tag: Int
|
||||
) : Externalizable {
|
||||
|
||||
constructor() : this(emptyList<Any?>(), 0) // for deserialization
|
||||
|
||||
override fun writeExternal(output: java.io.ObjectOutput) {
|
||||
output.writeByte(tag)
|
||||
output.writeInt(collection.size)
|
||||
for (element in collection) {
|
||||
output.writeObject(element)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
override fun readExternal(input: java.io.ObjectInput) {
|
||||
val flags = input.readByte().toInt()
|
||||
val tag = flags and 1
|
||||
val other = flags and 1.inv()
|
||||
if (other != 0) {
|
||||
throw InvalidObjectException("Unsupported flags value: $flags.")
|
||||
}
|
||||
val size = input.readInt()
|
||||
if (size < 0) throw InvalidObjectException("Illegal size value: $size.")
|
||||
collection = when (tag) {
|
||||
tagList -> buildList<Any?>(size) {
|
||||
repeat(size) { add(input.readObject()) }
|
||||
}
|
||||
tagSet -> buildSet<Any?>(size) {
|
||||
repeat(size) { add(input.readObject()) }
|
||||
}
|
||||
else ->
|
||||
throw InvalidObjectException("Unsupported collection type tag: $tag.")
|
||||
}
|
||||
}
|
||||
|
||||
private fun readResolve(): Any = collection
|
||||
|
||||
companion object {
|
||||
private const val serialVersionUID: Long = 0L
|
||||
const val tagList: Int = 0
|
||||
const val tagSet: Int = 1
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,14 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Copyright 2010-2021 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 kotlin.collections.builders
|
||||
|
||||
import java.io.Externalizable
|
||||
import java.io.InvalidObjectException
|
||||
import java.io.NotSerializableException
|
||||
|
||||
internal class MapBuilder<K, V> private constructor(
|
||||
private var keysArray: Array<K>,
|
||||
private var valuesArray: Array<V>?, // allocated only when actually used, always null in pure HashSet
|
||||
@@ -12,7 +16,7 @@ internal class MapBuilder<K, V> private constructor(
|
||||
private var hashArray: IntArray,
|
||||
private var maxProbeDistance: Int,
|
||||
private var length: Int
|
||||
) : MutableMap<K, V> {
|
||||
) : MutableMap<K, V>, Serializable {
|
||||
private var hashShift: Int = computeShift(hashSize)
|
||||
|
||||
override var size: Int = 0
|
||||
@@ -22,7 +26,8 @@ internal class MapBuilder<K, V> private constructor(
|
||||
private var valuesView: MapBuilderValues<V>? = null
|
||||
private var entriesView: MapBuilderEntries<K, V>? = null
|
||||
|
||||
private var isReadOnly: Boolean = false
|
||||
internal var isReadOnly: Boolean = false
|
||||
private set
|
||||
|
||||
// ---------------------------- functions ----------------------------
|
||||
|
||||
@@ -42,6 +47,12 @@ internal class MapBuilder<K, V> private constructor(
|
||||
return this
|
||||
}
|
||||
|
||||
private fun writeReplace(): Any =
|
||||
if (isReadOnly)
|
||||
SerializedMap(this)
|
||||
else
|
||||
throw NotSerializableException("The map cannot be serialized while it is being built.")
|
||||
|
||||
override fun isEmpty(): Boolean = size == 0
|
||||
override fun containsKey(key: K): Boolean = findKey(key) >= 0
|
||||
override fun containsValue(value: V): Boolean = findValue(value) >= 0
|
||||
@@ -624,3 +635,42 @@ internal class MapBuilderEntries<K, V> internal constructor(
|
||||
return super.retainAll(elements)
|
||||
}
|
||||
}
|
||||
|
||||
private class SerializedMap(
|
||||
private var map: Map<*, *>
|
||||
) : Externalizable {
|
||||
|
||||
constructor() : this(emptyMap<Any?, Any?>()) // for deserialization
|
||||
|
||||
override fun writeExternal(output: java.io.ObjectOutput) {
|
||||
output.writeByte(0) // flags
|
||||
output.writeInt(map.size)
|
||||
for (entry in map) {
|
||||
output.writeObject(entry.key)
|
||||
output.writeObject(entry.value)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
override fun readExternal(input: java.io.ObjectInput) {
|
||||
val flags = input.readByte().toInt()
|
||||
if (flags != 0) {
|
||||
throw InvalidObjectException("Unsupported flags value: $flags")
|
||||
}
|
||||
val size = input.readInt()
|
||||
if (size < 0) throw InvalidObjectException("Illegal size value: $size.")
|
||||
map = buildMap<Any?, Any?>(size) {
|
||||
repeat(size) {
|
||||
val key = input.readObject()
|
||||
val value = input.readObject()
|
||||
put(key, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun readResolve(): Any = map
|
||||
|
||||
companion object {
|
||||
private const val serialVersionUID: Long = 0L
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Copyright 2010-2021 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 kotlin.collections.builders
|
||||
|
||||
import java.io.NotSerializableException
|
||||
|
||||
internal class SetBuilder<E> internal constructor(
|
||||
private val backing: MapBuilder<E, *>
|
||||
) : MutableSet<E>, AbstractMutableSet<E>() {
|
||||
) : MutableSet<E>, AbstractMutableSet<E>(), Serializable {
|
||||
|
||||
constructor() : this(MapBuilder<E, Nothing>())
|
||||
|
||||
@@ -18,6 +20,12 @@ internal class SetBuilder<E> internal constructor(
|
||||
return this
|
||||
}
|
||||
|
||||
private fun writeReplace(): Any =
|
||||
if (backing.isReadOnly)
|
||||
SerializedCollection(this, SerializedCollection.tagSet)
|
||||
else
|
||||
throw NotSerializableException("The set cannot be serialized while it is being built.")
|
||||
|
||||
override val size: Int get() = backing.size
|
||||
override fun isEmpty(): Boolean = backing.isEmpty()
|
||||
override fun contains(element: E): Boolean = backing.containsKey(element)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Copyright 2010-2021 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.
|
||||
*/
|
||||
|
||||
@@ -8,6 +8,8 @@ package test.collections
|
||||
import test.assertStaticAndRuntimeTypeIs
|
||||
import test.io.deserializeFromHex
|
||||
import test.io.serializeAndDeserialize
|
||||
import test.io.serializeToByteArray
|
||||
import java.io.NotSerializableException
|
||||
import java.util.*
|
||||
import kotlin.test.*
|
||||
|
||||
@@ -186,7 +188,7 @@ class CollectionJVMTest {
|
||||
val result = serializeAndDeserialize(value)
|
||||
|
||||
assertEquals(value, result)
|
||||
assertTrue(value === result)
|
||||
assertSame(value, result)
|
||||
}
|
||||
|
||||
@Test fun deserializeEmptyList() = testPersistedDeserialization(
|
||||
@@ -205,4 +207,57 @@ class CollectionJVMTest {
|
||||
val actual = deserializeFromHex<Any>(hexValue)
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun builtListIsSerializable() {
|
||||
val source = buildList<Any?> {
|
||||
repeat(5) { add(it.toLong()) }
|
||||
add("string")
|
||||
add(null)
|
||||
assertFailsWith<NotSerializableException> { serializeToByteArray(this@buildList) }
|
||||
assertFailsWith<NotSerializableException> { serializeToByteArray(this@buildList.subList(0, 2)) }
|
||||
}
|
||||
testCollectionBuilderSerialization(source)
|
||||
testCollectionBuilderSerialization(source.subList(0, source.size - 1))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun builtSetIsSerializable() {
|
||||
val source = buildSet<Any?> {
|
||||
repeat(5) { add(it.toShort()) }
|
||||
repeat(5) { add(it.toLong()) }
|
||||
add("string")
|
||||
add('c')
|
||||
add(null)
|
||||
assertFailsWith<NotSerializableException> { serializeToByteArray(this@buildSet) }
|
||||
}
|
||||
testCollectionBuilderSerialization(source)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun builtMapIsSerializable() {
|
||||
val source = buildMap<Any?, Any?> {
|
||||
repeat(5) { put(it.toShort(), it.toLong()) }
|
||||
put('s', "string")
|
||||
put(null, null)
|
||||
assertFailsWith<NotSerializableException> { serializeToByteArray(this@buildMap) }
|
||||
}
|
||||
testCollectionBuilderSerialization(source)
|
||||
}
|
||||
|
||||
|
||||
private fun testCollectionBuilderSerialization(value: Any) {
|
||||
val result = serializeAndDeserialize(value)
|
||||
assertEquals(value, result)
|
||||
assertEquals(value.javaClass, result.javaClass)
|
||||
assertReadOnly(result)
|
||||
}
|
||||
|
||||
private fun assertReadOnly(collection: Any) {
|
||||
when (collection) {
|
||||
is MutableCollection<*> -> assertFails { collection.clear() }
|
||||
is MutableMap<*, *> -> assertFails { collection.clear() }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Copyright 2010-2021 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.
|
||||
*/
|
||||
|
||||
@@ -162,6 +162,8 @@ public inline fun <T> MutableList(size: Int, init: (index: Int) -> T): MutableLi
|
||||
* The list passed as a receiver to the [builderAction] is valid only inside that function.
|
||||
* Using it outside of the function produces an unspecified behavior.
|
||||
*
|
||||
* The returned list is serializable (JVM).
|
||||
*
|
||||
* @sample samples.collections.Builders.Lists.buildListSample
|
||||
*/
|
||||
@SinceKotlin("1.3")
|
||||
@@ -185,6 +187,8 @@ internal expect inline fun <E> buildListInternal(builderAction: MutableList<E>.(
|
||||
* The list passed as a receiver to the [builderAction] is valid only inside that function.
|
||||
* Using it outside of the function produces an unspecified behavior.
|
||||
*
|
||||
* The returned list is serializable (JVM).
|
||||
*
|
||||
* [capacity] is used to hint the expected number of elements added in the [builderAction].
|
||||
*
|
||||
* @throws IllegalArgumentException if the given [capacity] is negative.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Copyright 2010-2021 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.
|
||||
*/
|
||||
|
||||
@@ -132,6 +132,8 @@ public fun <K, V> linkedMapOf(vararg pairs: Pair<K, V>): LinkedHashMap<K, V> = p
|
||||
*
|
||||
* Entries of the map are iterated in the order they were added by the [builderAction].
|
||||
*
|
||||
* The returned map is serializable (JVM).
|
||||
*
|
||||
* @sample samples.collections.Builders.Maps.buildMapSample
|
||||
*/
|
||||
@SinceKotlin("1.3")
|
||||
@@ -159,6 +161,8 @@ internal expect inline fun <K, V> buildMapInternal(builderAction: MutableMap<K,
|
||||
*
|
||||
* Entries of the map are iterated in the order they were added by the [builderAction].
|
||||
*
|
||||
* The returned map is serializable (JVM).
|
||||
*
|
||||
* @throws IllegalArgumentException if the given [capacity] is negative.
|
||||
*
|
||||
* @sample samples.collections.Builders.Maps.buildMapSample
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Copyright 2010-2021 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.
|
||||
*/
|
||||
|
||||
@@ -118,6 +118,8 @@ public fun <T : Any> setOfNotNull(vararg elements: T?): Set<T> {
|
||||
*
|
||||
* Elements of the set are iterated in the order they were added by the [builderAction].
|
||||
*
|
||||
* The returned set is serializable (JVM).
|
||||
*
|
||||
* @sample samples.collections.Builders.Sets.buildSetSample
|
||||
*/
|
||||
@SinceKotlin("1.3")
|
||||
@@ -145,6 +147,8 @@ internal expect inline fun <E> buildSetInternal(builderAction: MutableSet<E>.()
|
||||
*
|
||||
* Elements of the set are iterated in the order they were added by the [builderAction].
|
||||
*
|
||||
* The returned set is serializable (JVM).
|
||||
*
|
||||
* @throws IllegalArgumentException if the given [capacity] is negative.
|
||||
*
|
||||
* @sample samples.collections.Builders.Sets.buildSetSample
|
||||
|
||||
Reference in New Issue
Block a user