Transform Kotlin's EmptyList, EmptySet and EmptyMap into Java classes (#1550)

* Transform Kotlin's EmptyList, EmptySet and EmptyMap into Java classes before serialising them.
* Transform Kotlin's EmptyList, EmptySet and EmptyMap to their unmodifiable Java equivalents.
This commit is contained in:
Chris Rankin
2017-09-26 08:33:30 +01:00
committed by GitHub
parent be0e7a8877
commit 8cc091b3e1
8 changed files with 240 additions and 36 deletions

View File

@ -3,12 +3,13 @@ package net.corda.nodeapi.internal.serialization
import com.esotericsoftware.kryo.*
import com.esotericsoftware.kryo.io.Input
import com.esotericsoftware.kryo.io.Output
import com.esotericsoftware.kryo.util.DefaultClassResolver
import com.esotericsoftware.kryo.util.MapReferenceResolver
import com.nhaarman.mockito_kotlin.mock
import com.nhaarman.mockito_kotlin.verify
import com.nhaarman.mockito_kotlin.whenever
import net.corda.core.node.services.AttachmentStorage
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.SerializationFactory
import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.*
import net.corda.core.utilities.ByteSequence
import net.corda.nodeapi.internal.AttachmentsClassLoader
import net.corda.nodeapi.internal.AttachmentsClassLoaderTests
@ -19,6 +20,7 @@ import org.junit.rules.ExpectedException
import java.lang.IllegalStateException
import java.sql.Connection
import java.util.*
import kotlin.test.*
@CordaSerializable
enum class Foo {
@ -56,7 +58,6 @@ open class SerializableViaSubInterface : SerializableSubInterface
class SerializableViaSuperSubInterface : SerializableViaSubInterface()
@CordaSerializable
class CustomSerializable : KryoSerializable {
override fun read(kryo: Kryo?, input: Input?) {
@ -79,7 +80,17 @@ class DefaultSerializableSerializer : Serializer<DefaultSerializable>() {
}
}
object EmptyWhitelist : ClassWhitelist {
override fun hasListed(type: Class<*>): Boolean = false
}
class CordaClassResolverTests {
private companion object {
val emptyListClass = listOf<Any>().javaClass
val emptySetClass = setOf<Any>().javaClass
val emptyMapClass = mapOf<Any, Any>().javaClass
}
val factory: SerializationFactory = object : SerializationFactory() {
override fun <T : Any> deserialize(byteSequence: ByteSequence, clazz: Class<T>, context: SerializationContext): T {
TODO("not implemented")
@ -88,7 +99,6 @@ class CordaClassResolverTests {
override fun <T : Any> serialize(obj: T, context: SerializationContext): SerializedBytes<T> {
TODO("not implemented")
}
}
private val emptyWhitelistContext: SerializationContext = SerializationContextImpl(KryoHeaderV0_1, this.javaClass.classLoader, EmptyWhitelist, emptyMap(), true, SerializationContext.UseCase.P2P)
@ -197,6 +207,69 @@ class CordaClassResolverTests {
resolver.getRegistration(HashSet::class.java)
}
@Test
fun `Kotlin EmptyList not registered`() {
val resolver = CordaClassResolver(allButBlacklistedContext)
assertNull(resolver.getRegistration(emptyListClass))
}
@Test
fun `Kotlin EmptyList registers as Java emptyList`() {
val javaEmptyListClass = Collections.emptyList<Any>().javaClass
val kryo = mock<Kryo>()
val resolver = CordaClassResolver(allButBlacklistedContext).apply { setKryo(kryo) }
whenever(kryo.getDefaultSerializer(javaEmptyListClass)).thenReturn(DefaultSerializableSerializer())
val registration = resolver.registerImplicit(emptyListClass)
assertNotNull(registration)
assertEquals(javaEmptyListClass, registration.type)
assertEquals(DefaultClassResolver.NAME.toInt(), registration.id)
verify(kryo).getDefaultSerializer(javaEmptyListClass)
assertEquals(registration, resolver.getRegistration(emptyListClass))
}
@Test
fun `Kotlin EmptySet not registered`() {
val resolver = CordaClassResolver(allButBlacklistedContext)
assertNull(resolver.getRegistration(emptySetClass))
}
@Test
fun `Kotlin EmptySet registers as Java emptySet`() {
val javaEmptySetClass = Collections.emptySet<Any>().javaClass
val kryo = mock<Kryo>()
val resolver = CordaClassResolver(allButBlacklistedContext).apply { setKryo(kryo) }
whenever(kryo.getDefaultSerializer(javaEmptySetClass)).thenReturn(DefaultSerializableSerializer())
val registration = resolver.registerImplicit(emptySetClass)
assertNotNull(registration)
assertEquals(javaEmptySetClass, registration.type)
assertEquals(DefaultClassResolver.NAME.toInt(), registration.id)
verify(kryo).getDefaultSerializer(javaEmptySetClass)
assertEquals(registration, resolver.getRegistration(emptySetClass))
}
@Test
fun `Kotlin EmptyMap not registered`() {
val resolver = CordaClassResolver(allButBlacklistedContext)
assertNull(resolver.getRegistration(emptyMapClass))
}
@Test
fun `Kotlin EmptyMap registers as Java emptyMap`() {
val javaEmptyMapClass = Collections.emptyMap<Any, Any>().javaClass
val kryo = mock<Kryo>()
val resolver = CordaClassResolver(allButBlacklistedContext).apply { setKryo(kryo) }
whenever(kryo.getDefaultSerializer(javaEmptyMapClass)).thenReturn(DefaultSerializableSerializer())
val registration = resolver.registerImplicit(emptyMapClass)
assertNotNull(registration)
assertEquals(javaEmptyMapClass, registration.type)
assertEquals(DefaultClassResolver.NAME.toInt(), registration.id)
verify(kryo).getDefaultSerializer(javaEmptyMapClass)
assertEquals(registration, resolver.getRegistration(emptyMapClass))
}
open class SubHashSet<E> : HashSet<E>()
@Test
fun `Check blacklisted subclass`() {

View File

@ -25,9 +25,8 @@ import org.slf4j.LoggerFactory
import java.io.ByteArrayInputStream
import java.io.InputStream
import java.time.Instant
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
import java.util.Collections
import kotlin.test.*
class KryoTests : TestDependencyInjectionBase() {
private lateinit var factory: SerializationFactory
@ -113,6 +112,27 @@ class KryoTests : TestDependencyInjectionBase() {
assertThat(deserialised).isSameAs(TestSingleton)
}
@Test
fun `check Kotlin EmptyList can be serialised`() {
val deserialisedList: List<Int> = emptyList<Int>().serialize(factory, context).deserialize(factory, context)
assertEquals(0, deserialisedList.size)
assertEquals<Any>(Collections.emptyList<Int>().javaClass, deserialisedList.javaClass)
}
@Test
fun `check Kotlin EmptySet can be serialised`() {
val deserialisedSet: Set<Int> = emptySet<Int>().serialize(factory, context).deserialize(factory, context)
assertEquals(0, deserialisedSet.size)
assertEquals<Any>(Collections.emptySet<Int>().javaClass, deserialisedSet.javaClass)
}
@Test
fun `check Kotlin EmptyMap can be serialised`() {
val deserialisedMap: Map<Int, Int> = emptyMap<Int, Int>().serialize(factory, context).deserialize(factory, context)
assertEquals(0, deserialisedMap.size)
assertEquals<Any>(Collections.emptyMap<Int, Int>().javaClass, deserialisedMap.javaClass)
}
@Test
fun `InputStream serialisation`() {
val rubbish = ByteArray(12345, { (it * it * 0.12345).toByte() })

View File

@ -1,18 +1,23 @@
package net.corda.nodeapi.internal.serialization
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
import com.esotericsoftware.kryo.Kryo
import com.esotericsoftware.kryo.util.DefaultClassResolver
import net.corda.core.serialization.*
import net.corda.node.services.statemachine.SessionData
import net.corda.testing.TestDependencyInjectionBase
import net.corda.testing.amqpSpecific
import org.assertj.core.api.Assertions
import org.junit.Assert.*
import org.junit.Test
import java.io.ByteArrayOutputStream
import java.io.NotSerializableException
import kotlin.test.assertEquals
import java.nio.charset.StandardCharsets.*
import java.util.*
class ListsSerializationTest : TestDependencyInjectionBase() {
private companion object {
val javaEmptyListClass = Collections.emptyList<Any>().javaClass
}
@Test
fun `check list can be serialized as root of serialization graph`() {
@ -23,16 +28,32 @@ class ListsSerializationTest : TestDependencyInjectionBase() {
@Test
fun `check list can be serialized as part of SessionData`() {
run {
val sessionData = SessionData(123, listOf(1))
assertEqualAfterRoundTripSerialization(sessionData)
}
run {
val sessionData = SessionData(123, listOf(1, 2))
assertEqualAfterRoundTripSerialization(sessionData)
}
run {
val sessionData = SessionData(123, emptyList<Int>())
assertEqualAfterRoundTripSerialization(sessionData)
}
}
@Test
fun `check empty list serialises as Java emptyList`() {
val nameID = 0
val serializedForm = emptyList<Int>().serialize()
val output = ByteArrayOutputStream().apply {
write(KryoHeaderV0_1.bytes)
write(DefaultClassResolver.NAME + 2)
write(nameID)
write(javaEmptyListClass.name.toAscii())
write(Kryo.NOT_NULL.toInt())
}
assertArrayEquals(output.toByteArray(), serializedForm.bytes)
}
@CordaSerializable
@ -55,4 +76,8 @@ internal inline fun<reified T : Any> assertEqualAfterRoundTripSerialization(obj:
val deserializedInstance = serializedForm.deserialize()
assertEquals(obj, deserializedInstance)
}
}
internal fun String.toAscii(): ByteArray = toByteArray(US_ASCII).apply {
this[lastIndex] = (this[lastIndex] + 0x80).toByte()
}

View File

@ -1,18 +1,25 @@
package net.corda.nodeapi.internal.serialization
import com.esotericsoftware.kryo.Kryo
import com.esotericsoftware.kryo.util.DefaultClassResolver
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.serialize
import net.corda.node.services.statemachine.SessionData
import net.corda.testing.TestDependencyInjectionBase
import net.corda.testing.amqpSpecific
import org.assertj.core.api.Assertions
import org.junit.Assert.assertArrayEquals
import org.junit.Test
import org.bouncycastle.asn1.x500.X500Name
import java.io.ByteArrayOutputStream
import java.io.NotSerializableException
import java.util.*
class MapsSerializationTest : TestDependencyInjectionBase() {
private val smallMap = mapOf("foo" to "bar", "buzz" to "bull")
private companion object {
val javaEmptyMapClass = Collections.emptyMap<Any, Any>().javaClass
val smallMap = mapOf("foo" to "bar", "buzz" to "bull")
}
@Test
fun `check EmptyMap serialization`() = amqpSpecific<MapsSerializationTest>("kotlin.collections.EmptyMap is not enabled for Kryo serialization") {
@ -54,4 +61,18 @@ class MapsSerializationTest : TestDependencyInjectionBase() {
MyKey(10.0) to MyValue(X500Name("CN=ten")))
assertEqualAfterRoundTripSerialization(myMap)
}
}
@Test
fun `check empty map serialises as Java emptytMap`() {
val nameID = 0
val serializedForm = emptyMap<Int, Int>().serialize()
val output = ByteArrayOutputStream().apply {
write(KryoHeaderV0_1.bytes)
write(DefaultClassResolver.NAME + 2)
write(nameID)
write(javaEmptyMapClass.name.toAscii())
write(Kryo.NOT_NULL.toInt())
}
assertArrayEquals(output.toByteArray(), serializedForm.bytes)
}
}

View File

@ -0,0 +1,54 @@
package net.corda.nodeapi.internal.serialization
import com.esotericsoftware.kryo.Kryo
import com.esotericsoftware.kryo.util.DefaultClassResolver
import net.corda.core.serialization.serialize
import net.corda.node.services.statemachine.SessionData
import net.corda.testing.TestDependencyInjectionBase
import org.junit.Assert.*
import org.junit.Test
import java.io.ByteArrayOutputStream
import java.util.*
class SetsSerializationTest : TestDependencyInjectionBase() {
private companion object {
val javaEmptySetClass = Collections.emptySet<Any>().javaClass
}
@Test
fun `check set can be serialized as root of serialization graph`() {
assertEqualAfterRoundTripSerialization(emptySet<Int>())
assertEqualAfterRoundTripSerialization(setOf(1))
assertEqualAfterRoundTripSerialization(setOf(1, 2))
}
@Test
fun `check set can be serialized as part of SessionData`() {
run {
val sessionData = SessionData(123, setOf(1))
assertEqualAfterRoundTripSerialization(sessionData)
}
run {
val sessionData = SessionData(123, setOf(1, 2))
assertEqualAfterRoundTripSerialization(sessionData)
}
run {
val sessionData = SessionData(123, emptySet<Int>())
assertEqualAfterRoundTripSerialization(sessionData)
}
}
@Test
fun `check empty set serialises as Java emptySet`() {
val nameID = 0
val serializedForm = emptySet<Int>().serialize()
val output = ByteArrayOutputStream().apply {
write(KryoHeaderV0_1.bytes)
write(DefaultClassResolver.NAME + 2)
write(nameID)
write(javaEmptySetClass.name.toAscii())
write(Kryo.NOT_NULL.toInt())
}
assertArrayEquals(output.toByteArray(), serializedForm.bytes)
}
}