/*
 * Copyright (c) 2013-2018 Cinchapi Inc.
 *
 * 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 com.cinchapi.concourse.server.plugin.io;

import java.io.Serializable;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.cinchapi.common.io.ByteBuffers;
import com.cinchapi.common.reflect.Reflection;
import com.cinchapi.concourse.server.plugin.RemoteMessage;
import com.cinchapi.concourse.thrift.ComplexTObject;
import com.cinchapi.concourse.thrift.TObject;
import com.cinchapi.concourse.thrift.Type;
import com.cinchapi.concourse.util.Serializables;

import io.atomix.catalyst.buffer.Buffer;
import io.atomix.catalyst.buffer.HeapBuffer;

/**
 * A tool for transparently serializing data passed among plugins and Concourse
 * Server, regardless of their type.
 * <p>
 * A {@link PluginSerializer} is a one-stop shop for dealing with all the
 * serialization protocols used by different classes within the framework. It
 * compactly embeds just enough information to be able to represent almost any
 * class.
 * </p>
 * 
 * @author Jeff Nelson
 */
public class PluginSerializer {

    /**
     * Return the object represented by the serialized data in the
     * {@link ByteBuffer}.
     * 
     * @param bytes the bytes from which the object is deserialized
     * @return the deserialized object
     */
    @SuppressWarnings("unchecked")
    public <T> T deserialize(ByteBuffer bytes) {
        Scheme scheme = Scheme.values()[bytes.get()];
        if(scheme == Scheme.PLUGIN_SERIALIZABLE) {
            Buffer buffer = HeapBuffer.wrap(ByteBuffers.getByteArray(bytes));
            buffer.position(bytes.position());
            Class<T> clazz = Reflection.getClassCasted(buffer.readUTF8());
            T instance = Reflection.newInstance(clazz);
            ((PluginSerializable) instance).deserialize(buffer);
            return instance;
        }
        else if(scheme == Scheme.REMOTE_MESSAGE) {
            Buffer buffer = HeapBuffer.wrap(ByteBuffers.getByteArray(bytes));
            buffer.position(bytes.position());
            T instance = (T) RemoteMessage.fromBuffer(buffer);
            return instance;
        }
        else if(scheme == Scheme.COMPLEX_TOBJECT) {
            ByteBuffer bytes0 = ByteBuffers.slice(bytes, 1, bytes.remaining());
            return (T) ComplexTObject.fromByteBuffer(bytes0);
        }
        else if(scheme == Scheme.TOBJECT) {
            ByteBuffer bytes0 = ByteBuffers.slice(bytes, 1, bytes.remaining());
            Type type = Type.values()[bytes0.get()];
            return (T) new TObject(
                    ByteBuffers.slice(bytes0, bytes0.remaining()), type);
        }
        else if(scheme == Scheme.JAVA_SERIALIZABLE) {
            int classLength = bytes.getShort();
            byte[] className = new byte[classLength];
            bytes.get(className);
            Class<T> clazz = Reflection.getClassCasted(
                    new String(className, StandardCharsets.UTF_8));
            bytes = ByteBuffers.get(bytes, bytes.remaining());
            Serializable instance = Serializables.read(bytes,
                    (Class<? extends Serializable>) clazz);
            return (T) instance;
        }
        else {
            // NOTE: In the future, if/when we add support for storing binary
            // blobs, we will need to add a scheme to distinguish a legitimate
            // blob from a blob that is really just a PluginSerializable object
            throw new IllegalStateException(
                    "Cannot plugin deserialize the provided byte stream");
        }
    }

