All Downloads are FREE. Search and download functionalities are using the official Maven repository.

dorkbox.serializers.SubListSerializers Maven / Gradle / Ivy

/*
 * 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 null if the given type is not supported by these
 * serializers.
 * 
 * @author Martin Grotzke
 */
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
     * null is returned.
     * @param type the class of the sublist.
     * @return a serializer instance or null.
     */
    @SuppressWarnings("rawtypes")
    public static Serializer> 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> {

        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> 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 java.util.ArrayList$SubList).
     */
    public static class ArrayListSubListSerializer extends Serializer> {

        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 true 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> 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 java.util.AbstractList$SubList).
     */
    public static class AbstractListSubListSerializer extends Serializer> {

        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 true 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> 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 java.util.SubList.
     */
    public static class JavaUtilSubListSerializer extends Serializer> {

        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 true 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> 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);
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy