From 845122a0220d6c80b7d208bee53839fcde3f5e05 Mon Sep 17 00:00:00 2001 From: Nicolas Vollmar Date: Mon, 28 Feb 2022 14:33:52 +0100 Subject: [PATCH 1/8] Sets Scala 3 as default --- build.sbt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index 7b76fd5..b9ad267 100644 --- a/build.sbt +++ b/build.sbt @@ -7,8 +7,8 @@ val typesafe = "Typesafe Repository" at "https://repo.typesafe.com/typesafe/rele val typesafeSnapshot = "Typesafe Snapshots Repository" at "https://repo.typesafe.com/typesafe/snapshots/" val sonatypeSnapshot = "Sonatype Snapshots Repository" at "https://oss.sonatype.org/content/repositories/snapshots/" -val mainScalaVersion = "2.13.8" -val secondayScalaVersions = Seq("2.12.15", "3.0.2") // note: Scala 3.1 is not forward compatible - publishing with 3.1 would force users to Scala 3.1 +val mainScalaVersion = "3.0.2" // note: Scala 3.1 is not forward compatible - publishing with 3.1 would force users to Scala 3.1 +val secondayScalaVersions = Seq("2.12.15", "2.13.8") val kryoVersion = "5.3.0" val defaultAkkaVersion = "2.6.18" From 4def01c6241990009b86b93a243e2add065b2814 Mon Sep 17 00:00:00 2001 From: Nicolas Vollmar Date: Mon, 28 Feb 2022 14:35:26 +0100 Subject: [PATCH 2/8] Adds support for Scala 3 specific files --- .../kryo/ScalaVersionSerializers.scala | 14 +++++ .../scala/ScalaCollectionSerializer.scala | 51 +++++++++++++++++++ .../scala/ScalaEnumSerializer.scala | 22 ++++++++ .../scala/ScalaVersionRegistry.scala | 16 ++++++ build.sbt | 6 ++- 5 files changed, 107 insertions(+), 2 deletions(-) create mode 100644 akka-kryo-serialization/src/main/scala-3/io/altoo/akka/serialization/kryo/ScalaVersionSerializers.scala create mode 100644 akka-kryo-serialization/src/main/scala-3/io/altoo/akka/serialization/kryo/serializer/scala/ScalaCollectionSerializer.scala create mode 100644 akka-kryo-serialization/src/main/scala-3/io/altoo/akka/serialization/kryo/serializer/scala/ScalaEnumSerializer.scala create mode 100644 akka-kryo-serialization/src/test/scala-3/io/altoo/akka/serialization/kryo/serializer/scala/ScalaVersionRegistry.scala diff --git a/akka-kryo-serialization/src/main/scala-3/io/altoo/akka/serialization/kryo/ScalaVersionSerializers.scala b/akka-kryo-serialization/src/main/scala-3/io/altoo/akka/serialization/kryo/ScalaVersionSerializers.scala new file mode 100644 index 0000000..1f085da --- /dev/null +++ b/akka-kryo-serialization/src/main/scala-3/io/altoo/akka/serialization/kryo/ScalaVersionSerializers.scala @@ -0,0 +1,14 @@ +package io.altoo.akka.serialization.kryo + +import com.esotericsoftware.kryo.Kryo +import io.altoo.akka.serialization.kryo.serializer.scala.{ScalaCollectionSerializer, ScalaEnumSerializer, ScalaImmutableMapSerializer} + +private[kryo] object ScalaVersionSerializers { + def mapAndSet(kryo: Kryo): Unit = { + kryo.addDefaultSerializer(classOf[scala.collection.MapFactory[_root_.scala.collection.Map]], classOf[ScalaImmutableMapSerializer]) + } + + def iterable(kryo: Kryo): Unit = { + kryo.addDefaultSerializer(classOf[scala.collection.Iterable[_]], classOf[ScalaCollectionSerializer]) + } +} diff --git a/akka-kryo-serialization/src/main/scala-3/io/altoo/akka/serialization/kryo/serializer/scala/ScalaCollectionSerializer.scala b/akka-kryo-serialization/src/main/scala-3/io/altoo/akka/serialization/kryo/serializer/scala/ScalaCollectionSerializer.scala new file mode 100644 index 0000000..ea4a61f --- /dev/null +++ b/akka-kryo-serialization/src/main/scala-3/io/altoo/akka/serialization/kryo/serializer/scala/ScalaCollectionSerializer.scala @@ -0,0 +1,51 @@ +/** + * ***************************************************************************** + * Copyright 2012 Roman Levenstein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * **************************************************************************** + */ + +package io.altoo.akka.serialization.kryo.serializer.scala + +import com.esotericsoftware.kryo.io.{Input, Output} +import com.esotericsoftware.kryo.{Kryo, Serializer} + +/** + * Generic serializer for traversable collections + * + * @author romix + */ +class ScalaCollectionSerializer() extends Serializer[Iterable[_]] { + + override def read(kryo: Kryo, input: Input, typ: Class[_ <: Iterable[_]]): Iterable[_] = { + val len = input.readInt(true) + val inst = kryo.newInstance(typ) + val coll = inst.iterableFactory.newBuilder[Any] + + var i = 0 + while (i < len) { + coll += kryo.readClassAndObject(input) + i += 1 + } + coll.result() + } + + override def write(kryo: Kryo, output: Output, obj: Iterable[_]): Unit = { + val collection: Iterable[_] = obj + val len = collection.size + output.writeInt(len, true) + collection.foreach { (e: Any) => kryo.writeClassAndObject(output, e) } + } +} + diff --git a/akka-kryo-serialization/src/main/scala-3/io/altoo/akka/serialization/kryo/serializer/scala/ScalaEnumSerializer.scala b/akka-kryo-serialization/src/main/scala-3/io/altoo/akka/serialization/kryo/serializer/scala/ScalaEnumSerializer.scala new file mode 100644 index 0000000..b29fdc7 --- /dev/null +++ b/akka-kryo-serialization/src/main/scala-3/io/altoo/akka/serialization/kryo/serializer/scala/ScalaEnumSerializer.scala @@ -0,0 +1,22 @@ +package io.altoo.akka.serialization.kryo.serializer.scala + +import com.esotericsoftware.kryo.{Kryo, Serializer} +import com.esotericsoftware.kryo.io.{Input, Output} + +import scala.runtime.EnumValue + +class ScalaEnumSerializer[T <: EnumValue] extends Serializer[T] { + + def read(kryo: Kryo, input: Input, typ: Class[_ <: T]): T = { + val clazz = kryo.readClass(input).getType + val ordinal = input.readInt() + clazz.getDeclaredMethod("fromOrdinal", Integer.TYPE).invoke(null, ordinal).asInstanceOf[T] + } + + def write(kryo: Kryo, output: Output, obj: T): Unit = { + val enumClass = obj.getClass.getSuperclass + val ordinal = obj.getClass.getDeclaredMethod("ordinal").invoke(obj).asInstanceOf[Int] + kryo.writeClass(output, enumClass) + output.writeInt(ordinal) + } +} diff --git a/akka-kryo-serialization/src/test/scala-3/io/altoo/akka/serialization/kryo/serializer/scala/ScalaVersionRegistry.scala b/akka-kryo-serialization/src/test/scala-3/io/altoo/akka/serialization/kryo/serializer/scala/ScalaVersionRegistry.scala new file mode 100644 index 0000000..1deb666 --- /dev/null +++ b/akka-kryo-serialization/src/test/scala-3/io/altoo/akka/serialization/kryo/serializer/scala/ScalaVersionRegistry.scala @@ -0,0 +1,16 @@ +package io.altoo.akka.serialization.kryo.serializer.scala + +import com.esotericsoftware.kryo.Kryo + +object ScalaVersionRegistry { + final val immutableHashMapImpl = "scala.collection.immutable.HashMap" + final val immutableHashSetImpl = "scala.collection.immutable.HashSet" + + def registerHashMap(kryo: Kryo): Unit = { + kryo.register(classOf[scala.collection.immutable.HashMap[_, _]], 40) + } + + def registerHashSet(kryo: Kryo): Unit = { + kryo.register(classOf[scala.collection.immutable.HashSet[_]], 41) + } +} diff --git a/build.sbt b/build.sbt index b9ad267..bbb3ca9 100644 --- a/build.sbt +++ b/build.sbt @@ -41,13 +41,15 @@ lazy val core: Project = Project("akka-kryo-serialization", file("akka-kryo-seri .settings(Compile / unmanagedSourceDirectories += { scalaBinaryVersion.value match { case "2.12" => baseDirectory.value / "src" / "main" / "scala-2.12" - case _ => baseDirectory.value / "src" / "main" / "scala-2.13" + case "2.13" => baseDirectory.value / "src" / "main" / "scala-2.13" + case _ => baseDirectory.value / "src" / "main" / "scala-3" } }) .settings(Test / unmanagedSourceDirectories += { scalaBinaryVersion.value match { case "2.12" => baseDirectory.value / "src" / "test" / "scala-2.12" - case _ => baseDirectory.value / "src" / "test" / "scala-2.13" + case "2.13" => baseDirectory.value / "src" / "test" / "scala-2.13" + case _ => baseDirectory.value / "src" / "test" / "scala-3" } }) From fcd9399b5a6043a7ded86915175298b7e195c0f1 Mon Sep 17 00:00:00 2001 From: Nicolas Vollmar Date: Mon, 28 Feb 2022 14:35:52 +0100 Subject: [PATCH 3/8] Adds Scala 3 enum serializer --- .../kryo/ScalaVersionSerializers.scala | 2 + .../kryo/ScalaVersionSerializers.scala | 2 + .../kryo/ScalaVersionSerializers.scala | 4 ++ .../kryo/DefaultKryoInitializer.scala | 1 + .../scala/EnumSerializationTest.scala | 43 +++++++++++++++++++ 5 files changed, 52 insertions(+) create mode 100644 akka-kryo-serialization/src/test/scala-3/io/altoo/akka/serialization/kryo/serializer/scala/EnumSerializationTest.scala diff --git a/akka-kryo-serialization/src/main/scala-2.12/io/altoo/akka/serialization/kryo/ScalaVersionSerializers.scala b/akka-kryo-serialization/src/main/scala-2.12/io/altoo/akka/serialization/kryo/ScalaVersionSerializers.scala index c465280..c1bb89b 100644 --- a/akka-kryo-serialization/src/main/scala-2.12/io/altoo/akka/serialization/kryo/ScalaVersionSerializers.scala +++ b/akka-kryo-serialization/src/main/scala-2.12/io/altoo/akka/serialization/kryo/ScalaVersionSerializers.scala @@ -12,4 +12,6 @@ private[kryo] object ScalaVersionSerializers { def iterable(kryo: Kryo) = { kryo.addDefaultSerializer(classOf[scala.collection.Traversable[_]], classOf[ScalaCollectionSerializer]) } + + def enums(kryo: Kryo): Unit = () // Scala 3 only } diff --git a/akka-kryo-serialization/src/main/scala-2.13/io/altoo/akka/serialization/kryo/ScalaVersionSerializers.scala b/akka-kryo-serialization/src/main/scala-2.13/io/altoo/akka/serialization/kryo/ScalaVersionSerializers.scala index 768f59a..03cd990 100644 --- a/akka-kryo-serialization/src/main/scala-2.13/io/altoo/akka/serialization/kryo/ScalaVersionSerializers.scala +++ b/akka-kryo-serialization/src/main/scala-2.13/io/altoo/akka/serialization/kryo/ScalaVersionSerializers.scala @@ -11,4 +11,6 @@ private[kryo] object ScalaVersionSerializers { def iterable(kryo: Kryo): Unit = { kryo.addDefaultSerializer(classOf[scala.collection.Iterable[_]], classOf[ScalaCollectionSerializer]) } + + def enums(kryo: Kryo): Unit = () // Scala 3 only } diff --git a/akka-kryo-serialization/src/main/scala-3/io/altoo/akka/serialization/kryo/ScalaVersionSerializers.scala b/akka-kryo-serialization/src/main/scala-3/io/altoo/akka/serialization/kryo/ScalaVersionSerializers.scala index 1f085da..b01eb72 100644 --- a/akka-kryo-serialization/src/main/scala-3/io/altoo/akka/serialization/kryo/ScalaVersionSerializers.scala +++ b/akka-kryo-serialization/src/main/scala-3/io/altoo/akka/serialization/kryo/ScalaVersionSerializers.scala @@ -11,4 +11,8 @@ private[kryo] object ScalaVersionSerializers { def iterable(kryo: Kryo): Unit = { kryo.addDefaultSerializer(classOf[scala.collection.Iterable[_]], classOf[ScalaCollectionSerializer]) } + + def enums(kryo: Kryo): Unit = { + kryo.addDefaultSerializer(classOf[scala.runtime.EnumValue], classOf[ScalaEnumSerializer[scala.runtime.EnumValue]]) + } } diff --git a/akka-kryo-serialization/src/main/scala/io/altoo/akka/serialization/kryo/DefaultKryoInitializer.scala b/akka-kryo-serialization/src/main/scala/io/altoo/akka/serialization/kryo/DefaultKryoInitializer.scala index 4efa0e3..650ad74 100644 --- a/akka-kryo-serialization/src/main/scala/io/altoo/akka/serialization/kryo/DefaultKryoInitializer.scala +++ b/akka-kryo-serialization/src/main/scala/io/altoo/akka/serialization/kryo/DefaultKryoInitializer.scala @@ -70,6 +70,7 @@ class DefaultKryoInitializer { // Map/Set Factories ScalaVersionSerializers.mapAndSet(kryo) ScalaVersionSerializers.iterable(kryo) + ScalaVersionSerializers.enums(kryo) } /** diff --git a/akka-kryo-serialization/src/test/scala-3/io/altoo/akka/serialization/kryo/serializer/scala/EnumSerializationTest.scala b/akka-kryo-serialization/src/test/scala-3/io/altoo/akka/serialization/kryo/serializer/scala/EnumSerializationTest.scala new file mode 100644 index 0000000..d15f07d --- /dev/null +++ b/akka-kryo-serialization/src/test/scala-3/io/altoo/akka/serialization/kryo/serializer/scala/EnumSerializationTest.scala @@ -0,0 +1,43 @@ +package io.altoo.akka.serialization.kryo.serializer.scala + +import com.esotericsoftware.kryo.util.{DefaultClassResolver, ListReferenceResolver} +import io.altoo.akka.serialization.kryo.testkit.{AbstractKryoTest, KryoSerializationTesting} +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers +import io.altoo.akka.serialization.kryo.ScalaVersionSerializers + +object EnumSerializationTest { + enum Sample(val name: String, val value: Int) { + case A extends Sample("a", 1) + case B extends Sample("b", 2) + case C extends Sample("c", 3) + } + + case class EmbeddedEnum(sample: Sample) { + def this() = this(null) + } +} + +class EnumSerializationTest extends AnyFlatSpec with Matchers with KryoSerializationTesting { + import EnumSerializationTest._ + + val kryo = new ScalaKryo(new DefaultClassResolver(), new ListReferenceResolver()) + kryo.setRegistrationRequired(false) + kryo.addDefaultSerializer(classOf[scala.runtime.EnumValue], new ScalaEnumSerializer[scala.runtime.EnumValue]) + + + behavior of "Kryo serialization" + + it should "reoundtrip enum" in { + kryo.setRegistrationRequired(false) + + testSerializationOf(Sample.B) + } + + it should "reoundtrip embedded enum" in { + kryo.setRegistrationRequired(false) + kryo.register(classOf[EmbeddedEnum], 46) + + testSerializationOf(EmbeddedEnum(Sample.C)) + } +} From 23327da840dc7d546e07a35a0f242f6f428d2ace Mon Sep 17 00:00:00 2001 From: Nicolas Vollmar Date: Mon, 28 Feb 2022 14:36:08 +0100 Subject: [PATCH 4/8] Small code improvements --- .../akka/serialization/kryo/serializer/scala/ScalaKryo.scala | 2 +- .../kryo/serializer/scala/ScalaObjectSerializer.scala | 2 +- .../kryo/serializer/scala/TupleSerializationTest.scala | 4 +++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/akka-kryo-serialization/src/main/scala/io/altoo/akka/serialization/kryo/serializer/scala/ScalaKryo.scala b/akka-kryo-serialization/src/main/scala/io/altoo/akka/serialization/kryo/serializer/scala/ScalaKryo.scala index daa7dc2..1ff65a5 100644 --- a/akka-kryo-serialization/src/main/scala/io/altoo/akka/serialization/kryo/serializer/scala/ScalaKryo.scala +++ b/akka-kryo-serialization/src/main/scala/io/altoo/akka/serialization/kryo/serializer/scala/ScalaKryo.scala @@ -23,7 +23,7 @@ import com.esotericsoftware.kryo.serializers.FieldSerializer class ScalaKryo(classResolver: ClassResolver, referenceResolver: ReferenceResolver) extends Kryo(classResolver, referenceResolver) { - lazy val objSer = new ObjectSerializer[AnyRef] + lazy val objSer = new ScalaObjectSerializer[AnyRef] override def getDefaultSerializer(typ: Class[_]): Serializer[_] = { if(isSingleton(typ)) { diff --git a/akka-kryo-serialization/src/main/scala/io/altoo/akka/serialization/kryo/serializer/scala/ScalaObjectSerializer.scala b/akka-kryo-serialization/src/main/scala/io/altoo/akka/serialization/kryo/serializer/scala/ScalaObjectSerializer.scala index 5a32938..e3c7cca 100644 --- a/akka-kryo-serialization/src/main/scala/io/altoo/akka/serialization/kryo/serializer/scala/ScalaObjectSerializer.scala +++ b/akka-kryo-serialization/src/main/scala/io/altoo/akka/serialization/kryo/serializer/scala/ScalaObjectSerializer.scala @@ -27,7 +27,7 @@ import scala.collection.mutable.{Map => MMap} import scala.util.control.Exception.allCatch // Stolen with pride from Chill ;-) -class ObjectSerializer[T] extends Serializer[T] { +class ScalaObjectSerializer[T] extends Serializer[T] { private val cachedObj = MMap[Class[_], Option[T]]() // Does nothing diff --git a/akka-kryo-serialization/src/test/scala/io/altoo/akka/serialization/kryo/serializer/scala/TupleSerializationTest.scala b/akka-kryo-serialization/src/test/scala/io/altoo/akka/serialization/kryo/serializer/scala/TupleSerializationTest.scala index 3b24e83..54a31b8 100644 --- a/akka-kryo-serialization/src/test/scala/io/altoo/akka/serialization/kryo/serializer/scala/TupleSerializationTest.scala +++ b/akka-kryo-serialization/src/test/scala/io/altoo/akka/serialization/kryo/serializer/scala/TupleSerializationTest.scala @@ -9,7 +9,9 @@ class TupleSerializationTest extends AbstractKryoTest { type IntTuple6 = (Int, Int, Int, Int, Int, Int) - "Kryo" should "roundtrip tuples" in { + behavior of "Kryo serialization" + + it should "roundtrip tuples" in { kryo.setRegistrationRequired(false) kryo.register(classOf[scala.Tuple1[Any]], 45) kryo.register(classOf[scala.Tuple2[Any, Any]], 46) From c087408d9d96eca9ed16ce8afd59d33d54bc9998 Mon Sep 17 00:00:00 2001 From: Nicolas Vollmar Date: Mon, 28 Feb 2022 14:41:05 +0100 Subject: [PATCH 5/8] Adds note for Scala 3 --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 21121c5..f36a0e2 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,8 @@ akka-kryo-serialization - kryo-based serializers for Scala and Akka [![Full test prior to release](https://github.com/altoo-ag/akka-kryo-serialization/actions/workflows/fullTest.yml/badge.svg)](https://github.com/altoo-ag/akka-kryo-serialization/actions/workflows/fullTest.yml) [![Latest version](https://index.scala-lang.org/altoo-ag/akka-kryo-serialization/akka-kryo-serialization/latest.svg)](https://index.scala-lang.org/altoo-ag/akka-kryo-serialization/akka-kryo-serialization) +:warning: **We found issues serializing Scala 3 Enums. If you use akka-kryo-serialization with Scala 3 you should upgrade to 2.4.1 asap.** + This library provides custom Kryo-based serializers for Scala and Akka. It can be used for more efficient akka actor's remoting. From 5ab82972bea6ca8aeeb866a9a1cd7328beb4e60b Mon Sep 17 00:00:00 2001 From: Nicolas Vollmar Date: Tue, 1 Mar 2022 06:55:53 +0100 Subject: [PATCH 6/8] Switches to value instead of ordinal for serialization --- .../kryo/serializer/scala/ScalaEnumSerializer.scala | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/akka-kryo-serialization/src/main/scala-3/io/altoo/akka/serialization/kryo/serializer/scala/ScalaEnumSerializer.scala b/akka-kryo-serialization/src/main/scala-3/io/altoo/akka/serialization/kryo/serializer/scala/ScalaEnumSerializer.scala index b29fdc7..1712dbb 100644 --- a/akka-kryo-serialization/src/main/scala-3/io/altoo/akka/serialization/kryo/serializer/scala/ScalaEnumSerializer.scala +++ b/akka-kryo-serialization/src/main/scala-3/io/altoo/akka/serialization/kryo/serializer/scala/ScalaEnumSerializer.scala @@ -9,14 +9,15 @@ class ScalaEnumSerializer[T <: EnumValue] extends Serializer[T] { def read(kryo: Kryo, input: Input, typ: Class[_ <: T]): T = { val clazz = kryo.readClass(input).getType - val ordinal = input.readInt() - clazz.getDeclaredMethod("fromOrdinal", Integer.TYPE).invoke(null, ordinal).asInstanceOf[T] + val name = input.readString() + // using value instead of ordinal to make serialization more stable, e.g. allowing reordering without breaking compatibility + clazz.getDeclaredMethod("valueOf", classOf[String]).invoke(null, name).asInstanceOf[T] } def write(kryo: Kryo, output: Output, obj: T): Unit = { val enumClass = obj.getClass.getSuperclass - val ordinal = obj.getClass.getDeclaredMethod("ordinal").invoke(obj).asInstanceOf[Int] + val name = obj.getClass.getDeclaredMethod("productPrefix").invoke(obj).asInstanceOf[String] kryo.writeClass(output, enumClass) - output.writeInt(ordinal) + output.writeString(name) } } From 68e0286a40dac4e8cba36750d04b8f8d78b50553 Mon Sep 17 00:00:00 2001 From: Nicolas Vollmar Date: Tue, 1 Mar 2022 08:16:45 +0100 Subject: [PATCH 7/8] Renames serializer to match Kryo Java enum serializer --- .../akka/serialization/kryo/ScalaVersionSerializers.scala | 4 ++-- ...EnumSerializer.scala => ScalaEnumNameSerializer.scala} | 3 ++- ...izationTest.scala => ScalaEnumSerializationTest.scala} | 8 ++++---- 3 files changed, 8 insertions(+), 7 deletions(-) rename akka-kryo-serialization/src/main/scala-3/io/altoo/akka/serialization/kryo/serializer/scala/{ScalaEnumSerializer.scala => ScalaEnumNameSerializer.scala} (80%) rename akka-kryo-serialization/src/test/scala-3/io/altoo/akka/serialization/kryo/serializer/scala/{EnumSerializationTest.scala => ScalaEnumSerializationTest.scala} (83%) diff --git a/akka-kryo-serialization/src/main/scala-3/io/altoo/akka/serialization/kryo/ScalaVersionSerializers.scala b/akka-kryo-serialization/src/main/scala-3/io/altoo/akka/serialization/kryo/ScalaVersionSerializers.scala index b01eb72..715bdab 100644 --- a/akka-kryo-serialization/src/main/scala-3/io/altoo/akka/serialization/kryo/ScalaVersionSerializers.scala +++ b/akka-kryo-serialization/src/main/scala-3/io/altoo/akka/serialization/kryo/ScalaVersionSerializers.scala @@ -1,7 +1,7 @@ package io.altoo.akka.serialization.kryo import com.esotericsoftware.kryo.Kryo -import io.altoo.akka.serialization.kryo.serializer.scala.{ScalaCollectionSerializer, ScalaEnumSerializer, ScalaImmutableMapSerializer} +import io.altoo.akka.serialization.kryo.serializer.scala.{ScalaCollectionSerializer, ScalaEnumNameSerializer, ScalaImmutableMapSerializer} private[kryo] object ScalaVersionSerializers { def mapAndSet(kryo: Kryo): Unit = { @@ -13,6 +13,6 @@ private[kryo] object ScalaVersionSerializers { } def enums(kryo: Kryo): Unit = { - kryo.addDefaultSerializer(classOf[scala.runtime.EnumValue], classOf[ScalaEnumSerializer[scala.runtime.EnumValue]]) + kryo.addDefaultSerializer(classOf[scala.runtime.EnumValue], classOf[ScalaEnumNameSerializer[scala.runtime.EnumValue]]) } } diff --git a/akka-kryo-serialization/src/main/scala-3/io/altoo/akka/serialization/kryo/serializer/scala/ScalaEnumSerializer.scala b/akka-kryo-serialization/src/main/scala-3/io/altoo/akka/serialization/kryo/serializer/scala/ScalaEnumNameSerializer.scala similarity index 80% rename from akka-kryo-serialization/src/main/scala-3/io/altoo/akka/serialization/kryo/serializer/scala/ScalaEnumSerializer.scala rename to akka-kryo-serialization/src/main/scala-3/io/altoo/akka/serialization/kryo/serializer/scala/ScalaEnumNameSerializer.scala index 1712dbb..1ac49c1 100644 --- a/akka-kryo-serialization/src/main/scala-3/io/altoo/akka/serialization/kryo/serializer/scala/ScalaEnumSerializer.scala +++ b/akka-kryo-serialization/src/main/scala-3/io/altoo/akka/serialization/kryo/serializer/scala/ScalaEnumNameSerializer.scala @@ -5,7 +5,8 @@ import com.esotericsoftware.kryo.io.{Input, Output} import scala.runtime.EnumValue -class ScalaEnumSerializer[T <: EnumValue] extends Serializer[T] { +/** Serializes enums using the enum's name. This prevents invalidating previously serialized bytes when the enum order changes */ +class ScalaEnumNameSerializer[T <: EnumValue] extends Serializer[T] { def read(kryo: Kryo, input: Input, typ: Class[_ <: T]): T = { val clazz = kryo.readClass(input).getType diff --git a/akka-kryo-serialization/src/test/scala-3/io/altoo/akka/serialization/kryo/serializer/scala/EnumSerializationTest.scala b/akka-kryo-serialization/src/test/scala-3/io/altoo/akka/serialization/kryo/serializer/scala/ScalaEnumSerializationTest.scala similarity index 83% rename from akka-kryo-serialization/src/test/scala-3/io/altoo/akka/serialization/kryo/serializer/scala/EnumSerializationTest.scala rename to akka-kryo-serialization/src/test/scala-3/io/altoo/akka/serialization/kryo/serializer/scala/ScalaEnumSerializationTest.scala index d15f07d..4a3b075 100644 --- a/akka-kryo-serialization/src/test/scala-3/io/altoo/akka/serialization/kryo/serializer/scala/EnumSerializationTest.scala +++ b/akka-kryo-serialization/src/test/scala-3/io/altoo/akka/serialization/kryo/serializer/scala/ScalaEnumSerializationTest.scala @@ -6,7 +6,7 @@ import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers import io.altoo.akka.serialization.kryo.ScalaVersionSerializers -object EnumSerializationTest { +object ScalaEnumSerializationTest { enum Sample(val name: String, val value: Int) { case A extends Sample("a", 1) case B extends Sample("b", 2) @@ -18,12 +18,12 @@ object EnumSerializationTest { } } -class EnumSerializationTest extends AnyFlatSpec with Matchers with KryoSerializationTesting { - import EnumSerializationTest._ +class ScalaEnumSerializationTest extends AnyFlatSpec with Matchers with KryoSerializationTesting { + import ScalaEnumSerializationTest._ val kryo = new ScalaKryo(new DefaultClassResolver(), new ListReferenceResolver()) kryo.setRegistrationRequired(false) - kryo.addDefaultSerializer(classOf[scala.runtime.EnumValue], new ScalaEnumSerializer[scala.runtime.EnumValue]) + kryo.addDefaultSerializer(classOf[scala.runtime.EnumValue], new ScalaEnumNameSerializer[scala.runtime.EnumValue]) behavior of "Kryo serialization" From a8757a2f566f52cda3efa865d3c719b99d7dce51 Mon Sep 17 00:00:00 2001 From: Nicolas Vollmar Date: Tue, 1 Mar 2022 10:44:35 +0100 Subject: [PATCH 8/8] Adds fix and test for external enums --- .../kryo/serializer/scala/ScalaEnumNameSerializer.scala | 4 +++- .../kryo/serializer/scala/ScalaEnumSerializationTest.scala | 6 ++++++ .../src/test/scala-3/io/altoo/external/ExternalEnum.scala | 7 +++++++ 3 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 akka-kryo-serialization/src/test/scala-3/io/altoo/external/ExternalEnum.scala diff --git a/akka-kryo-serialization/src/main/scala-3/io/altoo/akka/serialization/kryo/serializer/scala/ScalaEnumNameSerializer.scala b/akka-kryo-serialization/src/main/scala-3/io/altoo/akka/serialization/kryo/serializer/scala/ScalaEnumNameSerializer.scala index 1ac49c1..37b2755 100644 --- a/akka-kryo-serialization/src/main/scala-3/io/altoo/akka/serialization/kryo/serializer/scala/ScalaEnumNameSerializer.scala +++ b/akka-kryo-serialization/src/main/scala-3/io/altoo/akka/serialization/kryo/serializer/scala/ScalaEnumNameSerializer.scala @@ -17,7 +17,9 @@ class ScalaEnumNameSerializer[T <: EnumValue] extends Serializer[T] { def write(kryo: Kryo, output: Output, obj: T): Unit = { val enumClass = obj.getClass.getSuperclass - val name = obj.getClass.getDeclaredMethod("productPrefix").invoke(obj).asInstanceOf[String] + val productPrefixMethod = obj.getClass.getDeclaredMethod("productPrefix") + if (!productPrefixMethod.canAccess(obj)) productPrefixMethod.setAccessible(true) + val name = productPrefixMethod.invoke(obj).asInstanceOf[String] kryo.writeClass(output, enumClass) output.writeString(name) } diff --git a/akka-kryo-serialization/src/test/scala-3/io/altoo/akka/serialization/kryo/serializer/scala/ScalaEnumSerializationTest.scala b/akka-kryo-serialization/src/test/scala-3/io/altoo/akka/serialization/kryo/serializer/scala/ScalaEnumSerializationTest.scala index 4a3b075..8fd771e 100644 --- a/akka-kryo-serialization/src/test/scala-3/io/altoo/akka/serialization/kryo/serializer/scala/ScalaEnumSerializationTest.scala +++ b/akka-kryo-serialization/src/test/scala-3/io/altoo/akka/serialization/kryo/serializer/scala/ScalaEnumSerializationTest.scala @@ -34,6 +34,12 @@ class ScalaEnumSerializationTest extends AnyFlatSpec with Matchers with KryoSer testSerializationOf(Sample.B) } + it should "reoundtrip external enum" in { + kryo.setRegistrationRequired(false) + + testSerializationOf(io.altoo.external.ExternalEnum.A) + } + it should "reoundtrip embedded enum" in { kryo.setRegistrationRequired(false) kryo.register(classOf[EmbeddedEnum], 46) diff --git a/akka-kryo-serialization/src/test/scala-3/io/altoo/external/ExternalEnum.scala b/akka-kryo-serialization/src/test/scala-3/io/altoo/external/ExternalEnum.scala new file mode 100644 index 0000000..2a03689 --- /dev/null +++ b/akka-kryo-serialization/src/test/scala-3/io/altoo/external/ExternalEnum.scala @@ -0,0 +1,7 @@ +package io.altoo.external + +import io.altoo.akka.serialization.kryo.serializer.scala.ScalaEnumSerializationTest.Sample + +enum ExternalEnum(val name: String) { + case A extends ExternalEnum("a") +} \ No newline at end of file