dorkbox.network.pipeline.tcp.KryoDecoder.java Source code

Java tutorial

Introduction

Here is the source code for dorkbox.network.pipeline.tcp.KryoDecoder.java

Source

/*
 * Copyright 2018 dorkbox, llc.
 *
 *  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.network.pipeline.tcp;

import java.io.IOException;
import java.util.List;

import dorkbox.network.serialization.NetworkSerializationManager;
import dorkbox.util.bytes.OptimizeUtilsByteBuf;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;

public class KryoDecoder extends ByteToMessageDecoder {
    private final NetworkSerializationManager serializationManager;

    public KryoDecoder(final NetworkSerializationManager serializationManager) {
        super();
        this.serializationManager = serializationManager;
    }

    @SuppressWarnings("unused")
    protected Object readObject(NetworkSerializationManager serializationManager, ChannelHandlerContext context,
            ByteBuf in, int length) throws Exception {
        // no connection here because we haven't created one yet. When we do, we replace this handler with a new one.
        return serializationManager.read(in, length);
    }

    @Override
    protected void decode(ChannelHandlerContext context, ByteBuf in, List<Object> out) throws Exception {
        // Make sure if the length field was received,
        // and read the length of the next object from the socket.
        int lengthLength = OptimizeUtilsByteBuf.canReadInt(in);
        int readableBytes = in.readableBytes(); // full length of available bytes.

        if (lengthLength == 0 || readableBytes < 2 || readableBytes < lengthLength) {
            // The length field was not fully received - do nothing (wait for more...)
            // This method will be invoked again when more packets are
            // received and appended to the buffer.
            return;
        }

        // The length field is in the buffer.

        // save the writerIndex for local access
        int writerIndex = in.writerIndex();

        // Mark the current buffer position before reading the length fields
        // because the whole frame might not be in the buffer yet.
        // We will reset the buffer position to the marked position if
        // there's not enough bytes in the buffer.
        in.markReaderIndex();

        // Read the length field.
        int length = OptimizeUtilsByteBuf.readInt(in, true);
        readableBytes = in.readableBytes(); // have to adjust readable bytes, since we just read an int off the buffer.

        if (length == 0) {
            context.fireExceptionCaught(new IllegalStateException("KryoDecoder had a read length of 0"));
            return;
        }

        // we can't test against a single "max size", since objects can back-up on the buffer.
        // we must ABSOLUTELY follow a "max size" rule when encoding objects, however.

        final NetworkSerializationManager serializationManager = this.serializationManager;

        // Make sure if there's enough bytes in the buffer.
        if (length > readableBytes) {
            // The whole bytes were not received yet - return null.
            // This method will be invoked again when more packets are
            // received and appended to the buffer.

            // Reset to the marked position to read the length field again
            // next time.
            in.resetReaderIndex();

            // wait for the rest of the object to come in.
            // System.err.println(Thread.currentThread().getName() + " waiting for more of the object to arrive");
        }

        // how many objects are on this buffer?
        else if (readableBytes > length) {
            // more than one!
            // read the object off of the buffer. (save parts of the buffer so if it is too big, we can go back to it, and use it later on...)

            // we know we have at least one object
            int objectCount = 1;
            int endOfObjectPosition = in.readerIndex() + length;

            // set us up for the next object.
            in.readerIndex(endOfObjectPosition);

            // how many more objects?? The first time, it can be off, because we already KNOW it's > 0.
            //  (That's how we got here to begin with)
            while (readableBytes > 0) {
                if (OptimizeUtilsByteBuf.canReadInt(in) > 0) {
                    length = OptimizeUtilsByteBuf.readInt(in, true);

                    if (length <= 0) {
                        // throw new IllegalStateException("Kryo DecoderTCP had a read length of 0");
                        break;
                    }

                    endOfObjectPosition = in.readerIndex() + length;

                    // position the reader to look for the NEXT object
                    if (endOfObjectPosition <= writerIndex) {
                        in.readerIndex(endOfObjectPosition);
                        readableBytes = in.readableBytes();
                        objectCount++;
                    } else {
                        break;
                    }
                } else {
                    break;
                }
            }
            // readerIndex is currently at the MAX place it can read data off the buffer.
            // reset it to the spot BEFORE we started reading data from the buffer.
            in.resetReaderIndex();

            // System.err.println("Found " + objectCount + " objects in this buffer.");

            // NOW add each one of the NEW objects to the array!

            for (int i = 0; i < objectCount; i++) {
                length = OptimizeUtilsByteBuf.readInt(in, true); // object LENGTH

                // however many we need to
                Object object;
                try {
                    object = readObject(serializationManager, context, in, length);
                    out.add(object);
                } catch (Exception ex) {
                    context.fireExceptionCaught(new IOException("Unable to deserialize object!", ex));
                }
            }
            // the buffer reader index will be at the correct location, since the read object method advances it.
        } else {
            // exactly one!
            Object object;
            try {
                object = readObject(serializationManager, context, in, length);
                out.add(object);
            } catch (Exception ex) {
                context.fireExceptionCaught(
                        new IOException("Unable to deserialize object for " + this.getClass(), ex));
            }
        }
    }
}