diff --git a/src/com/esotericsoftware/kryo/serializers/SynchronizedCollectionSerializers.java b/src/com/esotericsoftware/kryo/serializers/SynchronizedCollectionSerializers.java new file mode 100644 index 000000000..199c4f81b --- /dev/null +++ b/src/com/esotericsoftware/kryo/serializers/SynchronizedCollectionSerializers.java @@ -0,0 +1,203 @@ +/* Copyright (c) 2008-2025, Nathan Sweet + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided with the distribution. + * - Neither the name of Esoteric Software nor the names of its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, + * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + +package com.esotericsoftware.kryo.serializers; + +import com.esotericsoftware.kryo.Kryo; +import com.esotericsoftware.kryo.KryoException; +import com.esotericsoftware.kryo.Serializer; +import com.esotericsoftware.kryo.io.Input; +import com.esotericsoftware.kryo.io.Output; +import com.esotericsoftware.kryo.unsafe.UnsafeUtil; +import com.esotericsoftware.minlog.Log; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.SortedMap; +import java.util.SortedSet; +import java.util.TreeMap; +import java.util.TreeSet; +import java.util.function.Function; + +/** Serializer for synchronized Collections and Maps created via Collections. */ +@SuppressWarnings({"rawtypes", "unchecked"}) +public class SynchronizedCollectionSerializers { + + private static class Offset { + private static final long SOURCE_COLLECTION_FIELD_OFFSET; + private static final long SOURCE_MAP_FIELD_OFFSET; + + static { + String clsName = "java.util.Collections$SynchronizedCollection"; + try { + SOURCE_COLLECTION_FIELD_OFFSET = UnsafeUtil.objectFieldOffset(Class.forName(clsName).getDeclaredField("c")); + } catch (Exception e) { + Log.warn("Could not access source collection field in {}", clsName); + throw new KryoException(e); + } + clsName = "java.util.Collections$SynchronizedMap"; + try { + SOURCE_MAP_FIELD_OFFSET = UnsafeUtil.objectFieldOffset(Class.forName(clsName).getDeclaredField("m")); + } catch (Exception e) { + Log.warn("Could not access source map field in {}", clsName); + throw new KryoException(e); + } + } + } + + static final class SynchronizedCollectionSerializer extends CollectionSerializer { + private final Function factory; + private final long offset; + + public SynchronizedCollectionSerializer (Function factory, long offset) { + setAcceptsNull(false); + this.factory = factory; + this.offset = offset; + } + + @Override + public void write (Kryo kryo, Output output, Collection collection) { + Object unwrapped = UnsafeUtil.getObject(collection, offset); + synchronized (collection) { + kryo.writeClassAndObject(output, unwrapped); + } + } + + @Override + public Collection read (Kryo kryo, Input input, Class type) { + final Object sourceCollection = kryo.readClassAndObject(input); + return (Collection)factory.apply(sourceCollection); + } + + @Override + public Collection copy (Kryo kryo, Collection original) { + final Object collection = UnsafeUtil.getObject(original, offset); + return (Collection)factory.apply(kryo.copy(collection)); + } + } + + static final class SynchronizedMapSerializer extends MapSerializer { + private final Function factory; + private final long offset; + + public SynchronizedMapSerializer (Function factory, long offset) { + setAcceptsNull(false); + this.factory = factory; + this.offset = offset; + } + + @Override + public void write (Kryo kryo, Output output, Map map) { + Object unwrapped = UnsafeUtil.getObject(map, offset); + synchronized (map) { + kryo.writeClassAndObject(output, unwrapped); + } + } + + @Override + public Map read (Kryo kryo, Input input, Class type) { + final Object sourceMap = kryo.readClassAndObject(input); + return (Map)factory.apply(sourceMap); + } + + @Override + public Map copy (Kryo kryo, Map original) { + final Object map = UnsafeUtil.getObject(original, offset); + return (Map)factory.apply(kryo.copy(map)); + } + } + + private static Serializer createSerializer (Map.Entry, Function> factory) { + if (Collection.class.isAssignableFrom(factory.getKey())) { + return new SynchronizedCollectionSerializer(factory.getValue(), Offset.SOURCE_COLLECTION_FIELD_OFFSET); + } else { + return new SynchronizedMapSerializer(factory.getValue(), Offset.SOURCE_MAP_FIELD_OFFSET); + } + } + + static Map, Function> synchronizedFactories () { + final Map, Function> factories = new HashMap<>(); + factories.put( + Collections.synchronizedCollection(Arrays.asList("")).getClass(), + o -> Collections.synchronizedCollection((Collection)o)); + factories.put( + Collections.synchronizedList(new ArrayList()).getClass(), + o1 -> Collections.synchronizedList((List)o1)); + factories.put( + Collections.synchronizedList(new LinkedList()).getClass(), + o2 -> Collections.synchronizedList((List)o2)); + factories.put( + Collections.synchronizedSet(new HashSet()).getClass(), + o3 -> Collections.synchronizedSet((Set)o3)); + factories.put( + Collections.synchronizedSortedSet(new TreeSet<>()).getClass(), + o4 -> Collections.synchronizedSortedSet((TreeSet)o4)); + factories.put( + Collections.synchronizedMap(new HashMap()).getClass(), + o5 -> Collections.synchronizedMap((Map)o5)); + factories.put( + Collections.synchronizedSortedMap(new TreeMap<>()).getClass(), + o6 -> Collections.synchronizedSortedMap((SortedMap)o6)); + return factories; + } + + /** Registering serializers for synchronized Collections and Maps created via {@link Collections}. + * + * @see Collections#synchronizedCollection(Collection) + * @see Collections#synchronizedList(List) + * @see Collections#synchronizedSet(Set) + * @see Collections#synchronizedSortedSet(SortedSet) + * @see Collections#synchronizedMap(Map) + * @see Collections#synchronizedSortedMap(SortedMap) **/ + public static void registerSerializers (Kryo kryo) { + try { + for (Map.Entry, Function> factory : synchronizedFactories().entrySet()) { + kryo.register(factory.getKey(), createSerializer(factory)); + } + } catch (Throwable ignored) { + // ignored + } + } + + /** Adding default serializers for synchronized Collections and Maps created via {@link Collections}. + * + * @see Collections#synchronizedCollection(Collection) + * @see Collections#synchronizedList(List) + * @see Collections#synchronizedSet(Set) + * @see Collections#synchronizedSortedSet(SortedSet) + * @see Collections#synchronizedMap(Map) + * @see Collections#synchronizedSortedMap(SortedMap) **/ + public static void addDefaultSerializers (Kryo kryo) { + try { + for (Map.Entry, Function> factory : synchronizedFactories().entrySet()) { + kryo.addDefaultSerializer(factory.getKey(), createSerializer(factory)); + } + } catch (Throwable ignored) { + // ignored + } + } +} diff --git a/src/com/esotericsoftware/kryo/serializers/UnmodifiableCollectionSerializers.java b/src/com/esotericsoftware/kryo/serializers/UnmodifiableCollectionSerializers.java new file mode 100644 index 000000000..fcddb6f0c --- /dev/null +++ b/src/com/esotericsoftware/kryo/serializers/UnmodifiableCollectionSerializers.java @@ -0,0 +1,199 @@ +/* Copyright (c) 2008-2025, Nathan Sweet + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided with the distribution. + * - Neither the name of Esoteric Software nor the names of its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, + * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + +package com.esotericsoftware.kryo.serializers; + +import com.esotericsoftware.kryo.Kryo; +import com.esotericsoftware.kryo.KryoException; +import com.esotericsoftware.kryo.Serializer; +import com.esotericsoftware.kryo.io.Input; +import com.esotericsoftware.kryo.io.Output; +import com.esotericsoftware.kryo.unsafe.UnsafeUtil; +import com.esotericsoftware.minlog.Log; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.SortedMap; +import java.util.SortedSet; +import java.util.TreeMap; +import java.util.TreeSet; +import java.util.function.Function; + +/** Serializer for unmodifiable Collections and Maps created via Collections. */ +@SuppressWarnings({"rawtypes", "unchecked"}) +public class UnmodifiableCollectionSerializers { + + private static class Offset { + private static final long SOURCE_COLLECTION_FIELD_OFFSET; + private static final long SOURCE_MAP_FIELD_OFFSET; + + static { + String clsName = "java.util.Collections$UnmodifiableCollection"; + try { + SOURCE_COLLECTION_FIELD_OFFSET = UnsafeUtil.objectFieldOffset(Class.forName(clsName).getDeclaredField("c")); + } catch (Exception e) { + Log.warn("Could not access source collection field in {}", clsName); + throw new KryoException(e); + } + clsName = "java.util.Collections$UnmodifiableMap"; + try { + SOURCE_MAP_FIELD_OFFSET = UnsafeUtil.objectFieldOffset(Class.forName(clsName).getDeclaredField("m")); + } catch (Exception e) { + Log.warn("Could not access source map field in {}", clsName); + throw new KryoException(e); + } + } + } + + static final class UnmodifiableCollectionSerializer extends CollectionSerializer { + private final Function factory; + private final long offset; + + public UnmodifiableCollectionSerializer (Function factory, long offset) { + setAcceptsNull(false); + this.factory = factory; + this.offset = offset; + } + + @Override + public void write (Kryo kryo, Output output, Collection collection) { + final Object fieldValue = UnsafeUtil.getObject(collection, offset); + kryo.writeClassAndObject(output, fieldValue); + } + + @Override + public Collection read (Kryo kryo, Input input, Class type) { + final Object sourceCollection = kryo.readClassAndObject(input); + return (Collection)factory.apply(sourceCollection); + } + + @Override + public Collection copy (Kryo kryo, Collection original) { + final Object collection = UnsafeUtil.getObject(original, offset); + return (Collection)factory.apply(kryo.copy(collection)); + } + } + + static final class UnmodifiableMapSerializer extends MapSerializer { + private final Function factory; + private final long offset; + + public UnmodifiableMapSerializer (Function factory, long offset) { + setAcceptsNull(false); + this.factory = factory; + this.offset = offset; + } + + @Override + public void write (Kryo kryo, Output output, Map map) { + Object fieldValue = UnsafeUtil.getObject(map, offset); + kryo.writeClassAndObject(output, fieldValue); + } + + @Override + public Map read (Kryo kryo, Input input, Class type) { + final Object sourceMap = kryo.readClassAndObject(input); + return (Map)factory.apply(sourceMap); + } + + @Override + public Map copy (Kryo kryo, Map original) { + final Object map = UnsafeUtil.getObject(original, offset); + return (Map)factory.apply(kryo.copy(map)); + } + } + + private static Serializer createSerializer (Map.Entry, Function> factory) { + if (Collection.class.isAssignableFrom(factory.getKey())) { + return new UnmodifiableCollectionSerializer(factory.getValue(), Offset.SOURCE_COLLECTION_FIELD_OFFSET); + } else { + return new UnmodifiableMapSerializer(factory.getValue(), Offset.SOURCE_MAP_FIELD_OFFSET); + } + } + + @SuppressWarnings("RedundantUnmodifiable") + static Map, Function> unmodifiableFactories () { + final Map, Function> factories = new HashMap<>(); + factories.put( + Collections.unmodifiableCollection(Collections.singletonList("")).getClass(), + o -> Collections.unmodifiableCollection((Collection)o)); + factories.put( + Collections.unmodifiableList(new ArrayList()).getClass(), + o1 -> Collections.unmodifiableList((List)o1)); + factories.put( + Collections.unmodifiableList(new LinkedList()).getClass(), + o2 -> Collections.unmodifiableList((List)o2)); + factories.put( + Collections.unmodifiableSet(new HashSet()).getClass(), + o3 -> Collections.unmodifiableSet((Set)o3)); + factories.put( + Collections.unmodifiableSortedSet(new TreeSet<>()).getClass(), + o4 -> Collections.unmodifiableSortedSet((SortedSet)o4)); + factories.put( + Collections.unmodifiableMap(new HashMap<>()).getClass(), + o5 -> Collections.unmodifiableMap((Map)o5)); + factories.put( + Collections.unmodifiableSortedMap(new TreeMap<>()).getClass(), + o6 -> Collections.unmodifiableSortedMap((SortedMap)o6)); + return factories; + } + + /** Registers serializers for unmodifiable Collections created via {@link Collections}, including {@link Map}s. + * + * @see Collections#unmodifiableCollection(Collection) + * @see Collections#unmodifiableList(List) + * @see Collections#unmodifiableSet(Set) + * @see Collections#unmodifiableSortedSet(SortedSet) + * @see Collections#unmodifiableMap(Map) + * @see Collections#unmodifiableSortedMap(SortedMap) */ + public static void registerSerializers (Kryo kryo) { + try { + for (Map.Entry, Function> factory : unmodifiableFactories().entrySet()) { + kryo.register(factory.getKey(), createSerializer(factory)); + } + } catch (Throwable ignored) { + // ignored + } + } + + /** Adds default serializers for unmodifiable Collections created via {@link Collections}, including {@link Map}s. + * + * @see Collections#unmodifiableCollection(Collection) + * @see Collections#unmodifiableList(List) + * @see Collections#unmodifiableSet(Set) + * @see Collections#unmodifiableSortedSet(SortedSet) + * @see Collections#unmodifiableMap(Map) + * @see Collections#unmodifiableSortedMap(SortedMap) */ + public static void addDefaultSerializers (Kryo kryo) { + try { + for (Map.Entry, Function> factory : unmodifiableFactories().entrySet()) { + kryo.addDefaultSerializer(factory.getKey(), createSerializer(factory)); + } + } catch (Throwable ignored) { + // ignored + } + } +} diff --git a/src/com/esotericsoftware/kryo/unsafe/UnsafeUtil.java b/src/com/esotericsoftware/kryo/unsafe/UnsafeUtil.java index 3139883b3..b3806d4b0 100644 --- a/src/com/esotericsoftware/kryo/unsafe/UnsafeUtil.java +++ b/src/com/esotericsoftware/kryo/unsafe/UnsafeUtil.java @@ -90,7 +90,15 @@ public class UnsafeUtil { booleanArrayBaseOffset = tempBooleanArrayBaseOffset; unsafe = tempUnsafe; } - + + public static long objectFieldOffset (Field c) { + return unsafe.objectFieldOffset(c); + } + + public static Object getObject (Object object, long offset) { + return unsafe.getObject(object, offset); + } + // Use a static inner class to defer initialization of direct buffer methods until first use private static final class DirectBuffers { // Constructor to be used for creation of ByteBuffers that use pre-allocated memory regions. diff --git a/test/com/esotericsoftware/kryo/serializers/SynchronizedCollectionSerializersTest.java b/test/com/esotericsoftware/kryo/serializers/SynchronizedCollectionSerializersTest.java new file mode 100644 index 000000000..ecadac0a2 --- /dev/null +++ b/test/com/esotericsoftware/kryo/serializers/SynchronizedCollectionSerializersTest.java @@ -0,0 +1,76 @@ +/* Copyright (c) 2008-2023, Nathan Sweet + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided with the distribution. + * - Neither the name of Esoteric Software nor the names of its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, + * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + +package com.esotericsoftware.kryo.serializers; + +import com.esotericsoftware.kryo.KryoTestCase; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.TreeMap; +import java.util.TreeSet; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class SynchronizedCollectionSerializersTest extends KryoTestCase { + { + supportsCopy = true; + } + + @BeforeEach + public void setUp () throws Exception { + super.setUp(); + + SynchronizedCollectionSerializers.registerSerializers(kryo); + + kryo.register(ArrayList.class); + kryo.register(LinkedList.class); + kryo.register(HashMap.class); + kryo.register(TreeMap.class); + kryo.register(HashSet.class); + kryo.register(TreeSet.class); + kryo.register(Arrays.asList("").getClass()); + } + + @Test + void setSerializer () { + roundTrip(3, Collections.synchronizedMap(new HashMap<>())); + roundTrip(4, Collections.synchronizedMap(new TreeMap<>())); + roundTrip(3, Collections.synchronizedList(new ArrayList<>())); + roundTrip(3, Collections.synchronizedList(new LinkedList<>())); + roundTrip(3, Collections.synchronizedSet(new HashSet<>())); + roundTrip(4, Collections.synchronizedSet(new TreeSet<>())); + roundTrip(6, Collections.synchronizedCollection(Arrays.asList(""))); + } + + protected void doAssertEquals (Object object1, Object object2) { + if (object1 instanceof Iterable && object2 instanceof Iterable) { + Assertions.assertEquals(object1.getClass(), object2.getClass()); + Assertions.assertIterableEquals((Iterable)object1, (Iterable)object2); + } else { + Assertions.assertEquals(object1, object2); + } + } +} diff --git a/test/com/esotericsoftware/kryo/serializers/UnmodifiableCollectionsSerializersTest.java b/test/com/esotericsoftware/kryo/serializers/UnmodifiableCollectionsSerializersTest.java new file mode 100644 index 000000000..a31e25a95 --- /dev/null +++ b/test/com/esotericsoftware/kryo/serializers/UnmodifiableCollectionsSerializersTest.java @@ -0,0 +1,76 @@ +/* Copyright (c) 2008-2023, Nathan Sweet + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided with the distribution. + * - Neither the name of Esoteric Software nor the names of its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, + * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + +package com.esotericsoftware.kryo.serializers; + +import com.esotericsoftware.kryo.KryoTestCase; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.TreeMap; +import java.util.TreeSet; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class UnmodifiableCollectionsSerializersTest extends KryoTestCase { + { + supportsCopy = true; + } + + @BeforeEach + public void setUp () throws Exception { + super.setUp(); + + UnmodifiableCollectionSerializers.registerSerializers(kryo); + + kryo.register(ArrayList.class); + kryo.register(LinkedList.class); + kryo.register(HashMap.class); + kryo.register(TreeMap.class); + kryo.register(HashSet.class); + kryo.register(TreeSet.class); + kryo.register(Arrays.asList("").getClass()); + } + + @Test + void testSerializer () { + roundTrip(3, Collections.unmodifiableMap(new HashMap<>())); + roundTrip(4, Collections.unmodifiableMap(new TreeMap<>())); + roundTrip(3, Collections.unmodifiableList(new ArrayList<>())); + roundTrip(3, Collections.unmodifiableList(new LinkedList<>())); + roundTrip(3, Collections.unmodifiableSet(new HashSet<>())); + roundTrip(4, Collections.unmodifiableSet(new TreeSet<>())); + roundTrip(6, Collections.unmodifiableCollection(Arrays.asList(""))); + } + + protected void doAssertEquals (Object object1, Object object2) { + if (object1 instanceof Iterable && object2 instanceof Iterable) { + Assertions.assertEquals(object1.getClass(), object2.getClass()); + Assertions.assertIterableEquals((Iterable)object1, (Iterable)object2); + } else { + Assertions.assertEquals(object1, object2); + } + } +}