    /**
     * Return a {@link ByteBuffer} that contains the serialized form of the
     * input {@code object}.
     * 
     * @param object the object to serialize
     * @return the serialized form within a ByteBuffer
     */
    public ByteBuffer serialize(Object object) {
        ByteBuffer buffer = null;
        if(object instanceof PluginSerializable) {
            HeapBuffer buffer0 = HeapBuffer.allocate();
            buffer0.writeByte(Scheme.PLUGIN_SERIALIZABLE.ordinal());
            buffer0.writeUTF8(object.getClass().getName());
            ((PluginSerializable) object).serialize(buffer0);
            byte[] bytes = new byte[(int) buffer0.position()];
            buffer0.flip();
            buffer0.read(bytes);
            buffer = ByteBuffer.wrap(bytes);
            return buffer;
        }
        else if(object instanceof RemoteMessage) {
            HeapBuffer buffer0 = (HeapBuffer) ((RemoteMessage) object)
                    .serialize();
            buffer = ByteBuffer.allocate((int) buffer0.remaining() + 1);
            buffer.put((byte) Scheme.REMOTE_MESSAGE.ordinal());
            buffer.put(buffer0.array(), 0, (int) buffer0.remaining());
            buffer.flip();
            return buffer;
        }
        else if(object instanceof ComplexTObject) {
            byte[] bytes = ((ComplexTObject) object).toByteBuffer().array();
            buffer = ByteBuffer.allocate(bytes.length + 1);
            buffer.put((byte) Scheme.COMPLEX_TOBJECT.ordinal());
            buffer.put(bytes);
            buffer.flip();
            return buffer;
        }
        else if(object instanceof TObject) {
            byte[] bytes = ((TObject) object).getData();
            buffer = ByteBuffer.allocate(bytes.length + 2);
            buffer.put((byte) Scheme.TOBJECT.ordinal());
            buffer.put((byte) ((TObject) object).getType().ordinal());
            buffer.put(bytes);
            buffer.flip();
            return buffer;
        }
        else if(object instanceof String || object.getClass().isPrimitive()
                || object instanceof Number || object instanceof Boolean
                || object instanceof Map || object instanceof List
                || object instanceof Set) {
            return serialize(ComplexTObject.fromJavaObject(object));
        }
        else if(object instanceof Serializable) {
            byte[] bytes = ByteBuffers.getByteArray(
                    Serializables.getBytes((Serializable) object));
            byte[] classBytes = object.getClass().getName()
                    .getBytes(StandardCharsets.UTF_8);
            buffer = ByteBuffer
                    .allocate(1 + 2 + classBytes.length + bytes.length);
            buffer.put((byte) Scheme.JAVA_SERIALIZABLE.ordinal());
            buffer.putShort((short) classBytes.length);
            buffer.put(classBytes);
            buffer.put(bytes);
            buffer.flip();
            return buffer;
        }
        else {
            throw new IllegalStateException(
                    "Cannot plugin serialize an object of type "
                            + object.getClass());
        }
    }

    /**
     * A list of all the serialization schemes.
     * 
     * @author Jeff Nelson
     */
    public enum Scheme {
        COMPLEX_TOBJECT,
        JAVA_SERIALIZABLE,
        PLUGIN_SERIALIZABLE,
        TOBJECT,
        REMOTE_MESSAGE;
    }
}

@@ -1929,16 +1929,18 @@
 arnings(
+%7B 
 %22uncheck
@@ -1942,16 +1942,33 @@
 checked%22
+, %22deprecation%22 %7D
 )%0A    pu
@@ -2163,35 +2163,34 @@
 rap(ByteBuffers.
-ge
 t
+o
 ByteArray(bytes)
@@ -2573,35 +2573,34 @@
 rap(ByteBuffers.
-ge
 t
+o
 ByteArray(bytes)
@@ -4404,32 +4404,69 @@
 eBuffer%0A     */%0A
+    @SuppressWarnings(%22deprecation%22)%0A
     public ByteB
@@ -6629,22 +6629,8 @@
 fers
-.getByteArray(
 %0A   
@@ -6638,32 +6638,45 @@
                 
+.toByteArray(
 Serializables.ge

Use toByteArray instead of getByteArray