Serializers/src/dorkbox/serializers/SubListSerializers.java

335 lines
13 KiB
Java

/*
* Copyright 2010 Martin Grotzke
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package dorkbox.serializers;
import java.lang.reflect.Field;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.Serializer;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
/**
* Kryo {@link Serializer}s for lists created via {@link List#subList(int, int)}.
* An instance of a serializer can be obtained via {@link #createFor(Class)}, which
* just returns <code>null</code> if the given type is not supported by these
* serializers.
*
* @author <a href="mailto:martin.grotzke@javakaffee.de">Martin Grotzke</a>
*/
public class SubListSerializers {
static Class<?> getClass(final String className) {
try {
return Class.forName(className);
} catch (final Exception e) {
throw new RuntimeException(e);
}
}
static Class<?> getClassOrNull(final String className) {
try {
return Class.forName(className);
} catch (final Exception e) {
return null;
}
}
// Workaround reference reading, this should be removed sometimes. See also
// https://groups.google.com/d/msg/kryo-users/Eu5V4bxCfws/k-8UQ22y59AJ
private static final Object FAKE_REFERENCE = new Object();
/**
* Obtain a serializer for the given sublist type. If the type is not supported
* <code>null</code> is returned.
* @param type the class of the sublist.
* @return a serializer instance or <code>null</code>.
*/
@SuppressWarnings("rawtypes")
public static Serializer<List<?>> createFor(final Class type) {
if (ArrayListSubListSerializer.canSerialize(type))
return new ArrayListSubListSerializer();
if (JavaUtilSubListSerializer.canSerialize(type))
return new JavaUtilSubListSerializer();
return null;
}
/**
* Adds appropriate sublist serializers as default serializers.
*/
public static Kryo addDefaultSerializers(Kryo kryo) {
ArrayListSubListSerializer.addDefaultSerializer(kryo);
AbstractListSubListSerializer.addDefaultSerializer(kryo);
JavaUtilSubListSerializer.addDefaultSerializer(kryo);
return kryo;
}
/**
* Supports sublists created via {@link ArrayList#subList(int, int)} since java7 and {@link LinkedList#subList(int, int)} since java9 (openjdk).
*/
private static class SubListSerializer extends Serializer<List<?>> {
private Field _parentField;
private Field _parentOffsetField;
private Field _sizeField;
public SubListSerializer(String subListClassName) {
try {
final Class<?> clazz = Class.forName(subListClassName);
_parentField = getParentField(clazz);
_parentOffsetField = getOffsetField(clazz);
_sizeField = clazz.getDeclaredField( "size" );
_parentField.setAccessible( true );
_parentOffsetField.setAccessible( true );
_sizeField.setAccessible( true );
} catch (final Exception e) {
throw new RuntimeException(e);
}
}
private static Field getParentField(Class clazz) throws NoSuchFieldException {
try {
// java 9+
return clazz.getDeclaredField("root");
} catch(NoSuchFieldException e) {
return clazz.getDeclaredField("parent");
}
}
private static Field getOffsetField(Class<?> clazz) throws NoSuchFieldException {
try {
// up to jdk8 (which also has an "offset" field (we don't need) - therefore we check "parentOffset" first
return clazz.getDeclaredField( "parentOffset" );
} catch (NoSuchFieldException e) {
// jdk9+ only has "offset" which is the parent offset
return clazz.getDeclaredField( "offset" );
}
}
@Override
public List<?> read(final Kryo kryo, final Input input, final Class<? extends List<?>> clazz) {
kryo.reference(FAKE_REFERENCE);
final List<?> list = (List<?>) kryo.readClassAndObject(input);
final int fromIndex = input.readInt(true);
final int toIndex = input.readInt(true);
return list.subList(fromIndex, toIndex);
}
@Override
public void write(final Kryo kryo, final Output output, final List<?> obj) {
try {
kryo.writeClassAndObject(output, _parentField.get(obj));
final int parentOffset = _parentOffsetField.getInt( obj );
final int fromIndex = parentOffset;
output.writeInt(fromIndex, true);
final int toIndex = fromIndex + _sizeField.getInt( obj );
output.writeInt(toIndex, true);
} catch (final RuntimeException e) {
// Don't eat and wrap RuntimeExceptions because the ObjectBuffer.write...
// handles SerializationException specifically (resizing the buffer)...
throw e;
} catch (final Exception e) {
throw new RuntimeException(e);
}
}
@Override
public List<?> copy(final Kryo kryo, final List<?> original) {
kryo.reference(FAKE_REFERENCE);
try {
final List<?> list = (List<?>) _parentField.get(original);
final int parentOffset = _parentOffsetField.getInt( original );
final int fromIndex = parentOffset;
final int toIndex = fromIndex + _sizeField.getInt( original );
return kryo.copy(list).subList(fromIndex, toIndex);
} catch(final Exception e) {
throw new RuntimeException(e);
}
}
}
/**
* Supports sublists created via {@link ArrayList#subList(int, int)} since java7 (oracle jdk,
* represented by <code>java.util.ArrayList$SubList</code>).
*/
public static class ArrayListSubListSerializer extends Serializer<List<?>> {
public static final Class<?> SUBLIST_CLASS = SubListSerializers.getClassOrNull("java.util.ArrayList$SubList");
private final SubListSerializer delegate = new SubListSerializer("java.util.ArrayList$SubList");
/**
* Can be used to determine, if the given type can be handled by this serializer.
*
* @param type
* the class to check.
* @return <code>true</code> if the given class can be serialized/deserialized by this serializer.
*/
public static boolean canSerialize(final Class<?> type) {
return SUBLIST_CLASS != null && SUBLIST_CLASS.isAssignableFrom(type);
}
public static Kryo addDefaultSerializer(Kryo kryo) {
if(SUBLIST_CLASS != null) kryo.addDefaultSerializer(SUBLIST_CLASS, new ArrayListSubListSerializer());
return kryo;
}
@Override
public List<?> read(final Kryo kryo, final Input input, final Class<? extends List<?>> clazz) {
return delegate.read(kryo, input, clazz);
}
@Override
public void write(final Kryo kryo, final Output output, final List<?> obj) {
delegate.write(kryo, output, obj);
}
@Override
public List<?> copy(final Kryo kryo, final List<?> original) {
return delegate.copy(kryo, original);
}
}
/**
* Supports sublists created via {@link LinkedList#subList(int, int)} since java9 (oracle jdk,
* represented by <code>java.util.AbstractList$SubList</code>).
*/
public static class AbstractListSubListSerializer extends Serializer<List<?>> {
public static final Class<?> SUBLIST_CLASS = SubListSerializers.getClassOrNull("java.util.AbstractList$SubList");
private final SubListSerializer delegate = new SubListSerializer("java.util.AbstractList$SubList");
/**
* Can be used to determine, if the given type can be handled by this serializer.
*
* @param type
* the class to check.
* @return <code>true</code> if the given class can be serialized/deserialized by this serializer.
*/
public static boolean canSerialize(final Class<?> type) {
return SUBLIST_CLASS != null && SUBLIST_CLASS.isAssignableFrom(type);
}
public static Kryo addDefaultSerializer(Kryo kryo) {
if(SUBLIST_CLASS != null) kryo.addDefaultSerializer(SUBLIST_CLASS, new AbstractListSubListSerializer());
return kryo;
}
@Override
public List<?> read(final Kryo kryo, final Input input, final Class<? extends List<?>> clazz) {
return delegate.read(kryo, input, clazz);
}
@Override
public void write(final Kryo kryo, final Output output, final List<?> obj) {
delegate.write(kryo, output, obj);
}
@Override
public List<?> copy(final Kryo kryo, final List<?> original) {
return delegate.copy(kryo, original);
}
}
/**
* Supports sublists created via {@link AbstractList#subList(int, int)}, e.g. LinkedList.
* In oracle jdk such sublists are represented by <code>java.util.SubList</code>.
*/
public static class JavaUtilSubListSerializer extends Serializer<List<?>> {
public static final Class<?> SUBLIST_CLASS = SubListSerializers.getClassOrNull("java.util.SubList");
private Field _listField;
private Field _offsetField;
private Field _sizeField;
public JavaUtilSubListSerializer() {
try {
final Class<?> clazz = Class.forName("java.util.SubList");
_listField = clazz.getDeclaredField("l");
_offsetField = clazz.getDeclaredField("offset");
_sizeField = clazz.getDeclaredField("size");
_listField.setAccessible(true);
_offsetField.setAccessible(true);
_sizeField.setAccessible(true);
} catch (final Exception e) {
throw new RuntimeException(e);
}
}
/**
* Can be used to determine, if the given type can be handled by this serializer.
*
* @param type
* the class to check.
* @return <code>true</code> if the given class can be serialized/deserialized by this serializer.
*/
public static boolean canSerialize(final Class<?> type) {
return SUBLIST_CLASS != null && SUBLIST_CLASS.isAssignableFrom(type);
}
public static Kryo addDefaultSerializer(Kryo kryo) {
if(SUBLIST_CLASS != null) kryo.addDefaultSerializer(SUBLIST_CLASS, new JavaUtilSubListSerializer());
return kryo;
}
@Override
public List<?> read(final Kryo kryo, final Input input, final Class<? extends List<?>> clazz) {
kryo.reference(FAKE_REFERENCE);
final List<?> list = (List<?>) kryo.readClassAndObject(input);
final int fromIndex = input.readInt(true);
final int toIndex = input.readInt(true);
return list.subList(fromIndex, toIndex);
}
@Override
public void write(final Kryo kryo, final Output output, final List<?> obj) {
try {
kryo.writeClassAndObject(output, _listField.get(obj));
final int fromIndex = _offsetField.getInt(obj);
output.writeInt(fromIndex, true);
final int toIndex = fromIndex + _sizeField.getInt(obj);
output.writeInt(toIndex, true);
} catch (final RuntimeException e) {
// Don't eat and wrap RuntimeExceptions because the ObjectBuffer.write...
// handles SerializationException specifically (resizing the buffer)...
throw e;
} catch (final Exception e) {
throw new RuntimeException(e);
}
}
@Override
public List<?> copy(final Kryo kryo, final List<?> obj) {
kryo.reference(FAKE_REFERENCE);
try {
final List<?> list = (List<?>) _listField.get(obj);
final int fromIndex = _offsetField.getInt(obj);
final int toIndex = fromIndex + _sizeField.getInt(obj);
return kryo.copy(list).subList(fromIndex, toIndex);
} catch(final Exception e) {
throw new RuntimeException(e);
}
}
}
}