Skip to content

Commit

Permalink
Fixed issue #314.
Browse files Browse the repository at this point in the history
Improve serialisation of generics:
- Don't crash on generic classes containing fields of unbound generic classes, e.g. class MyClass<T> { ArrayList<T> field; }
- Don't crash on concrete classes with bound generic superclasses, e.g. class MyClass: Superclass<Int>
  • Loading branch information
romix committed Jun 3, 2015
1 parent 7da2f22 commit 4764dee
Show file tree
Hide file tree
Showing 2 changed files with 208 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,27 @@ Generics buildGenericsScope (Class clazz, Class[] generics) {
else
typeParams = typ.getTypeParameters();
if (typeParams == null || typeParams.length == 0) {
if (typ == this.serializer.type)
if (typ == this.serializer.type) {
typ = this.serializer.componentType;
else
if (typ != null) continue;
// This is not a generic type.
// Check if its superclass is generic.
typ = this.serializer.type;
Type superclass = null;
do {
superclass = typ.getGenericSuperclass();
typ = typ.getSuperclass();
} while (superclass != null && !(superclass instanceof ParameterizedType));
if (superclass == null) break;
ParameterizedType pt = (ParameterizedType)superclass;
Type[] typeArgs = pt.getActualTypeArguments();
typeParams = typ.getTypeParameters();
generics = new Class[typeArgs.length];
for (int i = 0; i < typeArgs.length; i++) {
generics[i] = (typeArgs[i] instanceof Class) ? (Class)typeArgs[i] : Object.class;
}
break;
} else
typ = typ.getComponentType();
} else
break;
Expand Down Expand Up @@ -142,8 +160,12 @@ Class[] computeFieldGenerics (Type fieldGenericType, Field field, Class[] fieldC
fieldGenerics[i] = (Class)t;
else if (t instanceof ParameterizedType)
fieldGenerics[i] = (Class)((ParameterizedType)t).getRawType();
else if (t instanceof TypeVariable && serializer.getGenericsScope() != null)
fieldGenerics[i] = serializer.getGenericsScope().getConcreteClass(((TypeVariable)t).getName());
else if (t instanceof TypeVariable && serializer.getGenericsScope() != null) {
fieldGenerics[i] = serializer.getGenericsScope().
getConcreteClass(((TypeVariable)t).getName());
if (fieldGenerics[i] == null)
fieldGenerics[i] = Object.class;
}
else if (t instanceof WildcardType)
fieldGenerics[i] = Object.class;
else if (t instanceof GenericArrayType) {
Expand Down
182 changes: 182 additions & 0 deletions test/com/esotericsoftware/kryo/GenericsTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
/* Copyright (c) 2008, 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;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.io.Serializable;

import org.objenesis.strategy.StdInstantiatorStrategy;

import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import com.esotericsoftware.kryo.serializers.CollectionSerializer.BindCollection;
import com.esotericsoftware.kryo.serializers.DefaultArraySerializers.IntArraySerializer;
import com.esotericsoftware.kryo.serializers.DefaultArraySerializers.LongArraySerializer;
import com.esotericsoftware.kryo.serializers.DefaultArraySerializers.ObjectArraySerializer;
import com.esotericsoftware.kryo.serializers.DefaultSerializers.StringSerializer;
import com.esotericsoftware.kryo.serializers.FieldSerializer;
import com.esotericsoftware.kryo.serializers.FieldSerializer.Bind;
import com.esotericsoftware.kryo.serializers.FieldSerializer.Optional;
import com.esotericsoftware.kryo.serializers.MapSerializer;
import com.esotericsoftware.kryo.serializers.CollectionSerializer;
import com.esotericsoftware.kryo.serializers.MapSerializer.BindMap;

/** @author Nathan Sweet <[email protected]> */
public class GenericsTest extends KryoTestCase {
{
supportsCopy = true;
}

public void testGenericClassWithGenericFields () throws Exception {
kryo.setReferences(true);
kryo.setRegistrationRequired(false);
kryo.setAsmEnabled(true);
kryo.register(BaseGeneric.class);

List list = Arrays.asList(
new SerializableObjectFoo("one"),
new SerializableObjectFoo("two"),
new SerializableObjectFoo("three"));
BaseGeneric<SerializableObjectFoo> bg1 = new BaseGeneric<SerializableObjectFoo>(list);

roundTrip(108, 108, bg1);
}

public void testNonGenericClassWithGenericSuperclass () throws Exception {
kryo.setReferences(true);
kryo.setRegistrationRequired(false);
kryo.setAsmEnabled(true);
kryo.register(BaseGeneric.class);
kryo.register(ConcreteClass.class);

List list = Arrays.asList(
new SerializableObjectFoo("one"),
new SerializableObjectFoo("two"),
new SerializableObjectFoo("three"));
ConcreteClass cc1 = new ConcreteClass(list);

roundTrip(108, 108, cc1);
}

// A simple serializable class.
private static class SerializableObjectFoo implements Serializable {
String name;

SerializableObjectFoo (String name) {
this.name = name;
}

public SerializableObjectFoo () {
name = "Default";
}

@Override
public boolean equals (Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
SerializableObjectFoo other = (SerializableObjectFoo)obj;
if (name == null) {
if (other.name != null) return false;
} else if (!name.equals(other.name)) return false;
return true;
}
}

private static class BaseGeneric<T extends Serializable> {

// The type of this field cannot be derived from the context.
// Therefore, Kryo should consider it to be Object.
private final List<T> listPayload;

/** Kryo Constructor */
protected BaseGeneric () {
super();
this.listPayload = null;
}

protected BaseGeneric (final List<T> listPayload) {
super();
// Defensive copy, listPayload is mutable
this.listPayload = new ArrayList<T>(listPayload);
}

public final List<T> getPayload () {
return this.listPayload;
}

@Override
public boolean equals (Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
BaseGeneric other = (BaseGeneric)obj;
if (listPayload == null) {
if (other.listPayload != null) return false;
} else if (!listPayload.equals(other.listPayload)) return false;
return true;
}

}

// This is a non-generic class with a generic superclass.
private static class ConcreteClass2 extends BaseGeneric<SerializableObjectFoo> {
/** Kryo Constructor */
@SuppressWarnings("unused")
private ConcreteClass2 () {
super();
}

public ConcreteClass2 (final List listPayload) {
super(listPayload);
}
}

private static class ConcreteClass1 extends ConcreteClass2 {
/** Kryo Constructor */
@SuppressWarnings("unused")
private ConcreteClass1 () {
super();
}

public ConcreteClass1 (final List listPayload) {
super(listPayload);
}
}

private static class ConcreteClass extends ConcreteClass1 {
/** Kryo Constructor */
@SuppressWarnings("unused")
private ConcreteClass () {
super();
}

public ConcreteClass (final List listPayload) {
super(listPayload);
}
}
}

0 comments on commit 4764dee

Please sign in to comment.