From 3d559292233ae6734fef103703e6972eb39b7840 Mon Sep 17 00:00:00 2001 From: Shawn Yang Date: Wed, 25 Sep 2024 00:47:28 -0700 Subject: [PATCH] feat(scala): optimize scala class serialization (#1853) ## What does this PR do? - Optimize scala Iterable type serialization - Reduce type name writing for msot scala collection and factory types - Add serializers for ToFactory type ## Related issues ## Does this PR introduce any user-facing change? - [ ] Does this PR introduce any public API change? - [ ] Does this PR introduce any binary protocol compatibility change? ## Benchmark --- .../org/apache/fury/config/FuryBuilder.java | 11 ++ .../apache/fury/resolver/ClassResolver.java | 19 +- .../serializer/scala/ScalaDispatcher.java | 2 + .../serializer/scala/ScalaSerializers.java | 172 ++++++++++++++++++ .../scala/ToFactorySerializers.java | 97 ++++++++++ .../scala/CollectionSerializerTest.scala | 7 +- ...CompatibleSingleObjectSerializerTest.scala | 9 +- .../MutableCollectionSerializerTest.scala | 101 ++++++++++ .../fury/serializer/scala/ScalaTest.scala | 16 +- 9 files changed, 421 insertions(+), 13 deletions(-) create mode 100644 scala/src/main/java/org/apache/fury/serializer/scala/ScalaSerializers.java create mode 100644 scala/src/main/java/org/apache/fury/serializer/scala/ToFactorySerializers.java create mode 100644 scala/src/test/scala/org/apache/fury/serializer/scala/MutableCollectionSerializerTest.scala diff --git a/java/fury-core/src/main/java/org/apache/fury/config/FuryBuilder.java b/java/fury-core/src/main/java/org/apache/fury/config/FuryBuilder.java index 1210c7515d..c27a612418 100644 --- a/java/fury-core/src/main/java/org/apache/fury/config/FuryBuilder.java +++ b/java/fury-core/src/main/java/org/apache/fury/config/FuryBuilder.java @@ -30,6 +30,7 @@ import org.apache.fury.meta.DeflaterMetaCompressor; import org.apache.fury.meta.MetaCompressor; import org.apache.fury.pool.ThreadPoolFury; +import org.apache.fury.reflect.ReflectionUtils; import org.apache.fury.resolver.ClassResolver; import org.apache.fury.serializer.JavaSerializer; import org.apache.fury.serializer.ObjectStreamSerializer; @@ -316,6 +317,16 @@ public FuryBuilder withAsyncCompilation(boolean asyncCompilation) { /** Whether enable scala-specific serialization optimization. */ public FuryBuilder withScalaOptimizationEnabled(boolean enableScalaOptimization) { this.scalaOptimizationEnabled = enableScalaOptimization; + if (enableScalaOptimization) { + try { + Class.forName( + ReflectionUtils.getPackage(Fury.class) + ".serializer.scala.ScalaSerializers"); + } catch (ClassNotFoundException e) { + LOG.warn( + "`fury-scala` library is not in the classpath, please add it to class path and invoke " + + "`org.apache.fury.serializer.scala.ScalaSerializers.registerSerializers` for peek performance"); + } + } return this; } diff --git a/java/fury-core/src/main/java/org/apache/fury/resolver/ClassResolver.java b/java/fury-core/src/main/java/org/apache/fury/resolver/ClassResolver.java index 06748a041f..21adfb76f6 100644 --- a/java/fury-core/src/main/java/org/apache/fury/resolver/ClassResolver.java +++ b/java/fury-core/src/main/java/org/apache/fury/resolver/ClassResolver.java @@ -409,6 +409,10 @@ public void register(Class cls) { } } + public void register(String className) { + register(loadClass(className, false, 0, false)); + } + public void register(Class... classes) { for (Class cls : classes) { register(cls); @@ -481,6 +485,10 @@ public void register(Class cls, int classId) { extRegistry.classIdGenerator++; } + public void register(String className, int classId) { + register(loadClass(className, false, 0, false), classId); + } + public void register(Class cls, Short id, boolean createSerializer) { register(cls, id); if (createSerializer) { @@ -488,6 +496,10 @@ public void register(Class cls, Short id, boolean createSerializer) { } } + public void register(String className, Short classId, boolean createSerializer) { + register(loadClass(className, false, 0, false), classId, createSerializer); + } + public boolean isRegistered(Class cls) { return extRegistry.registeredClassIdMap.get(cls) != null; } @@ -1807,6 +1819,11 @@ private Class loadClass(ClassSpec classSpec) { } private Class loadClass(String className, boolean isEnum, int arrayDims) { + return loadClass(className, isEnum, arrayDims, fury.getConfig().deserializeNonexistentClass()); + } + + private Class loadClass( + String className, boolean isEnum, int arrayDims, boolean deserializeNonexistentClass) { extRegistry.classChecker.checkClass(this, className); try { return Class.forName(className, false, fury.getClassLoader()); @@ -1818,7 +1835,7 @@ private Class loadClass(String className, boolean isEnum, int arrayDims) { String.format( "Class %s not found from classloaders [%s, %s]", className, fury.getClassLoader(), Thread.currentThread().getContextClassLoader()); - if (fury.getConfig().deserializeNonexistentClass()) { + if (deserializeNonexistentClass) { LOG.warn(msg); return NonexistentClass.getNonexistentClass( className, isEnum, arrayDims, metaContextShareEnabled); diff --git a/scala/src/main/java/org/apache/fury/serializer/scala/ScalaDispatcher.java b/scala/src/main/java/org/apache/fury/serializer/scala/ScalaDispatcher.java index 4f43f34b11..3407460608 100644 --- a/scala/src/main/java/org/apache/fury/serializer/scala/ScalaDispatcher.java +++ b/scala/src/main/java/org/apache/fury/serializer/scala/ScalaDispatcher.java @@ -51,6 +51,8 @@ public Serializer createSerializer(Fury fury, Class clz) { return new ScalaSortedSetSerializer(fury, clz); } else if (scala.collection.Seq.class.isAssignableFrom(clz)) { return new ScalaSeqSerializer(fury, clz); + } else if (scala.collection.Iterable.class.isAssignableFrom(clz)) { + return new ScalaCollectionSerializer(fury, clz); } if (DefaultSerializable.class.isAssignableFrom(clz)) { Method replaceMethod = JavaSerializer.getWriteReplaceMethod(clz); diff --git a/scala/src/main/java/org/apache/fury/serializer/scala/ScalaSerializers.java b/scala/src/main/java/org/apache/fury/serializer/scala/ScalaSerializers.java new file mode 100644 index 0000000000..5cfdcd8903 --- /dev/null +++ b/scala/src/main/java/org/apache/fury/serializer/scala/ScalaSerializers.java @@ -0,0 +1,172 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.fury.serializer.scala; + +import org.apache.fury.Fury; +import org.apache.fury.resolver.ClassResolver; +import org.apache.fury.serializer.Serializer; +import org.apache.fury.serializer.SerializerFactory; +import scala.collection.immutable.NumericRange; +import scala.collection.immutable.Range; + +import static org.apache.fury.serializer.scala.ToFactorySerializers.IterableToFactoryClass; +import static org.apache.fury.serializer.scala.ToFactorySerializers.MapToFactoryClass; + +public class ScalaSerializers { + + public static void registerSerializers(Fury fury) { + ClassResolver resolver = setSerializerFactory(fury); + + resolver.registerSerializer(IterableToFactoryClass, new ToFactorySerializers.IterableToFactorySerializer(fury)); + resolver.registerSerializer(MapToFactoryClass, new ToFactorySerializers.MapToFactorySerializer(fury)); + + // Seq + resolver.register(scala.collection.immutable.Seq.class); + resolver.register(scala.collection.immutable.Nil$.class); + resolver.register(scala.collection.immutable.List$.class); + resolver.register(scala.collection.immutable.$colon$colon.class); + // StrictOptimizedSeqFactory -> ... extends -> IterableFactory + resolver.register(scala.collection.immutable.Vector$.class); + resolver.register("scala.collection.immutable.VectorImpl"); + resolver.register("scala.collection.immutable.Vector0"); + resolver.register("scala.collection.immutable.Vector1"); + resolver.register("scala.collection.immutable.Vector2"); + resolver.register("scala.collection.immutable.Vector3"); + resolver.register("scala.collection.immutable.Vector4"); + resolver.register("scala.collection.immutable.Vector5"); + resolver.register("scala.collection.immutable.Vector6"); + resolver.register(scala.collection.immutable.Queue.class); + resolver.register(scala.collection.immutable.Queue$.class); + resolver.register(scala.collection.immutable.LazyList.class); + resolver.register(scala.collection.immutable.LazyList$.class); + resolver.register(scala.collection.immutable.ArraySeq.class); + resolver.register(scala.collection.immutable.ArraySeq$.class); + + // Set + resolver.register(scala.collection.immutable.Set.class); + // IterableFactory + resolver.register(scala.collection.immutable.Set$.class); + resolver.register(scala.collection.immutable.Set.Set1.class); + resolver.register(scala.collection.immutable.Set.Set2.class); + resolver.register(scala.collection.immutable.Set.Set3.class); + resolver.register(scala.collection.immutable.Set.Set4.class); + resolver.register(scala.collection.immutable.HashSet.class); + resolver.register(scala.collection.immutable.TreeSet.class); + // SortedIterableFactory + resolver.register(scala.collection.immutable.TreeSet$.class); + // IterableFactory + resolver.register(scala.collection.immutable.HashSet$.class); + resolver.register(scala.collection.immutable.ListSet.class); + resolver.register(scala.collection.immutable.ListSet$.class); + resolver.register("scala.collection.immutable.Set$EmptySet$"); + resolver.register("scala.collection.immutable.SetBuilderImpl"); + resolver.register("scala.collection.immutable.SortedMapOps$ImmutableKeySortedSet"); + + // Map + resolver.register(scala.collection.immutable.Map.class); + resolver.register(scala.collection.immutable.Map$.class); + resolver.register(scala.collection.immutable.Map.Map1.class); + resolver.register(scala.collection.immutable.Map.Map2.class); + resolver.register(scala.collection.immutable.Map.Map3.class); + resolver.register(scala.collection.immutable.Map.Map4.class); + resolver.register(scala.collection.immutable.Map.WithDefault.class); + resolver.register("scala.collection.immutable.MapBuilderImpl"); + resolver.register("scala.collection.immutable.Map$EmptyMap$"); + resolver.register("scala.collection.immutable.SeqMap$EmptySeqMap$"); + resolver.register(scala.collection.immutable.HashMap.class); + resolver.register(scala.collection.immutable.HashMap$.class); + resolver.register(scala.collection.immutable.TreeMap.class); + resolver.register(scala.collection.immutable.TreeMap$.class); + resolver.register(scala.collection.immutable.SortedMap$.class); + resolver.register(scala.collection.immutable.TreeSeqMap.class); + resolver.register(scala.collection.immutable.TreeSeqMap$.class); + resolver.register(scala.collection.immutable.ListMap.class); + resolver.register(scala.collection.immutable.ListMap$.class); + resolver.register(scala.collection.immutable.IntMap.class); + resolver.register(scala.collection.immutable.IntMap$.class); + resolver.register(scala.collection.immutable.LongMap.class); + resolver.register(scala.collection.immutable.LongMap$.class); + + // Range + resolver.register(Range.Inclusive.class); + resolver.register(Range.Exclusive.class); + resolver.register(NumericRange.class); + resolver.register(NumericRange.Inclusive.class); + resolver.register(NumericRange.Exclusive.class); + + resolver.register(scala.collection.generic.SerializeEnd$.class); + resolver.register(scala.collection.generic.DefaultSerializationProxy.class); + resolver.register(scala.runtime.ModuleSerializationProxy.class); + + // mutable collection types + resolver.register(scala.collection.mutable.StringBuilder.class); + resolver.register(scala.collection.mutable.ArrayBuffer.class); + resolver.register(scala.collection.mutable.ArrayBuffer$.class); + resolver.register(scala.collection.mutable.ArraySeq.class); + resolver.register(scala.collection.mutable.ArraySeq$.class); + resolver.register(scala.collection.mutable.ListBuffer.class); + resolver.register(scala.collection.mutable.ListBuffer$.class); + resolver.register(scala.collection.mutable.Buffer$.class); + resolver.register(scala.collection.mutable.ArrayDeque.class); + resolver.register(scala.collection.mutable.ArrayDeque$.class); + + resolver.register(scala.collection.mutable.HashSet.class); + resolver.register(scala.collection.mutable.HashSet$.class); + resolver.register(scala.collection.mutable.TreeSet.class); + resolver.register(scala.collection.mutable.TreeSet$.class); + + resolver.register(scala.collection.mutable.HashMap.class); + resolver.register(scala.collection.mutable.HashMap$.class); + resolver.register(scala.collection.mutable.TreeMap.class); + resolver.register(scala.collection.mutable.TreeMap$.class); + resolver.register(scala.collection.mutable.LinkedHashMap.class); + resolver.register(scala.collection.mutable.LinkedHashMap$.class); + resolver.register(scala.collection.mutable.LinkedHashSet.class); + resolver.register(scala.collection.mutable.LinkedHashSet$.class); + resolver.register(scala.collection.mutable.LongMap.class); + resolver.register(scala.collection.mutable.LongMap$.class); + + resolver.register(scala.collection.mutable.Queue.class); + resolver.register(scala.collection.mutable.Queue$.class); + resolver.register(scala.collection.mutable.Stack.class); + resolver.register(scala.collection.mutable.Stack$.class); + resolver.register(scala.collection.mutable.BitSet.class); + resolver.register(scala.collection.mutable.BitSet$.class); + } + + private static ClassResolver setSerializerFactory(Fury fury) { + ClassResolver resolver = fury.getClassResolver(); + ScalaDispatcher dispatcher = new ScalaDispatcher(); + SerializerFactory factory = resolver.getSerializerFactory(); + if (factory != null) { + SerializerFactory newFactory = (f, cls) -> { + Serializer serializer = factory.createSerializer(f, cls); + if (serializer == null) { + serializer = dispatcher.createSerializer(f, cls); + } + return serializer; + }; + resolver.setSerializerFactory(newFactory); + } else { + resolver.setSerializerFactory(dispatcher); + } + return resolver; + } +} diff --git a/scala/src/main/java/org/apache/fury/serializer/scala/ToFactorySerializers.java b/scala/src/main/java/org/apache/fury/serializer/scala/ToFactorySerializers.java new file mode 100644 index 0000000000..a800b98a59 --- /dev/null +++ b/scala/src/main/java/org/apache/fury/serializer/scala/ToFactorySerializers.java @@ -0,0 +1,97 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.fury.serializer.scala; + +import org.apache.fury.Fury; +import org.apache.fury.memory.MemoryBuffer; +import org.apache.fury.memory.Platform; +import org.apache.fury.reflect.ReflectionUtils; +import org.apache.fury.serializer.Serializer; + +import java.lang.reflect.Field; + +public class ToFactorySerializers { + static final Class IterableToFactoryClass = ReflectionUtils.loadClass( + "scala.collection.IterableFactory$ToFactory"); + static final Class MapToFactoryClass = ReflectionUtils.loadClass( + "scala.collection.MapFactory$ToFactory"); + + public static class IterableToFactorySerializer extends Serializer { + private static final long fieldOffset; + + static { + try { + // for graalvm field offset auto rewrite + Field field = Class.forName("scala.collection.IterableFactory$ToFactory").getDeclaredField("factory"); + fieldOffset = Platform.objectFieldOffset(field); + } catch (final Exception e) { + throw new RuntimeException(e); + } + } + + public IterableToFactorySerializer(Fury fury) { + super(fury, IterableToFactoryClass); + } + + @Override + public void write(MemoryBuffer buffer, Object value) { + fury.writeRef(buffer, Platform.getObject(value, fieldOffset)); + } + + @Override + public Object read(MemoryBuffer buffer) { + Object o = Platform.newInstance(type); + Platform.putObject(o, fieldOffset, fury.readRef(buffer)); + return o; + } + } + + public static class MapToFactorySerializer extends Serializer { + private static final long fieldOffset; + + static { + try { + // for graalvm field offset auto rewrite + Field field = Class.forName("scala.collection.MapFactory$ToFactory").getDeclaredField("factory"); + fieldOffset = Platform.objectFieldOffset(field); + } catch (final Exception e) { + throw new RuntimeException(e); + } + } + + public MapToFactorySerializer(Fury fury) { + super(fury, MapToFactoryClass); + } + + @Override + public void write(MemoryBuffer buffer, Object value) { + fury.writeRef(buffer, Platform.getObject(value, fieldOffset)); + } + + @Override + public Object read(MemoryBuffer buffer) { + Object o = Platform.newInstance(type); + Platform.putObject(o, fieldOffset, fury.readRef(buffer)); + return o; + } + } + + +} diff --git a/scala/src/test/scala/org/apache/fury/serializer/scala/CollectionSerializerTest.scala b/scala/src/test/scala/org/apache/fury/serializer/scala/CollectionSerializerTest.scala index b86662adee..041447bb0b 100644 --- a/scala/src/test/scala/org/apache/fury/serializer/scala/CollectionSerializerTest.scala +++ b/scala/src/test/scala/org/apache/fury/serializer/scala/CollectionSerializerTest.scala @@ -31,7 +31,10 @@ class CollectionSerializerTest extends AnyWordSpec with Matchers { .withLanguage(Language.JAVA) .withRefTracking(true) .withScalaOptimizationEnabled(setOpt) - .requireClassRegistration(false).build() + .requireClassRegistration(false) + .suppressClassRegistrationWarnings(false) + .build() + ScalaSerializers.registerSerializers(fury1) if (setFactory) { fury1.getClassResolver.setSerializerFactory(new ScalaDispatcher()) } @@ -43,6 +46,8 @@ class CollectionSerializerTest extends AnyWordSpec with Matchers { "serialize/deserialize List" in { val list = List(100, 10000L) fury1.deserialize(fury1.serialize(list)) shouldEqual list + val list2 = List(100, 10000L, 10000L, 10000L) + fury1.deserialize(fury1.serialize(list2)) shouldEqual list2 } "serialize/deserialize empty List" in { fury1.deserialize(fury1.serialize(List.empty)) shouldEqual List.empty diff --git a/scala/src/test/scala/org/apache/fury/serializer/scala/CompatibleSingleObjectSerializerTest.scala b/scala/src/test/scala/org/apache/fury/serializer/scala/CompatibleSingleObjectSerializerTest.scala index 1b8afc8565..f9c5cf250c 100644 --- a/scala/src/test/scala/org/apache/fury/serializer/scala/CompatibleSingleObjectSerializerTest.scala +++ b/scala/src/test/scala/org/apache/fury/serializer/scala/CompatibleSingleObjectSerializerTest.scala @@ -45,6 +45,7 @@ class CompatibleSingleObjectSerializerTest extends AnyWordSpec with Matchers { .requireClassRegistration(false) .withRefTracking(true) .withCompatibleMode(CompatibleMode.COMPATIBLE) + .suppressClassRegistrationWarnings(false) .build() } @@ -59,10 +60,10 @@ class CompatibleSingleObjectSerializerTest extends AnyWordSpec with Matchers { fury.deserialize(bytes) shouldEqual A.B.C("hello, world!") } "testArraySeqQuery" in { - val o = SingletonObject.ArraySeqQuery(ArraySeq(SingletonObject.Query)) - fury.deserialize( - fury.serialize( - o)) shouldEqual o + val o = SingletonObject.ArraySeqQuery(ArraySeq(SingletonObject.Query)) + fury.deserialize( + fury.serialize( + o)) shouldEqual o } "testArrayQuery" in { val o = SingletonObject.ArrayQuery(Array(SingletonObject.Query)) diff --git a/scala/src/test/scala/org/apache/fury/serializer/scala/MutableCollectionSerializerTest.scala b/scala/src/test/scala/org/apache/fury/serializer/scala/MutableCollectionSerializerTest.scala new file mode 100644 index 0000000000..bea24cb14e --- /dev/null +++ b/scala/src/test/scala/org/apache/fury/serializer/scala/MutableCollectionSerializerTest.scala @@ -0,0 +1,101 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.fury.serializer.scala + +import org.apache.fury.Fury +import org.apache.fury.config.Language +import org.scalatest.matchers.should.Matchers +import org.scalatest.wordspec.AnyWordSpec + +import scala.collection.{Seq, Map, Set, mutable} +import scala.collection.mutable.ListBuffer + +class MutableCollectionSerializerTest extends AnyWordSpec with Matchers { + val params: Seq[(Boolean, Boolean)] = List( (true, true)) + params.foreach{case (setOpt, setFactory) => { + val fury1: Fury = Fury.builder() + .withLanguage(Language.JAVA) + .withRefTracking(true) + .withScalaOptimizationEnabled(setOpt) + .requireClassRegistration(false) + .suppressClassRegistrationWarnings(false) + .build() + if (setFactory) { + ScalaSerializers.registerSerializers(fury1) + } + s"fury scala collection support: setOpt $setOpt, setFactory $setFactory" should { + "serialize/deserialize Seq" in { + val seq = mutable.Seq(100, 10000L) + fury1.deserialize(fury1.serialize(seq)) shouldEqual seq + } + "serialize/deserialize List" in { + val list = mutable.ListBuffer(100, 10000L) + fury1.deserialize(fury1.serialize(list)) shouldEqual list + val list2 = mutable.ListBuffer(100, 10000L, 10000L, 10000L) + fury1.deserialize(fury1.serialize(list2)) shouldEqual list2 + } + "serialize/deserialize empty List" in { + fury1.deserialize(fury1.serialize(ListBuffer.empty)) shouldEqual ListBuffer.empty + } + "serialize/deserialize Set" in { + val set = mutable.Set(100, 10000L) + fury1.deserialize(fury1.serialize(set)) shouldEqual set + } + "serialize/deserialize CollectionStruct2" in { + val struct = CollectionStruct2(mutable.ListBuffer("a", "b")) + fury1.deserialize(fury1.serialize(struct)) shouldEqual struct + } + "serialize/deserialize CollectionStruct2 with empty List" in { + val struct1 = CollectionStruct2(ListBuffer.empty) + fury1.deserialize(fury1.serialize(struct1)) shouldEqual struct1 + } + "serialize/deserialize NestedCollectionStruct1" in { + val struct = NestedCollectionStruct1(ListBuffer(ListBuffer("a", "b"), ListBuffer("a", "b")), mutable.Set(Set("c", "d"))) + fury1.deserialize(fury1.serialize(struct)) shouldEqual struct + } + } + s"fury scala map support: setOpt $setOpt, setFactory $setFactory" should { + "serialize/deserialize Map" in { + val map = mutable.Map("a" -> 100, "b" -> 10000L) + fury1.deserialize(fury1.serialize(map)) shouldEqual map + } + "serialize/deserialize MapStruct2" in { + val struct = MapStruct2(mutable.Map("k1" -> "v1", "k2" -> "v2")) + fury1.deserialize(fury1.serialize(struct)) shouldEqual struct + } + "serialize/deserialize MapStruct2 with empty map" in { + val struct = MapStruct2(mutable.Map.empty) + fury1.deserialize(fury1.serialize(struct)) shouldEqual struct + } + "serialize/deserialize NestedMapStruc1" in { + val struct = NestedMapStruc1(mutable.Map("K1" -> mutable.Map("k1" -> "v1", "k2" -> "v2"), "K2" -> mutable.Map("k1" -> "v1"))) + fury1.deserialize(fury1.serialize(struct)) shouldEqual struct + } + } + }} +} + +case class CollectionStruct2(list: Seq[String]) + +case class NestedCollectionStruct1(list: Seq[Seq[String]], set: Set[Set[String]]) + +case class MapStruct2(map: Map[String, String]) + +case class NestedMapStruc1(map: Map[String, Map[String, String]]) diff --git a/scala/src/test/scala/org/apache/fury/serializer/scala/ScalaTest.scala b/scala/src/test/scala/org/apache/fury/serializer/scala/ScalaTest.scala index 9a391dce31..8e094ee9ee 100644 --- a/scala/src/test/scala/org/apache/fury/serializer/scala/ScalaTest.scala +++ b/scala/src/test/scala/org/apache/fury/serializer/scala/ScalaTest.scala @@ -29,14 +29,15 @@ package object SomePackageObject { } class ScalaTest extends AnyWordSpec with Matchers { + def fury: Fury = Fury.builder() + .withLanguage(Language.JAVA) + .withRefTracking(true) + .withScalaOptimizationEnabled(true) + .requireClassRegistration(false) + .suppressClassRegistrationWarnings(false).build() + "fury scala support" should { "serialize/deserialize package object" in { - val fury = Fury.builder() - .withLanguage(Language.JAVA) - .withRefTracking(true) - .withScalaOptimizationEnabled(true) - .requireClassRegistration(false).build() - val p = SomePackageObject.SomeClass(1) fury.deserialize(fury.serialize(p)) shouldEqual p } @@ -61,7 +62,7 @@ object PkgObjectMain extends App { .builder() .withScalaOptimizationEnabled(true) .requireClassRegistration(false) - .withRefTracking(true) + .withRefTracking(true).suppressClassRegistrationWarnings(false) .build() import PkgObject._ @@ -81,6 +82,7 @@ object PkgObjectMain2 extends App { .withScalaOptimizationEnabled(true) .requireClassRegistration(false) .withRefTracking(true) + .suppressClassRegistrationWarnings(false) .build() import PkgObject._