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)
|
||||
|
||||
Reference in New Issue
Block a user