Skip to content

Commit

Permalink
Add MinimalGenerics and allow users to set a GenericStrategy
Browse files Browse the repository at this point in the history
  • Loading branch information
theigl committed Jan 26, 2022
1 parent 9241663 commit 1c841e9
Show file tree
Hide file tree
Showing 6 changed files with 198 additions and 66 deletions.
32 changes: 31 additions & 1 deletion src/com/esotericsoftware/kryo/Kryo.java
Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand Down
93 changes: 93 additions & 0 deletions 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;
}

}
67 changes: 3 additions & 64 deletions src/com/esotericsoftware/kryo/util/DefaultGenerics.java
Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down
44 changes: 44 additions & 0 deletions 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 only. */
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;
}
}
Expand Up @@ -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;

Expand Down Expand Up @@ -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<E> extends TreeSet<E> {
public TreeSetSubclass () {
}
Expand All @@ -120,4 +132,15 @@ public TreeSetSubclass (Comparator<? super E> comparator) {
super(comparator);
}
}

public static class HasGenerics {
public List<String> 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);
}
}
}
Expand Up @@ -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"));
}
Expand Down

0 comments on commit 1c841e9

Please sign in to comment.