From b3a6dfd0fe287384b68a6eb8d36a341098428552 Mon Sep 17 00:00:00 2001 From: Thomas Heigl Date: Wed, 26 Jan 2022 19:36:02 +0100 Subject: [PATCH] Add `MinimalGenerics` and allow users to set a `GenericStrategy` --- src/com/esotericsoftware/kryo/Kryo.java | 32 ++++++- .../kryo/util/BaseGenerics.java | 93 +++++++++++++++++++ .../kryo/util/DefaultGenerics.java | 67 +------------ .../kryo/util/MinimalGenerics.java | 44 +++++++++ .../serializers/CollectionSerializerTest.java | 23 +++++ .../kryo/serializers/MapSerializerTest.java | 5 +- 6 files changed, 198 insertions(+), 66 deletions(-) create mode 100644 src/com/esotericsoftware/kryo/util/BaseGenerics.java create mode 100644 src/com/esotericsoftware/kryo/util/MinimalGenerics.java diff --git a/src/com/esotericsoftware/kryo/Kryo.java b/src/com/esotericsoftware/kryo/Kryo.java index fa0434023..3df7c9ef6 100644 --- a/src/com/esotericsoftware/kryo/Kryo.java +++ b/src/com/esotericsoftware/kryo/Kryo.java @@ -92,6 +92,7 @@ import com.esotericsoftware.kryo.util.IdentityMap; import com.esotericsoftware.kryo.util.IntArray; import com.esotericsoftware.kryo.util.MapReferenceResolver; +import com.esotericsoftware.kryo.util.MinimalGenerics; import com.esotericsoftware.kryo.util.NoGenerics; import com.esotericsoftware.kryo.util.ObjectMap; import com.esotericsoftware.kryo.util.Util; @@ -1291,7 +1292,36 @@ public Generics getGenerics () { * setting in order to be compatible. * @param optimizedGenerics whether to optimize generics (default is true) */ public void setOptimizedGenerics (boolean optimizedGenerics) { - generics = optimizedGenerics ? new DefaultGenerics(this) : NoGenerics.INSTANCE; + setGenericsStrategy(optimizedGenerics ? GenericsStrategy.DEFAULT : GenericsStrategy.NONE); + } + + /** Sets a {@link GenericsStrategy}. + * + * TODO JavaDoc + * + * @param strategy the strategy for processing generics information */ + public void setGenericsStrategy (GenericsStrategy strategy) { + this.generics = strategy.createInstance(this); + } + + public enum GenericsStrategy { + DEFAULT { + public Generics createInstance (Kryo kryo) { + return new DefaultGenerics(kryo); + } + }, + MINIMAL { + public Generics createInstance (Kryo kryo) { + return new MinimalGenerics(kryo); + } + }, + NONE { + public Generics createInstance (Kryo kryo) { + return NoGenerics.INSTANCE; + } + }; + + public abstract Generics createInstance (Kryo kryo); } static final class DefaultSerializerEntry { diff --git a/src/com/esotericsoftware/kryo/util/BaseGenerics.java b/src/com/esotericsoftware/kryo/util/BaseGenerics.java new file mode 100644 index 000000000..365b1f0eb --- /dev/null +++ b/src/com/esotericsoftware/kryo/util/BaseGenerics.java @@ -0,0 +1,93 @@ +/* Copyright (c) 2008-2022, 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.util; + +import com.esotericsoftware.kryo.Kryo; + +/** Base class for implementations of {@link Generics} that stores generic type information. + * @author Nathan Sweet */ +abstract class BaseGenerics implements Generics { + final Kryo kryo; + + private int genericTypesSize; + private GenericType[] genericTypes = new GenericType[16]; + private int[] depths = new int[16]; + + protected BaseGenerics (Kryo kryo) { + this.kryo = kryo; + } + + @Override + public void pushGenericType (GenericType fieldType) { + // Ensure genericTypes and depths capacity. + int size = genericTypesSize; + if (size + 1 == genericTypes.length) { + GenericType[] genericTypesNew = new GenericType[genericTypes.length << 1]; + System.arraycopy(genericTypes, 0, genericTypesNew, 0, size); + genericTypes = genericTypesNew; + int[] depthsNew = new int[depths.length << 1]; + System.arraycopy(depths, 0, depthsNew, 0, size); + depths = depthsNew; + } + + genericTypesSize = size + 1; + genericTypes[size] = fieldType; + depths[size] = kryo.getDepth(); + } + + @Override + public void popGenericType () { + int size = genericTypesSize; + if (size == 0) return; + size--; + if (depths[size] < kryo.getDepth()) return; + genericTypes[size] = null; + genericTypesSize = size; + } + + @Override + public GenericType[] nextGenericTypes () { + int index = genericTypesSize; + if (index > 0) { + index--; + GenericType genericType = genericTypes[index]; + if (genericType.arguments == null) return null; + // The depth must match to prevent the types being wrong if a serializer doesn't call nextGenericTypes. + if (depths[index] == kryo.getDepth() - 1) { + pushGenericType(genericType.arguments[genericType.arguments.length - 1]); + return genericType.arguments; + } + } + return null; + } + + @Override + public Class nextGenericClass () { + GenericType[] arguments = nextGenericTypes(); + if (arguments == null) return null; + return arguments[0].resolve(this); + } + + @Override + public int getGenericTypesSize () { + return genericTypesSize; + } + +} diff --git a/src/com/esotericsoftware/kryo/util/DefaultGenerics.java b/src/com/esotericsoftware/kryo/util/DefaultGenerics.java index f4d24def5..428c38197 100644 --- a/src/com/esotericsoftware/kryo/util/DefaultGenerics.java +++ b/src/com/esotericsoftware/kryo/util/DefaultGenerics.java @@ -26,74 +26,18 @@ /** Stores the generic type arguments and actual classes for type variables in the current location in the object graph. * @author Nathan Sweet */ -public final class DefaultGenerics implements Generics { - private final Kryo kryo; - - private int genericTypesSize; - private GenericType[] genericTypes = new GenericType[16]; - private int[] depths = new int[16]; +public final class DefaultGenerics extends BaseGenerics { private int argumentsSize; private Type[] arguments = new Type[16]; public DefaultGenerics (Kryo kryo) { - this.kryo = kryo; - } - - @Override - public void pushGenericType (GenericType fieldType) { - // Ensure genericTypes and depths capacity. - int size = genericTypesSize; - if (size + 1 == genericTypes.length) { - GenericType[] genericTypesNew = new GenericType[genericTypes.length << 1]; - System.arraycopy(genericTypes, 0, genericTypesNew, 0, size); - genericTypes = genericTypesNew; - int[] depthsNew = new int[depths.length << 1]; - System.arraycopy(depths, 0, depthsNew, 0, size); - depths = depthsNew; - } - - genericTypesSize = size + 1; - genericTypes[size] = fieldType; - depths[size] = kryo.getDepth(); - } - - @Override - public void popGenericType () { - int size = genericTypesSize; - if (size == 0) return; - size--; - if (depths[size] < kryo.getDepth()) return; - genericTypes[size] = null; - genericTypesSize = size; - } - - @Override - public GenericType[] nextGenericTypes () { - int index = genericTypesSize; - if (index > 0) { - index--; - GenericType genericType = genericTypes[index]; - if (genericType.arguments == null) return null; - // The depth must match to prevent the types being wrong if a serializer doesn't call nextGenericTypes. - if (depths[index] == kryo.getDepth() - 1) { - pushGenericType(genericType.arguments[genericType.arguments.length - 1]); - return genericType.arguments; - } - } - return null; - } - - @Override - public Class nextGenericClass () { - GenericType[] arguments = nextGenericTypes(); - if (arguments == null) return null; - return arguments[0].resolve(this); + super(kryo); } @Override public int pushTypeVariables (GenericsHierarchy hierarchy, GenericType[] args) { - // Do not store type variables if hierarchy is empty or we do not have arguments for all root parameters. + // Do not store type variables if hierarchy is empty, or we do not have arguments for all root parameters. if (hierarchy.total == 0 || hierarchy.rootTotal > args.length) return 0; int startSize = this.argumentsSize; @@ -145,11 +89,6 @@ public Class resolveTypeVariable (TypeVariable typeVariable) { return null; } - @Override - public int getGenericTypesSize () { - return genericTypesSize; - } - public String toString () { StringBuilder buffer = new StringBuilder(); for (int i = 0; i < argumentsSize; i += 2) { diff --git a/src/com/esotericsoftware/kryo/util/MinimalGenerics.java b/src/com/esotericsoftware/kryo/util/MinimalGenerics.java new file mode 100644 index 000000000..01b1bd0e7 --- /dev/null +++ b/src/com/esotericsoftware/kryo/util/MinimalGenerics.java @@ -0,0 +1,44 @@ +/* Copyright (c) 2008-2022, 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.util; + +import com.esotericsoftware.kryo.Kryo; + +import java.lang.reflect.TypeVariable; + +/** Implementation of {@link Generics} that stores generic type information, but does not attempt to resolve type variables. */ +public final class MinimalGenerics extends BaseGenerics { + + public MinimalGenerics(Kryo kryo) { + super(kryo); + } + + public int pushTypeVariables(GenericsHierarchy hierarchy, GenericType[] args) { + return 0; + } + + public void popTypeVariables(int count) { + + } + + public Class resolveTypeVariable(TypeVariable typeVariable) { + return null; + } +} diff --git a/test/com/esotericsoftware/kryo/serializers/CollectionSerializerTest.java b/test/com/esotericsoftware/kryo/serializers/CollectionSerializerTest.java index 77f2bb3a5..d29f9089c 100644 --- a/test/com/esotericsoftware/kryo/serializers/CollectionSerializerTest.java +++ b/test/com/esotericsoftware/kryo/serializers/CollectionSerializerTest.java @@ -32,6 +32,7 @@ import java.util.Comparator; import java.util.LinkedList; import java.util.List; +import java.util.Objects; import java.util.TreeSet; import java.util.concurrent.CopyOnWriteArrayList; @@ -112,6 +113,17 @@ void testCopy () { assertNotSame(objects1.get(0), objects2.get(0)); } + @Test + void testGenerics() { + kryo.register(HasGenerics.class); + kryo.register(ArrayList.class); + + final HasGenerics test = new HasGenerics(); + test.list.add("moo"); + + roundTrip(6, test); + } + public static class TreeSetSubclass extends TreeSet { public TreeSetSubclass () { } @@ -120,4 +132,15 @@ public TreeSetSubclass (Comparator comparator) { super(comparator); } } + + public static class HasGenerics { + public List list = new ArrayList<>(); + + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + HasGenerics that = (HasGenerics) o; + return Objects.equals(list, that.list); + } + } } diff --git a/test/com/esotericsoftware/kryo/serializers/MapSerializerTest.java b/test/com/esotericsoftware/kryo/serializers/MapSerializerTest.java index 168ffb790..8bcd4aba6 100644 --- a/test/com/esotericsoftware/kryo/serializers/MapSerializerTest.java +++ b/test/com/esotericsoftware/kryo/serializers/MapSerializerTest.java @@ -117,7 +117,10 @@ void testGenerics () { kryo.writeClassAndObject(output, test); output.flush(); - input = new Input(output.toBytes()); + final byte[] bytes = output.toBytes(); + assertEquals(bytes.length, 13); + + input = new Input(bytes); HasGenerics test2 = (HasGenerics)kryo.readClassAndObject(input); assertArrayEquals(test.map.get("moo"), test2.map.get("moo")); }