Created
December 17, 2015 10:53
-
-
Save mikehearn/53b4cc21033314cafb3d to your computer and use it in GitHub Desktop.
A Kryo serialiser that lets you serialise immutable classes like "class Foo(val a: Int, val b: Bar)" without bypassing the c'tor.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
interface SerializeableWithKryo | |
class ImmutableClassSerializer<T : SerializeableWithKryo>(val klass: KClass<T>) : Serializer<T>() { | |
val props = klass.memberProperties.sortedBy { it.name } | |
val propsByName = props.toMapBy { it.name } | |
val constructor = klass.primaryConstructor!! | |
init { | |
// Verify that this class is immutable (all properties are final) | |
assert(props.none { it is KMutableProperty<*> }) | |
} | |
override fun write(kryo: Kryo, output: Output, obj: T) { | |
output.writeVarInt(constructor.parameters.size, true) | |
output.writeInt(constructor.parameters.hashCode()) | |
for (param in constructor.parameters) { | |
val kProperty = propsByName[param.name!!]!! | |
when (param.type.javaType.typeName) { | |
"int" -> output.writeVarInt(kProperty.get(obj) as Int, true) | |
"long" -> output.writeVarLong(kProperty.get(obj) as Long, true) | |
"short" -> output.writeShort(kProperty.get(obj) as Int) | |
"char" -> output.writeChar(kProperty.get(obj) as Char) | |
"byte" -> output.writeByte(kProperty.get(obj) as Byte) | |
"double" -> output.writeDouble(kProperty.get(obj) as Double) | |
"float" -> output.writeFloat(kProperty.get(obj) as Float) | |
else -> try { | |
kryo.writeClassAndObject(output, kProperty.get(obj)) | |
} catch (e: Exception) { | |
throw IllegalStateException("Failed to serialize ${param.name} in ${klass.qualifiedName}", e) | |
} | |
} | |
} | |
} | |
override fun read(kryo: Kryo, input: Input, type: Class<T>): T { | |
assert(type.kotlin == klass) | |
assert(kryo.isRegistrationRequired) | |
val numFields = input.readVarInt(true) | |
val fieldTypeHash = input.readInt() | |
// A few quick checks for data evolution. Note that this is not guaranteed to catch every problem! But it's | |
// good enough for a prototype. | |
if (numFields != constructor.parameters.size) | |
throw KryoException("Mismatch between number of constructor parameters and number of serialised fields for ${klass.qualifiedName} ($numFields vs ${constructor.parameters.size})") | |
if (fieldTypeHash != constructor.parameters.hashCode()) | |
throw KryoException("Hashcode mismatch for parameter types for ${klass.qualifiedName}: unsupported type evolution has happened.") | |
val args = arrayOfNulls<Any?>(numFields) | |
var cursor = 0 | |
for (param in constructor.parameters) { | |
args[cursor++] = when (param.type.javaType.typeName) { | |
"int" -> input.readVarInt(true) | |
"long" -> input.readVarLong(true) | |
"short" -> input.readShort() | |
"char" -> input.readChar() | |
"byte" -> input.readByte() | |
"double" -> input.readDouble() | |
"float" -> input.readFloat() | |
else -> kryo.readClassAndObject(input) | |
} | |
} | |
// If the constructor throws an exception, pass it through instead of wrapping it. | |
return try { constructor.call(*args) } catch (e: InvocationTargetException) { throw e.cause!! } | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Example usage?