/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 org.apache.ignite.internal.binary;

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.net.InetSocketAddress;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.AbstractQueue;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Queue;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import com.google.common.collect.ImmutableList;
import org.apache.ignite.IgniteCheckedException;
import org.apache.ignite.IgniteException;
import org.apache.ignite.IgniteSystemProperties;
import org.apache.ignite.binary.BinaryBasicIdMapper;
import org.apache.ignite.binary.BinaryBasicNameMapper;
import org.apache.ignite.binary.BinaryCollectionFactory;
import org.apache.ignite.binary.BinaryField;
import org.apache.ignite.binary.BinaryIdMapper;
import org.apache.ignite.binary.BinaryMapFactory;
import org.apache.ignite.binary.BinaryNameMapper;
import org.apache.ignite.binary.BinaryObject;
import org.apache.ignite.binary.BinaryObjectBuilder;
import org.apache.ignite.binary.BinaryObjectException;
import org.apache.ignite.binary.BinaryRawReader;
import org.apache.ignite.binary.BinaryRawWriter;
import org.apache.ignite.binary.BinaryReader;
import org.apache.ignite.binary.BinarySerializer;
import org.apache.ignite.binary.BinaryType;
import org.apache.ignite.binary.BinaryTypeConfiguration;
import org.apache.ignite.binary.BinaryWriter;
import org.apache.ignite.binary.Binarylizable;
import org.apache.ignite.configuration.BinaryConfiguration;
import org.apache.ignite.configuration.IgniteConfiguration;
import org.apache.ignite.internal.binary.builder.BinaryObjectBuilderImpl;
import org.apache.ignite.internal.binary.streams.BinaryHeapInputStream;
import org.apache.ignite.internal.binary.streams.BinaryHeapOutputStream;
import org.apache.ignite.internal.managers.discovery.GridDiscoveryManager;
import org.apache.ignite.internal.managers.systemview.GridSystemViewManager;
import org.apache.ignite.internal.managers.systemview.JmxSystemViewExporterSpi;
import org.apache.ignite.internal.processors.cache.CacheObjectContext;
import org.apache.ignite.internal.processors.platform.utils.PlatformUtils;
import org.apache.ignite.internal.util.GridUnsafe;
import org.apache.ignite.internal.util.lang.GridMapEntry;
import org.apache.ignite.internal.util.lang.IgnitePair;
import org.apache.ignite.internal.util.lang.IgniteThrowableConsumer;
import org.apache.ignite.internal.util.typedef.F;
import org.apache.ignite.internal.util.typedef.T2;
import org.apache.ignite.internal.util.typedef.internal.S;
import org.apache.ignite.internal.util.typedef.internal.U;
import org.apache.ignite.lang.IgniteUuid;
import org.apache.ignite.logger.NullLogger;
import org.apache.ignite.marshaller.MarshallerContextTestImpl;
import org.apache.ignite.spi.discovery.DiscoverySpiCustomMessage;
import org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi;
import org.apache.ignite.testframework.GridTestUtils;
import org.apache.ignite.testframework.junits.GridTestKernalContext;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.junit.Assert;
import org.junit.Test;

import static java.nio.charset.StandardCharsets.UTF_8;
import static org.apache.ignite.internal.binary.streams.BinaryMemoryAllocator.THREAD_LOCAL;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertNotEquals;

/**
 * Binary marshaller tests.
 */
@SuppressWarnings({"OverlyStrongTypeCast", "ConstantConditions"})
public class BinaryMarshallerSelfTest extends AbstractBinaryArraysTest {
    /**
     * @throws Exception If failed.
     */
    @Test
    public void testNull() throws Exception {
        assertNull(marshalUnmarshal(null));
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testByte() throws Exception {
        assertEquals((byte)100, marshalUnmarshal((byte)100).byteValue());
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testShort() throws Exception {
        assertEquals((short)100, marshalUnmarshal((short)100).shortValue());
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testInt() throws Exception {
        assertEquals(100, marshalUnmarshal(100).intValue());
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testLong() throws Exception {
        assertEquals(100L, marshalUnmarshal(100L).longValue());
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testFloat() throws Exception {
        assertEquals(100.001f, marshalUnmarshal(100.001f), 0);
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testDouble() throws Exception {
        assertEquals(100.001d, marshalUnmarshal(100.001d), 0);
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testChar() throws Exception {
        assertEquals((char)100, marshalUnmarshal((char)100).charValue());
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testBoolean() throws Exception {
        assertEquals(true, marshalUnmarshal(true).booleanValue());
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testDecimal() throws Exception {
        BigDecimal val;

        assertEquals((val = BigDecimal.ZERO), marshalUnmarshal(val));
        assertEquals((val = BigDecimal.valueOf(Long.MAX_VALUE, 0)), marshalUnmarshal(val));
        assertEquals((val = BigDecimal.valueOf(Long.MIN_VALUE, 0)), marshalUnmarshal(val));
        assertEquals((val = BigDecimal.valueOf(Long.MAX_VALUE, 8)), marshalUnmarshal(val));
        assertEquals((val = BigDecimal.valueOf(Long.MIN_VALUE, 8)), marshalUnmarshal(val));

        assertEquals((val = new BigDecimal(new BigInteger("-79228162514264337593543950336"))), marshalUnmarshal(val));
    }


    /**
     * @throws Exception If failed.
     */
    @Test
    public void testNegativeScaleDecimal() throws Exception {
        BigDecimal val;

        assertEquals((val = BigDecimal.valueOf(Long.MAX_VALUE, -1)), marshalUnmarshal(val));
        assertEquals((val = BigDecimal.valueOf(Long.MIN_VALUE, -2)), marshalUnmarshal(val));
        assertEquals((val = BigDecimal.valueOf(Long.MAX_VALUE, -3)), marshalUnmarshal(val));
        assertEquals((val = BigDecimal.valueOf(Long.MIN_VALUE, -4)), marshalUnmarshal(val));
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testNegativeScaleRoundingModeDecimal() throws Exception {
        BigDecimal val;

        assertEquals((val = BigDecimal.ZERO.setScale(-1, RoundingMode.HALF_UP)), marshalUnmarshal(val));
        assertEquals((val = BigDecimal.valueOf(Long.MAX_VALUE).setScale(-3, RoundingMode.HALF_DOWN)), marshalUnmarshal(val));
        assertEquals((val = BigDecimal.valueOf(Long.MIN_VALUE).setScale(-5, RoundingMode.HALF_EVEN)), marshalUnmarshal(val));
        assertEquals((val = BigDecimal.valueOf(Integer.MAX_VALUE).setScale(-8, RoundingMode.UP)), marshalUnmarshal(val));
        assertEquals((val = BigDecimal.valueOf(Integer.MIN_VALUE).setScale(-10, RoundingMode.DOWN)), marshalUnmarshal(val));
        assertEquals((val = BigDecimal.valueOf(Double.MAX_VALUE).setScale(-12, RoundingMode.CEILING)), marshalUnmarshal(val));
        assertEquals((val = BigDecimal.valueOf(Double.MIN_VALUE).setScale(-15, RoundingMode.FLOOR)), marshalUnmarshal(val));
    }


    /**
     * @throws Exception If failed.
     */
    @Test
    public void testStringVer1() throws Exception {
        doTestString(false);
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testStringVer2() throws Exception {
        doTestString(true);
    }

    /**
     * @throws Exception If failed
     */
    private void doTestString(boolean ver2) throws Exception {
        // Ascii check.
        String str = "ascii0123456789";
        assertEquals(str, marshalUnmarshal(str));

        byte[] bytes = str.getBytes(UTF_8);
        assertEquals(str, BinaryUtils.utf8BytesToStr(bytes, 0, bytes.length));

        bytes = BinaryUtils.strToUtf8Bytes(str);
        assertEquals(str, new String(bytes, UTF_8));

        // Extended symbols set check set.
        str = "的的abcdкириллица";
        assertEquals(str, marshalUnmarshal(str));

        bytes = str.getBytes(UTF_8);
        assertEquals(str, BinaryUtils.utf8BytesToStr(bytes, 0, bytes.length));

        bytes = BinaryUtils.strToUtf8Bytes(str);
        assertEquals(str, new String(bytes, UTF_8));

        // Special symbols check.
        str = new String(new char[] {0xD800, '的', 0xD800, 0xD800, 0xDC00, 0xDFFF});
        if (ver2) {
            bytes = BinaryUtils.strToUtf8Bytes(str);
            assertEquals(str, BinaryUtils.utf8BytesToStr(bytes, 0, bytes.length));
        }
        else
            assertNotEquals(str, marshalUnmarshal(str));

        str = new String(new char[] {55296});
        if (ver2) {
            bytes = BinaryUtils.strToUtf8Bytes(str);
            assertEquals(str, BinaryUtils.utf8BytesToStr(bytes, 0, bytes.length));
        }
        else
            assertNotEquals(str, marshalUnmarshal(str));

        bytes = str.getBytes(UTF_8);
        assertNotEquals(str, new String(bytes, UTF_8));

        bytes = str.getBytes(UTF_8);
        assertNotEquals(str, BinaryUtils.utf8BytesToStr(bytes, 0, bytes.length));

        str = new String(new char[] {0xD801, 0xDC37});
        assertEquals(str, marshalUnmarshal(str));

        bytes = str.getBytes(UTF_8);
        assertEquals(str, new String(bytes, UTF_8));
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testUuid() throws Exception {
        UUID uuid = UUID.randomUUID();

        assertEquals(uuid, marshalUnmarshal(uuid));
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testIgniteUuid() throws Exception {
        IgniteUuid uuid = IgniteUuid.randomUuid();

        assertEquals(uuid, marshalUnmarshal(uuid));
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testDate() throws Exception {
        Date date = new Date();

        Date val = marshalUnmarshal(date);

        assertEquals(date, val);
        assertEquals(Date.class, val.getClass());
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testTimestamp() throws Exception {
        Timestamp ts = new Timestamp(System.currentTimeMillis());

        ts.setNanos(999999999);

        assertEquals(ts, marshalUnmarshal(ts));
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testTime() throws Exception {
        Time time = new Time(System.currentTimeMillis());
        assertEquals(time, marshalUnmarshal(time));
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testTimeArray() throws Exception {
        Time[] times = new Time[]{new Time(System.currentTimeMillis()), new Time(123456789)};
        assertArrayEquals(times, marshalUnmarshal(times));
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testByteArray() throws Exception {
        byte[] arr = new byte[] {10, 20, 30};

        assertArrayEquals(arr, marshalUnmarshal(arr));
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testShortArray() throws Exception {
        short[] arr = new short[] {10, 20, 30};

        assertArrayEquals(arr, marshalUnmarshal(arr));
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testIntArray() throws Exception {
        int[] arr = new int[] {10, 20, 30};

        assertArrayEquals(arr, marshalUnmarshal(arr));
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testLongArray() throws Exception {
        long[] arr = new long[] {10, 20, 30};

        assertArrayEquals(arr, marshalUnmarshal(arr));
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testFloatArray() throws Exception {
        float[] arr = new float[] {10.1f, 20.1f, 30.1f};

        assertArrayEquals(arr, marshalUnmarshal(arr), 0);
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testDoubleArray() throws Exception {
        double[] arr = new double[] {10.1d, 20.1d, 30.1d};

        assertArrayEquals(arr, marshalUnmarshal(arr), 0);
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testCharArray() throws Exception {
        char[] arr = new char[] {10, 20, 30};

        assertArrayEquals(arr, marshalUnmarshal(arr));
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testBooleanArray() throws Exception {
        boolean[] arr = new boolean[] {true, false, true};

        assertBooleanArrayEquals(arr, marshalUnmarshal(arr));
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testDecimalArray() throws Exception {
        BigDecimal[] arr = new BigDecimal[] {BigDecimal.ZERO, BigDecimal.ONE, BigDecimal.TEN};

        assertArrayEquals(arr, marshalUnmarshal(arr));
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testStringArray() throws Exception {
        String[] arr = new String[] {"str1", "str2", "str3"};

        assertArrayEquals(arr, marshalUnmarshal(arr));
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testUuidArray() throws Exception {
        UUID[] arr = new UUID[] {UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID()};

        assertArrayEquals(arr, marshalUnmarshal(arr));
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testDateArray() throws Exception {
        Date[] arr = new Date[] {new Date(11111), new Date(22222), new Date(33333)};

        assertArrayEquals(arr, marshalUnmarshal(arr));
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testObjectArray() throws Exception {
        Object[] arr = new Object[] {1, 2, 3};

        assertArrayEquals(arr, marshalUnmarshal(arr));
    }

    /** */
    @Test
    public void testBinaryArray() throws IgniteCheckedException {
        TestClass1[] arr = new TestClass1[] {new TestClass1(), new TestClass1()};

        assertArrayEquals(arr, marshalUnmarshal(arr));

        Object[] arr1 = new Object[] {arr, arr};

        Object[] arr2 = marshalUnmarshal(arr1);

        Assert.assertSame("Same array should be returned because of HANDLE usage", arr2[0], arr2[1]);

        assertArrayEquals(arr1, arr2);

        ArrayFieldClass o1 = new ArrayFieldClass();

        o1.arr1 = arr;
        o1.arr2 = arr;

        ArrayFieldClass o2 = marshalUnmarshal(o1);

        Assert.assertSame("Same array should be returned because of HANDLE usage", o2.arr1, o2.arr2);

        assertArrayEquals(o1.arr1, o2.arr1);
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testException() throws Exception {
        Exception ex = new RuntimeException();

        // Checks that Optimize marshaller will be used, because Throwable has writeObject method.
        // Exception's stacktrace equals to zero-length array by default and generates at Throwable's writeObject method.
        assertNotEquals(0, marshalUnmarshal(ex).getStackTrace().length);
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testCollection() throws Exception {
        testCollection(new ArrayList<Integer>(3));
        testCollection(new LinkedHashSet<Integer>());
        testCollection(new HashSet<Integer>());
        testCollection(new TreeSet<Integer>());
        testCollection(new ConcurrentSkipListSet<Integer>());
    }

    /**
     * @throws Exception If failed.
     */
    private void testCollection(Collection<Integer> col) throws Exception {
        col.add(1);
        col.add(2);
        col.add(3);

        assertEquals(col, marshalUnmarshal(col));
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testMap() throws Exception {
        testMap(new HashMap<Integer, String>());
        testMap(new LinkedHashMap<Integer, String>());
        testMap(new TreeMap<Integer, String>());
        testMap(new ConcurrentHashMap<Integer, String>());
        testMap(new ConcurrentHashMap<Integer, String>());
    }

    /**
     * @throws Exception If failed.
     */
    private void testMap(Map<Integer, String> map) throws Exception {
        map.put(1, "str1");
        map.put(2, "str2");
        map.put(3, "str3");

        assertEquals(map, marshalUnmarshal(map));
    }

    /**
     * Test serialization of custom collections.
     *
     * @throws Exception If failed.
     */
    @SuppressWarnings("unchecked")
    @Test
    public void testCustomCollections() throws Exception {
        CustomCollections cc = new CustomCollections();

        cc.list.add(1);
        cc.customList.add(new Value(1));

        CustomCollections copiedCc = marshalUnmarshal(cc);

        assert copiedCc.customList.getClass().equals(CustomArrayList.class);

        assertEquals(cc.list.size(), copiedCc.list.size());
        assertEquals(cc.customList.size(), copiedCc.customList.size());

        assertEquals(cc.list.get(0), copiedCc.list.get(0));
        assertEquals(cc.customList.get(0), copiedCc.customList.get(0));
    }

    /**
     * Test serialization of custom collections.
     *
     * @throws Exception If failed.
     */
    @SuppressWarnings("unchecked")
    @Test
    public void testCustomCollections2() throws Exception {
        CustomArrayList arrList = new CustomArrayList();

        arrList.add(1);

        Object cp = marshalUnmarshal(arrList);

        assert cp.getClass().equals(CustomArrayList.class);

        CustomArrayList customCp = (CustomArrayList)cp;

        assertEquals(customCp.size(), arrList.size());

        assertEquals(customCp.get(0), arrList.get(0));
    }

    /**
     * Test custom collections with factories.
     *
     * @throws Exception If failed.
     */
    @SuppressWarnings("unchecked")
    @Test
    public void testCustomCollectionsWithFactory() throws Exception {
        CustomCollectionsWithFactory cc = new CustomCollectionsWithFactory();

        cc.list.add(new DummyHolder(1));
        cc.map.put(new DummyHolder(2), new DummyHolder(3));

        CustomCollectionsWithFactory copiedCc = marshalUnmarshal(cc);

        assertEquals(cc.list.size(), copiedCc.list.size());
        assertEquals(cc.map.size(), copiedCc.map.size());

        assertEquals(cc.list.get(0), copiedCc.list.get(0));
        assertEquals(cc.map.get(new DummyHolder(2)), copiedCc.map.get(new DummyHolder(2)));
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testExternalizableInEnclosing() throws Exception {
        SimpleEnclosingObject obj = new SimpleEnclosingObject();
        obj.simpl = new SimpleExternalizable("field");

        SimpleEnclosingObject other = marshalUnmarshal(obj);

        assertEquals(((SimpleExternalizable)obj.simpl).field, ((SimpleExternalizable)other.simpl).field);
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testMapEntry() throws Exception {
        Map.Entry<Integer, String> e = new GridMapEntry<>(1, "str1");

        assertEquals(e, marshalUnmarshal(e));

        Map<Integer, String> map = new HashMap<>(1);

        map.put(2, "str2");

        e = F.firstEntry(map);

        Map.Entry<Integer, String> e0 = marshalUnmarshal(e);

        assertEquals(2, e0.getKey().intValue());
        assertEquals("str2", e0.getValue());
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testBinaryObject() throws Exception {
        BinaryMarshaller marsh = binaryMarshaller(Arrays.asList(new BinaryTypeConfiguration(SimpleObject.class.getName())));

        SimpleObject obj = simpleObject();

        BinaryObject po = marshal(obj, marsh);

        BinaryObject po0 = marshalUnmarshal(po, marsh);

        assertTrue(po.hasField("b"));
        assertTrue(po.hasField("s"));
        assertTrue(po.hasField("i"));
        assertTrue(po.hasField("l"));
        assertTrue(po.hasField("f"));
        assertTrue(po.hasField("d"));
        assertTrue(po.hasField("c"));
        assertTrue(po.hasField("bool"));

        assertFalse(po.hasField("no_such_field"));

        assertEquals(obj, po.deserialize());
        assertEquals(obj, po0.deserialize());
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testEnum() throws Exception {
        BinaryMarshaller marsh = binaryMarshaller(Arrays.asList(new BinaryTypeConfiguration(TestEnum.class.getName())));

        assertEquals(TestEnum.B, marshalUnmarshal(TestEnum.B, marsh));
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testDeclaredBodyEnum() throws Exception {
        final MarshallerContextTestImpl ctx = new MarshallerContextTestImpl();
        ctx.registerClassName((byte)0, 1, EnumObject.class.getName(), false);
        ctx.registerClassName((byte)0, 2, DeclaredBodyEnum.class.getName(), false);

        BinaryMarshaller marsh = binaryMarshaller();
        marsh.setContext(ctx);

        EnumObject obj = new EnumObject(1L, "test 1", DeclaredBodyEnum.TWO);

        final byte[] marshal = marsh.marshal(obj);
        final Object restored = marsh.unmarshal(marshal, null);

        assertTrue(restored instanceof EnumObject);

        obj = (EnumObject)restored;

        assertEquals(1, obj.id);
        assertEquals(DeclaredBodyEnum.TWO.ordinal(), obj.type.ordinal());
        assertEquals(DeclaredBodyEnum.TWO, obj.type);
        assertTrue(obj.type == DeclaredBodyEnum.TWO);
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testDateAndTimestampInSingleObject() throws Exception {
        BinaryTypeConfiguration cfg1 = new BinaryTypeConfiguration(DateClass1.class.getName());

        BinaryMarshaller marsh = binaryMarshaller(Arrays.asList(cfg1));

        Date date = new Date();
        Timestamp ts = new Timestamp(System.currentTimeMillis());
        Time time = new Time(System.currentTimeMillis());
        Time[] timeArr = new Time[]{time, new Time(date.getTime()), new Time(System.currentTimeMillis())};

        DateClass1 obj1 = new DateClass1();
        obj1.date = date;
        obj1.ts = ts;
        obj1.time = time;
        obj1.timeArr = timeArr;

        BinaryObject po1 = marshal(obj1, marsh);

        assertEquals(date, po1.field("date"));
        assertEquals(Date.class, po1.field("date").getClass());
        assertEquals(ts, po1.field("ts"));
        assertEquals(Timestamp.class, po1.field("ts").getClass());
        assertEquals(time, po1.field("time"));
        assertEquals(Time.class, po1.field("time").getClass());
        assertArrayEquals(timeArr, (Object[])po1.field("timeArr"));
        assertEquals(Time[].class, po1.field("timeArr").getClass());

        obj1 = po1.deserialize();
        assertEquals(date, obj1.date);
        assertEquals(ts, obj1.ts);
        assertEquals(time, obj1.time);
        assertArrayEquals(timeArr, obj1.timeArr);
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testSimpleObject() throws Exception {
        BinaryMarshaller marsh = binaryMarshaller(Arrays.asList(
            new BinaryTypeConfiguration(SimpleObject.class.getName())
        ));

        SimpleObject obj = simpleObject();

        BinaryObject po = marshal(obj, marsh);

        assertEquals(obj, po.deserialize());

        assertEquals(obj.b, (byte)po.field("b"));
        assertEquals(obj.s, (short)po.field("s"));
        assertEquals(obj.i, (int)po.field("i"));
        assertEquals(obj.l, (long)po.field("l"));
        assertEquals(obj.f, (float)po.field("f"), 0);
        assertEquals(obj.d, (double)po.field("d"), 0);
        assertEquals(obj.c, (char)po.field("c"));
        assertEquals(obj.bool, (boolean)po.field("bool"));
        assertEquals(obj.str, po.field("str"));
        assertEquals(obj.uuid, po.field("uuid"));
        assertEquals(obj.date, po.field("date"));
        assertEquals(Date.class, obj.date.getClass());
        assertEquals(obj.ts, po.field("ts"));
        assertArrayEquals(obj.bArr, (byte[])po.field("bArr"));
        assertArrayEquals(obj.sArr, (short[])po.field("sArr"));
        assertArrayEquals(obj.iArr, (int[])po.field("iArr"));
        assertArrayEquals(obj.lArr, (long[])po.field("lArr"));
        assertArrayEquals(obj.fArr, (float[])po.field("fArr"), 0);
        assertArrayEquals(obj.dArr, (double[])po.field("dArr"), 0);
        assertArrayEquals(obj.cArr, (char[])po.field("cArr"));
        assertBooleanArrayEquals(obj.boolArr, (boolean[])po.field("boolArr"));
        assertArrayEquals(obj.strArr, (String[])po.field("strArr"));
        assertArrayEquals(obj.uuidArr, (UUID[])po.field("uuidArr"));
        assertArrayEquals(obj.dateArr, (Date[])po.field("dateArr"));
        assertArrayEquals(
            obj.objArr,
            useBinaryArrays ? po.<BinaryArray>field("objArr").array() : po.field("objArr")
        );
        assertEquals(obj.col, po.field("col"));
        assertEquals(obj.map, po.field("map"));
        assertEquals(new Integer(obj.enumVal.ordinal()), new Integer(((BinaryObject)po.field("enumVal")).enumOrdinal()));
        assertArrayEquals(ordinals(obj.enumArr), binaryOrdinals(po.field("enumArr")));
        assertNull(po.field("unknown"));

        BinaryObject innerPo = po.field("inner");

        assertEquals(obj.inner, innerPo.deserialize());

        assertEquals(obj.inner.b, (byte)innerPo.field("b"));
        assertEquals(obj.inner.s, (short)innerPo.field("s"));
        assertEquals(obj.inner.i, (int)innerPo.field("i"));
        assertEquals(obj.inner.l, (long)innerPo.field("l"));
        assertEquals(obj.inner.f, (float)innerPo.field("f"), 0);
        assertEquals(obj.inner.d, (double)innerPo.field("d"), 0);
        assertEquals(obj.inner.c, (char)innerPo.field("c"));
        assertEquals(obj.inner.bool, (boolean)innerPo.field("bool"));
        assertEquals(obj.inner.str, innerPo.field("str"));
        assertEquals(obj.inner.uuid, innerPo.field("uuid"));
        assertEquals(obj.inner.date, innerPo.field("date"));
        assertEquals(Date.class, obj.inner.date.getClass());
        assertEquals(obj.inner.ts, innerPo.field("ts"));
        assertArrayEquals(obj.inner.bArr, (byte[])innerPo.field("bArr"));
        assertArrayEquals(obj.inner.sArr, (short[])innerPo.field("sArr"));
        assertArrayEquals(obj.inner.iArr, (int[])innerPo.field("iArr"));
        assertArrayEquals(obj.inner.lArr, (long[])innerPo.field("lArr"));
        assertArrayEquals(obj.inner.fArr, (float[])innerPo.field("fArr"), 0);
        assertArrayEquals(obj.inner.dArr, (double[])innerPo.field("dArr"), 0);
        assertArrayEquals(obj.inner.cArr, (char[])innerPo.field("cArr"));
        assertBooleanArrayEquals(obj.inner.boolArr, (boolean[])innerPo.field("boolArr"));
        assertArrayEquals(obj.inner.strArr, (String[])innerPo.field("strArr"));
        assertArrayEquals(obj.inner.uuidArr, (UUID[])innerPo.field("uuidArr"));
        assertArrayEquals(obj.inner.dateArr, (Date[])innerPo.field("dateArr"));
        assertArrayEquals(
            obj.inner.objArr,
            useBinaryArrays ? innerPo.<BinaryArray>field("objArr").array() : innerPo.field("objArr")
        );
        assertEquals(obj.inner.col, innerPo.field("col"));
        assertEquals(obj.inner.map, innerPo.field("map"));
        assertEquals(new Integer(obj.inner.enumVal.ordinal()),
            new Integer(((BinaryObject)innerPo.field("enumVal")).enumOrdinal()));
        assertArrayEquals(ordinals(obj.inner.enumArr), binaryOrdinals(innerPo.field("enumArr")));
        assertNull(innerPo.field("inner"));
        assertNull(innerPo.field("unknown"));
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testBinary() throws Exception {
        BinaryMarshaller marsh = binaryMarshaller(Arrays.asList(
            new BinaryTypeConfiguration(SimpleObject.class.getName()),
            new BinaryTypeConfiguration(TestBinary.class.getName())
        ));

        TestBinary obj = binaryObject();

        BinaryObject po = marshal(obj, marsh);

        assertEquals(obj, po.deserialize());

        assertEquals(obj.b, (byte)po.field("_b"));
        assertEquals(obj.s, (short)po.field("_s"));
        assertEquals(obj.i, (int)po.field("_i"));
        assertEquals(obj.l, (long)po.field("_l"));
        assertEquals(obj.f, (float)po.field("_f"), 0);
        assertEquals(obj.d, (double)po.field("_d"), 0);
        assertEquals(obj.c, (char)po.field("_c"));
        assertEquals(obj.bool, (boolean)po.field("_bool"));
        assertEquals(obj.str, po.field("_str"));
        assertEquals(obj.uuid, po.field("_uuid"));
        assertEquals(obj.date, po.field("_date"));
        assertEquals(obj.ts, po.field("_ts"));
        assertArrayEquals(obj.bArr, (byte[])po.field("_bArr"));
        assertArrayEquals(obj.sArr, (short[])po.field("_sArr"));
        assertArrayEquals(obj.iArr, (int[])po.field("_iArr"));
        assertArrayEquals(obj.lArr, (long[])po.field("_lArr"));
        assertArrayEquals(obj.fArr, (float[])po.field("_fArr"), 0);
        assertArrayEquals(obj.dArr, (double[])po.field("_dArr"), 0);
        assertArrayEquals(obj.cArr, (char[])po.field("_cArr"));
        assertBooleanArrayEquals(obj.boolArr, (boolean[])po.field("_boolArr"));
        assertArrayEquals(obj.strArr, (String[])po.field("_strArr"));
        assertArrayEquals(obj.uuidArr, (UUID[])po.field("_uuidArr"));
        assertArrayEquals(obj.dateArr, (Date[])po.field("_dateArr"));
        assertArrayEquals(
            obj.objArr,
            useBinaryArrays ? po.<BinaryArray>field("_objArr").array() : po.field("_objArr")
        );
        assertEquals(obj.col, po.field("_col"));
        assertEquals(obj.map, po.field("_map"));
        assertEquals(new Integer(obj.enumVal.ordinal()), new Integer(((BinaryObject)po.field("_enumVal")).enumOrdinal()));
        assertArrayEquals(ordinals(obj.enumArr), binaryOrdinals(po.field("_enumArr")));
        assertNull(po.field("unknown"));

        BinaryObject simplePo = po.field("_simple");

        assertEquals(obj.simple, simplePo.deserialize());

        assertEquals(obj.simple.b, (byte)simplePo.field("b"));
        assertEquals(obj.simple.s, (short)simplePo.field("s"));
        assertEquals(obj.simple.i, (int)simplePo.field("i"));
        assertEquals(obj.simple.l, (long)simplePo.field("l"));
        assertEquals(obj.simple.f, (float)simplePo.field("f"), 0);
        assertEquals(obj.simple.d, (double)simplePo.field("d"), 0);
        assertEquals(obj.simple.c, (char)simplePo.field("c"));
        assertEquals(obj.simple.bool, (boolean)simplePo.field("bool"));
        assertEquals(obj.simple.str, simplePo.field("str"));
        assertEquals(obj.simple.uuid, simplePo.field("uuid"));
        assertEquals(obj.simple.date, simplePo.field("date"));
        assertEquals(Date.class, obj.simple.date.getClass());
        assertEquals(obj.simple.ts, simplePo.field("ts"));
        assertArrayEquals(obj.simple.bArr, (byte[])simplePo.field("bArr"));
        assertArrayEquals(obj.simple.sArr, (short[])simplePo.field("sArr"));
        assertArrayEquals(obj.simple.iArr, (int[])simplePo.field("iArr"));
        assertArrayEquals(obj.simple.lArr, (long[])simplePo.field("lArr"));
        assertArrayEquals(obj.simple.fArr, (float[])simplePo.field("fArr"), 0);
        assertArrayEquals(obj.simple.dArr, (double[])simplePo.field("dArr"), 0);
        assertArrayEquals(obj.simple.cArr, (char[])simplePo.field("cArr"));
        assertBooleanArrayEquals(obj.simple.boolArr, (boolean[])simplePo.field("boolArr"));
        assertArrayEquals(obj.simple.strArr, (String[])simplePo.field("strArr"));
        assertArrayEquals(obj.simple.uuidArr, (UUID[])simplePo.field("uuidArr"));
        assertArrayEquals(obj.simple.dateArr, (Date[])simplePo.field("dateArr"));
        assertArrayEquals(
            obj.simple.objArr,
            useBinaryArrays ? simplePo.<BinaryArray>field("objArr").array() : simplePo.field("objArr")
        );
        assertEquals(obj.simple.col, simplePo.field("col"));
        assertEquals(obj.simple.map, simplePo.field("map"));
        assertEquals(new Integer(obj.simple.enumVal.ordinal()),
            new Integer(((BinaryObject)simplePo.field("enumVal")).enumOrdinal()));
        assertArrayEquals(ordinals(obj.simple.enumArr), binaryOrdinals(simplePo.field("enumArr")));
        assertNull(simplePo.field("simple"));
        assertNull(simplePo.field("binary"));
        assertNull(simplePo.field("unknown"));

        BinaryObject binaryPo = po.field("_binary");

        assertEquals(obj.binary, binaryPo.deserialize());

        assertEquals(obj.binary.b, (byte)binaryPo.field("_b"));
        assertEquals(obj.binary.s, (short)binaryPo.field("_s"));
        assertEquals(obj.binary.i, (int)binaryPo.field("_i"));
        assertEquals(obj.binary.l, (long)binaryPo.field("_l"));
        assertEquals(obj.binary.f, (float)binaryPo.field("_f"), 0);
        assertEquals(obj.binary.d, (double)binaryPo.field("_d"), 0);
        assertEquals(obj.binary.c, (char)binaryPo.field("_c"));
        assertEquals(obj.binary.bool, (boolean)binaryPo.field("_bool"));
        assertEquals(obj.binary.str, binaryPo.field("_str"));
        assertEquals(obj.binary.uuid, binaryPo.field("_uuid"));
        assertEquals(obj.binary.date, binaryPo.field("_date"));
        assertEquals(obj.binary.ts, binaryPo.field("_ts"));
        assertArrayEquals(obj.binary.bArr, (byte[])binaryPo.field("_bArr"));
        assertArrayEquals(obj.binary.sArr, (short[])binaryPo.field("_sArr"));
        assertArrayEquals(obj.binary.iArr, (int[])binaryPo.field("_iArr"));
        assertArrayEquals(obj.binary.lArr, (long[])binaryPo.field("_lArr"));
        assertArrayEquals(obj.binary.fArr, (float[])binaryPo.field("_fArr"), 0);
        assertArrayEquals(obj.binary.dArr, (double[])binaryPo.field("_dArr"), 0);
        assertArrayEquals(obj.binary.cArr, (char[])binaryPo.field("_cArr"));
        assertBooleanArrayEquals(obj.binary.boolArr, (boolean[])binaryPo.field("_boolArr"));
        assertArrayEquals(obj.binary.strArr, (String[])binaryPo.field("_strArr"));
        assertArrayEquals(obj.binary.uuidArr, (UUID[])binaryPo.field("_uuidArr"));
        assertArrayEquals(obj.binary.dateArr, (Date[])binaryPo.field("_dateArr"));
        assertArrayEquals(
            obj.binary.objArr,
            useBinaryArrays ? binaryPo.<BinaryArray>field("_objArr").array() : binaryPo.field("_objArr")
        );
        assertEquals(obj.binary.col, binaryPo.field("_col"));
        assertEquals(obj.binary.map, binaryPo.field("_map"));
        assertEquals(new Integer(obj.binary.enumVal.ordinal()),
            new Integer(((BinaryObject)binaryPo.field("_enumVal")).enumOrdinal()));
        assertArrayEquals(ordinals(obj.binary.enumArr), binaryOrdinals(binaryPo.field("_enumArr")));
        assertNull(binaryPo.field("_simple"));
        assertNull(binaryPo.field("_binary"));
        assertNull(binaryPo.field("unknown"));
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testObjectFieldOfExternalizableCollection() throws Exception {
        EnclosingObj obj = new EnclosingObj();

        obj.queue = new TestQueue("test");

        assertEquals(obj, marshalUnmarshal(obj));
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testVoid() throws Exception {
        Class clazz = Void.class;

        assertEquals(clazz, marshalUnmarshal(clazz));

        clazz = Void.TYPE;

        assertEquals(clazz, marshalUnmarshal(clazz));
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testWriteReplacePrivate() throws Exception {
        BinaryMarshaller marsh = binaryMarshaller(Collections.singleton(
            new BinaryTypeConfiguration(TestObject.class.getName())
        ));

        TestObject obj = new TestObject();

        BinaryObject po = marshal(obj, marsh);

        assertEquals(obj, po.deserialize());

        assertEquals(obj.val, ((BinaryObject)po.field("val")).deserialize());
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testWriteReplaceInheritable() throws Exception {
        ImmutableList<String> obj = ImmutableList.of("This is a test");

        BinaryMarshaller marsh = binaryMarshaller(Collections.singleton(
            new BinaryTypeConfiguration(obj.getClass().getName())
        ));

        BinaryObject po = marshal(obj, marsh);

        Object des = po.deserialize();

        assertEquals(obj, des);
    }

    /**
     * Checks {@link BinaryBuilderReader#parseValue()} for object that contains handles to collection.
     *
     * @throws Exception If failed.
     */
    @Test
    public void handleToCollection() throws Exception {
        final IgnitePair<String>[] fieldsColAndHandle = new IgnitePair[] {
            new IgnitePair<>("lst", "hndLst"),
            new IgnitePair<>("linkedLst", "hndLinkedLst"),
            new IgnitePair<>("map", "hndMap"),
            new IgnitePair<>("linkedMap", "hndLinkedMap")
        };
        BinaryMarshaller m = binaryMarshaller();

        HandleToCollections obj = new HandleToCollections();

        BinaryObject bo = marshal(obj, m);

        for (int i = 0; i < 10; ++i) {
            BinaryObjectBuilder bob = bo.toBuilder();

            if (i > 0)
                assertEquals(i - 1, (int)bo.field("a"));

            bob.setField("a", i);

            for (IgnitePair<String> flds : fieldsColAndHandle) {
                // Different orders to read collection and handle to collection.
                Object col;
                Object colHnd;

                if (i % 2 == 0) {
                    col = bob.getField(flds.get1());
                    colHnd = bob.getField(flds.get2());
                }
                else {
                    colHnd = bob.getField(flds.get2());
                    col = bob.getField(flds.get1());
                }

                // Must be assertSame but now BinaryObjectBuilder doesn't support handle to collection.
                // Now we check only that BinaryObjectBuilder#getField doesn't crash and returns valid collection.
                assertEquals("Check: " + flds, col, colHnd);
            }

            bo = bob.build();
        }
    }

    /** */
    @Test
    public void rebuildWithHandleToCollection() throws Exception {
        BinaryMarshaller m = binaryMarshaller();

        HandleToCollections obj = new HandleToCollections();

        BinaryObjectImpl bo = marshal(obj, m);

        for (int i = 0; i < 10; ++i) {
            BinaryObjectBuilder bob = bo.toBuilder();

            bob.setField("a", i);

            bo = (BinaryObjectImpl)bob.build();

            // Check unmarshal is OK.
            HandleToCollections modified = unmarshal(bo, m);

            assertEquals(i, modified.a);
            assertEquals(obj.lst, modified.lst);
            assertEquals(obj.hndLst, modified.hndLst);
            assertEquals(obj.linkedLst, modified.linkedLst);
            assertEquals(obj.hndLinkedLst, modified.hndLinkedLst);
            assertEquals(obj.map, modified.map);
            assertEquals(obj.hndMap, modified.hndMap);
        }
    }

    /** */
    @Test
    public void rebuildObjectWithCollections() throws Exception {
        BinaryMarshaller m = binaryMarshaller();

        TwoCollections obj = new TwoCollections();

        BinaryObjectImpl bo = marshal(obj, m);

        for (int i = 0; i < 10; ++i) {
            BinaryObjectBuilder bob = bo.toBuilder();

            bob.setField("a", i);

            bo = (BinaryObjectImpl)bob.build();

            // Check unmarshal is OK.
            TwoCollections modified = unmarshal(bo, m);

            assertEquals(i, modified.a);
            assertEquals(obj.lst0, modified.lst0);
            assertEquals(obj.lst1, modified.lst1);
        }
    }

    /** */
    @Test
    public void emptyObjectBuilder() throws IgniteCheckedException {
        BinaryObject emptyBinObj = builder("test_type").build();

        BinaryObjectBuilder bob2 = emptyBinObj.toBuilder();

        // Check any field is null at the empty object.
        assertNull(bob2.getField("a"));

        // Modify empty object: add field.
        BinaryObjectImpl o1 = (BinaryObjectImpl)bob2.setField("a", 1).build();
        BinaryObjectImpl o2 = (BinaryObjectImpl)builder("test_type").setField("a", 1).build();

        // Check that modified empty object and the new object with the sanme field are equals.
        assertEquals(o1.schemaId(), o2.schemaId());
        assertEquals(o1, o2);
    }

    /** */
    @Test
    public void emptyObjectBinarylizable() throws IgniteCheckedException {
        BinaryMarshaller m = binaryMarshaller();

        BinaryObjectBuilder bob = marshal(new ObjectRaw(), m).toBuilder();

        // Check any field is null at the empty object.
        assertNull(bob.getField("a"));
    }

    /**
     *
     */
    private static class EnclosingObj implements Serializable {
        /** Queue. */
        Queue<Integer> queue = new TestQueue("test");

        /** {@inheritDoc} */
        @Override public boolean equals(Object o) {
            if (this == o)
                return true;

            if (o == null || getClass() != o.getClass())
                return false;

            EnclosingObj obj = (EnclosingObj)o;

            return Objects.equals(queue, obj.queue);
        }

        /** {@inheritDoc} */
        @Override public int hashCode() {
            return Objects.hash(queue);
        }
    }

    /**
     *
     */
    private static class TestQueue extends AbstractQueue<Integer> implements Externalizable {
        /** Name. */
        private String name;

        /**
         * {@link Externalizable} support.
         */
        public TestQueue() {
            // No-op.
        }

        /**
         * @param name Name.
         */
        public TestQueue(String name) {
            this.name = name;
        }

        /** {@inheritDoc} */
        @NotNull @Override public Iterator<Integer> iterator() {
            return Collections.emptyIterator();
        }

        /** {@inheritDoc} */
        @Override public int size() {
            return 0;
        }

        /** {@inheritDoc} */
        @Override public void writeExternal(ObjectOutput out) throws IOException {
            out.writeObject(name);
        }

        /** {@inheritDoc} */
        @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
            name = (String)in.readObject();
        }

        /** {@inheritDoc} */
        @Override public boolean offer(Integer integer) {
            throw new UnsupportedOperationException();
        }

        /** {@inheritDoc} */
        @Override public Integer poll() {
            throw new UnsupportedOperationException();
        }

        /** {@inheritDoc} */
        @Override public Integer peek() {
            throw new UnsupportedOperationException();
        }

        /** {@inheritDoc} */
        @Override public boolean equals(Object o) {
            if (this == o)
                return true;

            if (o == null || getClass() != o.getClass())
                return false;

            TestQueue integers = (TestQueue)o;

            return Objects.equals(name, integers.name);
        }

        /** {@inheritDoc} */
        @Override public int hashCode() {
            return Objects.hash(name);
        }
    }

    /**
     * @param obj Simple object.
     * @param po Binary object.
     */
    private void checkSimpleObjectData(SimpleObject obj, BinaryObject po) {
        assertEquals(obj.b, (byte)po.field("b"));
        assertEquals(obj.s, (short)po.field("s"));
        assertEquals(obj.i, (int)po.field("i"));
        assertEquals(obj.l, (long)po.field("l"));
        assertEquals(obj.f, (float)po.field("f"), 0);
        assertEquals(obj.d, (double)po.field("d"), 0);
        assertEquals(obj.c, (char)po.field("c"));
        assertEquals(obj.bool, (boolean)po.field("bool"));
        assertEquals(obj.str, po.field("str"));
        assertEquals(obj.uuid, po.field("uuid"));
        assertEquals(obj.date, po.field("date"));
        assertEquals(Date.class, obj.date.getClass());
        assertEquals(obj.ts, po.field("ts"));
        assertArrayEquals(obj.bArr, (byte[])po.field("bArr"));
        assertArrayEquals(obj.sArr, (short[])po.field("sArr"));
        assertArrayEquals(obj.iArr, (int[])po.field("iArr"));
        assertArrayEquals(obj.lArr, (long[])po.field("lArr"));
        assertArrayEquals(obj.fArr, (float[])po.field("fArr"), 0);
        assertArrayEquals(obj.dArr, (double[])po.field("dArr"), 0);
        assertArrayEquals(obj.cArr, (char[])po.field("cArr"));
        assertBooleanArrayEquals(obj.boolArr, (boolean[])po.field("boolArr"));
        assertArrayEquals(obj.strArr, (String[])po.field("strArr"));
        assertArrayEquals(obj.uuidArr, (UUID[])po.field("uuidArr"));
        assertArrayEquals(obj.dateArr, (Date[])po.field("dateArr"));
        assertArrayEquals(
            obj.objArr,
            useBinaryArrays ? po.<BinaryArray>field("objArr").array() : po.field("objArr")
        );
        assertEquals(obj.col, po.field("col"));
        assertEquals(obj.map, po.field("map"));
        assertEquals(new Integer(obj.enumVal.ordinal()), new Integer(((BinaryObject)po.field("enumVal")).enumOrdinal()));
        assertArrayEquals(ordinals(obj.enumArr), binaryOrdinals(po.field("enumArr")));
        assertNull(po.field("unknown"));

        assertEquals(obj, po.deserialize());
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testClassWithoutPublicConstructor() throws Exception {
        BinaryMarshaller marsh = binaryMarshaller(Arrays.asList(
            new BinaryTypeConfiguration(NoPublicConstructor.class.getName()),
            new BinaryTypeConfiguration(NoPublicDefaultConstructor.class.getName()),
            new BinaryTypeConfiguration(ProtectedConstructor.class.getName()))
        );

        NoPublicConstructor npc = new NoPublicConstructor();
        BinaryObject npc2 = marshal(npc, marsh);

        assertEquals("test", npc2.<NoPublicConstructor>deserialize().val);

        NoPublicDefaultConstructor npdc = new NoPublicDefaultConstructor(239);
        BinaryObject npdc2 = marshal(npdc, marsh);

        assertEquals(239, npdc2.<NoPublicDefaultConstructor>deserialize().val);

        ProtectedConstructor pc = new ProtectedConstructor();
        BinaryObject pc2 = marshal(pc, marsh);

        assertEquals(ProtectedConstructor.class, pc2.<ProtectedConstructor>deserialize().getClass());
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testCustomSerializer() throws Exception {
        BinaryTypeConfiguration type =
            new BinaryTypeConfiguration(CustomSerializedObject1.class.getName());

        type.setSerializer(new CustomSerializer1());

        BinaryMarshaller marsh = binaryMarshaller(Arrays.asList(type));

        CustomSerializedObject1 obj1 = new CustomSerializedObject1(10);

        BinaryObject po1 = marshal(obj1, marsh);

        assertEquals(20, po1.<CustomSerializedObject1>deserialize().val);
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testCustomSerializerWithGlobal() throws Exception {
        BinaryTypeConfiguration type1 =
            new BinaryTypeConfiguration(CustomSerializedObject1.class.getName());
        BinaryTypeConfiguration type2 =
            new BinaryTypeConfiguration(CustomSerializedObject2.class.getName());

        type2.setSerializer(new CustomSerializer2());

        BinaryMarshaller marsh = binaryMarshaller(new CustomSerializer1(), Arrays.asList(type1, type2));

        CustomSerializedObject1 obj1 = new CustomSerializedObject1(10);

        BinaryObject po1 = marshal(obj1, marsh);

        assertEquals(20, po1.<CustomSerializedObject1>deserialize().val);

        CustomSerializedObject2 obj2 = new CustomSerializedObject2(10);

        BinaryObject po2 = marshal(obj2, marsh);

        assertEquals(30, po2.<CustomSerializedObject2>deserialize().val);
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testCustomIdMapper() throws Exception {
        BinaryTypeConfiguration type =
            new BinaryTypeConfiguration(CustomMappedObject1.class.getName());

        type.setIdMapper(new BinaryIdMapper() {
            @Override public int typeId(String clsName) {
                return 11111;
            }

            @Override public int fieldId(int typeId, String fieldName) {
                assert typeId == 11111;

                if ("val1".equals(fieldName))
                    return 22222;
                else if ("val2".equals(fieldName))
                    return 33333;

                assert false : "Unknown field: " + fieldName;

                return 0;
            }
        });

        BinaryMarshaller marsh = binaryMarshaller(Arrays.asList(type));

        CustomMappedObject1 obj1 = new CustomMappedObject1(10, "str");

        BinaryObjectExImpl po1 = marshal(obj1, marsh);

        assertEquals(11111, po1.type().typeId());
        assertEquals((Integer)10, po1.field(22222));
        assertEquals("str", po1.field(33333));

        assertEquals(10, po1.<CustomMappedObject1>deserialize().val1);
        assertEquals("str", po1.<CustomMappedObject1>deserialize().val2);
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testCustomIdMapperWithGlobal() throws Exception {
        BinaryTypeConfiguration type1 =
            new BinaryTypeConfiguration(CustomMappedObject1.class.getName());
        BinaryTypeConfiguration type2 =
            new BinaryTypeConfiguration(CustomMappedObject2.class.getName());

        type2.setIdMapper(new BinaryIdMapper() {
            @Override public int typeId(String clsName) {
                return 44444;
            }

            @Override public int fieldId(int typeId, String fieldName) {
                assert typeId == 44444;

                if ("val1".equals(fieldName))
                    return 55555;
                else if ("val2".equals(fieldName))
                    return 66666;

                assert false : "Unknown field: " + fieldName;

                return 0;
            }
        });

        BinaryMarshaller marsh = binaryMarshaller(null, new BinaryIdMapper() {
            @Override public int typeId(String clsName) {
                return 11111;
            }

            @Override public int fieldId(int typeId, String fieldName) {
                assert typeId == 11111;

                if ("val1".equals(fieldName))
                    return 22222;
                else if ("val2".equals(fieldName))
                    return 33333;

                assert false : "Unknown field: " + fieldName;

                return 0;
            }
        }, Arrays.asList(type1, type2));

        CustomMappedObject1 obj1 = new CustomMappedObject1(10, "str1");

        BinaryObjectExImpl po1 = marshal(obj1, marsh);

        assertEquals(11111, po1.type().typeId());
        assertEquals((Integer)10, po1.field(22222));
        assertEquals("str1", po1.field(33333));

        assertEquals(10, po1.<CustomMappedObject1>deserialize().val1);
        assertEquals("str1", po1.<CustomMappedObject1>deserialize().val2);

        CustomMappedObject2 obj2 = new CustomMappedObject2(20, "str2");

        BinaryObjectExImpl po2 = marshal(obj2, marsh);

        assertEquals(44444, po2.type().typeId());
        assertEquals((Integer)20, po2.field(55555));
        assertEquals("str2", po2.field(66666));

        assertEquals(20, po2.<CustomMappedObject2>deserialize().val1);
        assertEquals("str2", po2.<CustomMappedObject2>deserialize().val2);
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testSimpleNameLowerCaseMappers() throws Exception {
        BinaryTypeConfiguration innerClassType = new BinaryTypeConfiguration(InnerMappedObject.class.getName());
        BinaryTypeConfiguration publicClassType = new BinaryTypeConfiguration(TestMappedObject.class.getName());
        BinaryTypeConfiguration typeWithCustomMapper = new BinaryTypeConfiguration(CustomMappedObject2.class.getName());

        typeWithCustomMapper.setIdMapper(new BinaryIdMapper() {
            @Override public int typeId(String clsName) {
                return 44444;
            }

            @Override public int fieldId(int typeId, String fieldName) {
                assert typeId == 44444;

                if ("val1".equals(fieldName))
                    return 55555;
                else if ("val2".equals(fieldName))
                    return 66666;

                assert false : "Unknown field: " + fieldName;

                return 0;
            }
        });

        BinaryMarshaller marsh = binaryMarshaller(new BinaryBasicNameMapper(true), new BinaryBasicIdMapper(true),
            Arrays.asList(innerClassType, publicClassType, typeWithCustomMapper));

        InnerMappedObject innerObj = new InnerMappedObject(10, "str1");

        BinaryObjectExImpl innerBo = marshal(innerObj, marsh);

        assertEquals("InnerMappedObject".toLowerCase().hashCode(), innerBo.type().typeId());

        assertEquals(10, innerBo.<CustomMappedObject1>deserialize().val1);
        assertEquals("str1", innerBo.<CustomMappedObject1>deserialize().val2);

        TestMappedObject publicObj = new TestMappedObject();

        BinaryObjectExImpl publicBo = marshal(publicObj, marsh);

        assertEquals("TestMappedObject".toLowerCase().hashCode(), publicBo.type().typeId());

        CustomMappedObject2 obj2 = new CustomMappedObject2(20, "str2");

        BinaryObjectExImpl po2 = marshal(obj2, marsh);

        assertEquals(44444, po2.type().typeId());
        assertEquals((Integer)20, po2.field(55555));
        assertEquals("str2", po2.field(66666));

        assertEquals(20, po2.<CustomMappedObject2>deserialize().val1);
        assertEquals("str2", po2.<CustomMappedObject2>deserialize().val2);
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testDynamicObject() throws Exception {
        BinaryMarshaller marsh = binaryMarshaller(Arrays.asList(
            new BinaryTypeConfiguration(DynamicObject.class.getName())
        ));

        BinaryObject po1 = marshal(new DynamicObject(0, 10, 20, 30), marsh);

        assertEquals(new Integer(10), po1.field("val1"));
        assertEquals(null, po1.field("val2"));
        assertEquals(null, po1.field("val3"));

        DynamicObject do1 = po1.deserialize();

        assertEquals(10, do1.val1);
        assertEquals(0, do1.val2);
        assertEquals(0, do1.val3);

        BinaryObject po2 = marshal(new DynamicObject(1, 10, 20, 30), marsh);

        assertEquals(new Integer(10), po2.field("val1"));
        assertEquals(new Integer(20), po2.field("val2"));
        assertEquals(null, po2.field("val3"));

        DynamicObject do2 = po2.deserialize();

        assertEquals(10, do2.val1);
        assertEquals(20, do2.val2);
        assertEquals(0, do2.val3);

        BinaryObject po3 = marshal(new DynamicObject(2, 10, 20, 30), marsh);

        assertEquals(new Integer(10), po3.field("val1"));
        assertEquals(new Integer(20), po3.field("val2"));
        assertEquals(new Integer(30), po3.field("val3"));

        DynamicObject do3 = po3.deserialize();

        assertEquals(10, do3.val1);
        assertEquals(20, do3.val2);
        assertEquals(30, do3.val3);
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testCycleLink() throws Exception {
        BinaryMarshaller marsh = binaryMarshaller(Arrays.asList(
            new BinaryTypeConfiguration(CycleLinkObject.class.getName())
        ));

        CycleLinkObject obj = new CycleLinkObject();

        obj.self = obj;

        BinaryObject po = marshal(obj, marsh);

        CycleLinkObject obj0 = po.deserialize();

        assert obj0.self == obj0;
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testDetached() throws Exception {
        BinaryMarshaller marsh = binaryMarshaller(Arrays.asList(
            new BinaryTypeConfiguration(DetachedTestObject.class.getName()),
            new BinaryTypeConfiguration(DetachedInnerTestObject.class.getName())
        ));

        UUID id = UUID.randomUUID();

        DetachedTestObject obj = marshal(new DetachedTestObject(
            new DetachedInnerTestObject(null, id)), marsh).deserialize();

        assertEquals(id, obj.inner1.id);
        assertEquals(id, obj.inner4.id);

        assert obj.inner1 == obj.inner4;

        BinaryObjectImpl innerPo = (BinaryObjectImpl)obj.inner2;

        assert innerPo.detached();

        DetachedInnerTestObject inner = innerPo.deserialize();

        assertEquals(id, inner.id);

        BinaryObjectImpl detachedPo = (BinaryObjectImpl)innerPo.detach();

        assert detachedPo.detached();

        inner = detachedPo.deserialize();

        assertEquals(id, inner.id);

        innerPo = (BinaryObjectImpl)obj.inner3;

        assert innerPo.detached();

        inner = innerPo.deserialize();

        assertEquals(id, inner.id);
        assertNotNull(inner.inner);

        detachedPo = (BinaryObjectImpl)innerPo.detach();

        assert detachedPo.detached();

        inner = innerPo.deserialize();

        assertEquals(id, inner.id);
        assertNotNull(inner.inner);
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testCollectionFields() throws Exception {
        BinaryMarshaller marsh = binaryMarshaller(Arrays.asList(
            new BinaryTypeConfiguration(CollectionFieldsObject.class.getName()),
            new BinaryTypeConfiguration(Key.class.getName()),
            new BinaryTypeConfiguration(Value.class.getName())
        ));

        Object[] arr = new Object[] {new Value(1), new Value(2), new Value(3)};
        Collection<Value> col = new ArrayList<>(Arrays.asList(new Value(4), new Value(5), new Value(6)));
        Map<Key, Value> map = new HashMap<>(F.asMap(new Key(10), new Value(10), new Key(20), new Value(20), new Key(30), new Value(30)));

        CollectionFieldsObject obj = new CollectionFieldsObject(arr, col, map);

        BinaryObject po = marshal(obj, marsh);

        Object[] arr0 = useBinaryArrays ? po.<BinaryArray>field("arr").array() : po.field("arr");

        assertEquals(3, arr0.length);

        int i = 1;

        for (Object valPo : arr0)
            assertEquals(i++, ((BinaryObject)valPo).<Value>deserialize().val);

        Collection<BinaryObject> col0 = po.field("col");

        i = 4;

        for (BinaryObject valPo : col0)
            assertEquals(i++, valPo.<Value>deserialize().val);

        Map<BinaryObject, BinaryObject> map0 = po.field("map");

        for (Map.Entry<BinaryObject, BinaryObject> e : map0.entrySet())
            assertEquals(e.getKey().<Key>deserialize().key, e.getValue().<Value>deserialize().val);
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testDefaultMapping() throws Exception {
        BinaryTypeConfiguration customMappingType =
            new BinaryTypeConfiguration(TestBinary.class.getName());

        customMappingType.setIdMapper(new BinaryIdMapper() {
            @Override public int typeId(String clsName) {
                String typeName = BinaryContext.SIMPLE_NAME_LOWER_CASE_MAPPER.typeName(clsName);

                return typeName.toLowerCase().hashCode();
            }

            @Override public int fieldId(int typeId, String fieldName) {
                return fieldName.toLowerCase().hashCode();
            }
        });

        BinaryMarshaller marsh1 = binaryMarshaller(Arrays.asList(
            new BinaryTypeConfiguration(SimpleObject.class.getName()),
            customMappingType
        ));

        TestBinary obj = binaryObject();

        BinaryObjectImpl po = marshal(obj, marsh1);

        BinaryMarshaller marsh2 = binaryMarshaller(Arrays.asList(
            new BinaryTypeConfiguration(SimpleObject.class.getName()),
            new BinaryTypeConfiguration(TestBinary.class.getName())
        ));

        po = marshal(obj, marsh2);

        assertEquals(obj, po.deserialize());
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testTypeNamesSimpleNameMapper() throws Exception {
        BinaryTypeConfiguration customType1 = new BinaryTypeConfiguration(Value.class.getName());

        customType1.setIdMapper(new BinaryIdMapper() {
            @Override public int typeId(String clsName) {
                return 300;
            }

            @Override public int fieldId(int typeId, String fieldName) {
                return 0;
            }
        });

        BinaryTypeConfiguration customType2 = new BinaryTypeConfiguration("org.gridgain.NonExistentClass1");

        customType2.setIdMapper(new BinaryIdMapper() {
            @Override public int typeId(String clsName) {
                return 400;
            }

            @Override public int fieldId(int typeId, String fieldName) {
                return 0;
            }
        });

        BinaryTypeConfiguration customType3 = new BinaryTypeConfiguration("NonExistentClass2");

        customType3.setIdMapper(new BinaryIdMapper() {
            @Override public int typeId(String clsName) {
                return 500;
            }

            @Override public int fieldId(int typeId, String fieldName) {
                return 0;
            }
        });

        BinaryTypeConfiguration customType4 = new BinaryTypeConfiguration("NonExistentClass0");

        customType4.setIdMapper(new BinaryIdMapper() {
            @Override public int typeId(String clsName) {
                return 0;
            }

            @Override public int fieldId(int typeId, String fieldName) {
                return 0;
            }
        });

        BinaryMarshaller marsh = binaryMarshaller(new BinaryBasicNameMapper(true), new BinaryBasicIdMapper(true),
            Arrays.asList(
                new BinaryTypeConfiguration(Key.class.getName()),
                new BinaryTypeConfiguration("org.gridgain.NonExistentClass3"),
                new BinaryTypeConfiguration("NonExistentClass4"),
                customType1,
                customType2,
                customType3,
                customType4
            ));

        BinaryContext ctx = binaryContext(marsh);

        // Full name hashCode.
        assertEquals("notconfiguredclass".hashCode(), ctx.typeId("NotConfiguredClass"));
        assertEquals("key".hashCode(), ctx.typeId(Key.class.getName()));
        assertEquals("nonexistentclass3".hashCode(), ctx.typeId("org.gridgain.NonExistentClass3"));
        assertEquals("nonexistentclass4".hashCode(), ctx.typeId("NonExistentClass4"));
        assertEquals(300, ctx.typeId(Value.class.getName()));
        assertEquals(400, ctx.typeId("org.gridgain.NonExistentClass1"));
        assertEquals(500, ctx.typeId("NonExistentClass2"));

        // BinaryIdMapper.typeId() contract.
        assertEquals("nonexistentclass0".hashCode(), ctx.typeId("NonExistentClass0"));
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testTypeNamesFullNameMappers() throws Exception {
        BinaryTypeConfiguration customType1 = new BinaryTypeConfiguration(Value.class.getName());

        customType1.setIdMapper(new BinaryIdMapper() {
            @Override public int typeId(String clsName) {
                return 300;
            }

            @Override public int fieldId(int typeId, String fieldName) {
                return 0;
            }
        });

        BinaryTypeConfiguration customType2 = new BinaryTypeConfiguration("org.gridgain.NonExistentClass1");

        customType2.setIdMapper(new BinaryIdMapper() {
            @Override public int typeId(String clsName) {
                return 400;
            }

            @Override public int fieldId(int typeId, String fieldName) {
                return 0;
            }
        });

        BinaryTypeConfiguration customType3 = new BinaryTypeConfiguration("NonExistentClass2");

        customType3.setIdMapper(new BinaryIdMapper() {
            @Override public int typeId(String clsName) {
                return 500;
            }

            @Override public int fieldId(int typeId, String fieldName) {
                return 0;
            }
        });

        BinaryTypeConfiguration customType4 = new BinaryTypeConfiguration("NonExistentClass0");

        customType4.setIdMapper(new BinaryIdMapper() {
            @Override public int typeId(String clsName) {
                return 0;
            }

            @Override public int fieldId(int typeId, String fieldName) {
                return 0;
            }
        });

        BinaryMarshaller marsh = binaryMarshaller(new BinaryBasicNameMapper(false), new BinaryBasicIdMapper(false),
            Arrays.asList(
                new BinaryTypeConfiguration(Key.class.getName()),
                new BinaryTypeConfiguration("org.gridgain.NonExistentClass3"),
                new BinaryTypeConfiguration("NonExistentClass4"),
                customType1,
                customType2,
                customType3,
                customType4
            ));

        BinaryContext ctx = binaryContext(marsh);

        // Full name hashCode.
        assertEquals("NotConfiguredClass".hashCode(), ctx.typeId("NotConfiguredClass"));
        assertEquals(Key.class.getName().hashCode(), ctx.typeId(Key.class.getName()));
        assertEquals("org.gridgain.NonExistentClass3".hashCode(), ctx.typeId("org.gridgain.NonExistentClass3"));
        assertEquals("NonExistentClass4".hashCode(), ctx.typeId("NonExistentClass4"));
        assertEquals(300, ctx.typeId(Value.class.getName()));
        assertEquals(400, ctx.typeId("org.gridgain.NonExistentClass1"));
        assertEquals(500, ctx.typeId("NonExistentClass2"));

        // BinaryIdMapper.typeId() contract.
        assertEquals("nonexistentclass0".hashCode(), ctx.typeId("NonExistentClass0"));
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testTypeNamesSimpleNameMappers() throws Exception {
        BinaryTypeConfiguration customType1 = new BinaryTypeConfiguration(Value.class.getName());

        customType1.setIdMapper(new BinaryIdMapper() {
            @Override public int typeId(String clsName) {
                return 300;
            }

            @Override public int fieldId(int typeId, String fieldName) {
                return 0;
            }
        });

        BinaryTypeConfiguration customType2 = new BinaryTypeConfiguration("org.gridgain.NonExistentClass1");

        customType2.setIdMapper(new BinaryIdMapper() {
            @Override public int typeId(String clsName) {
                return 400;
            }

            @Override public int fieldId(int typeId, String fieldName) {
                return 0;
            }
        });

        BinaryTypeConfiguration customType3 = new BinaryTypeConfiguration("NonExistentClass2");

        customType3.setIdMapper(new BinaryIdMapper() {
            @Override public int typeId(String clsName) {
                return 500;
            }

            @Override public int fieldId(int typeId, String fieldName) {
                return 0;
            }
        });

        BinaryTypeConfiguration customType4 = new BinaryTypeConfiguration("NonExistentClass0");

        customType4.setIdMapper(new BinaryIdMapper() {
            @Override public int typeId(String clsName) {
                return 0;
            }

            @Override public int fieldId(int typeId, String fieldName) {
                return 0;
            }
        });

        BinaryTypeConfiguration customType5 = new BinaryTypeConfiguration(DateClass1.class.getName());

        customType5.setNameMapper(new BinaryBasicNameMapper(false));
        customType5.setIdMapper(new BinaryBasicIdMapper(false));

        BinaryMarshaller marsh = binaryMarshaller(new BinaryBasicNameMapper(true), new BinaryBasicIdMapper(true),
            Arrays.asList(
                new BinaryTypeConfiguration(Key.class.getName()),
                new BinaryTypeConfiguration("org.gridgain.NonExistentClass3"),
                new BinaryTypeConfiguration("NonExistentClass4"),
                customType1,
                customType2,
                customType3,
                customType4,
                customType5
            ));

        BinaryContext ctx = binaryContext(marsh);

        assertEquals("notconfiguredclass".hashCode(), ctx.typeId("NotConfiguredClass"));
        assertEquals("notconfiguredclass".hashCode(), ctx.typeId("org.blabla.NotConfiguredClass"));
        assertEquals("key".hashCode(), ctx.typeId(Key.class.getName()));
        assertEquals("nonexistentclass3".hashCode(), ctx.typeId("org.gridgain.NonExistentClass3"));
        assertEquals("nonexistentclass4".hashCode(), ctx.typeId("NonExistentClass4"));

        assertEquals(300, ctx.typeId(Value.class.getName()));
        assertEquals(400, ctx.typeId("org.gridgain.NonExistentClass1"));
        assertEquals(500, ctx.typeId("NonExistentClass2"));

        assertEquals(DateClass1.class.getName().hashCode(), ctx.typeId(DateClass1.class.getName()));

        // BinaryIdMapper.typeId() contract.
        assertEquals("nonexistentclass0".hashCode(), ctx.typeId("NonExistentClass0"));
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testTypeNamesCustomIdMapper() throws Exception {
        BinaryTypeConfiguration customType1 = new BinaryTypeConfiguration(Value.class.getName());

        customType1.setIdMapper(new BinaryIdMapper() {
            @Override public int typeId(String clsName) {
                return 300;
            }

            @Override public int fieldId(int typeId, String fieldName) {
                return 0;
            }
        });

        BinaryTypeConfiguration customType2 = new BinaryTypeConfiguration("org.gridgain.NonExistentClass1");

        customType2.setIdMapper(new BinaryIdMapper() {
            @Override public int typeId(String clsName) {
                return 400;
            }

            @Override public int fieldId(int typeId, String fieldName) {
                return 0;
            }
        });

        BinaryTypeConfiguration customType3 = new BinaryTypeConfiguration("NonExistentClass2");

        customType3.setIdMapper(new BinaryIdMapper() {
            @Override public int typeId(String clsName) {
                return 500;
            }

            @Override public int fieldId(int typeId, String fieldName) {
                return 0;
            }
        });

        BinaryTypeConfiguration customType4 = new BinaryTypeConfiguration("NonExistentClass0");

        customType4.setIdMapper(new BinaryIdMapper() {
            @Override public int typeId(String clsName) {
                return 0;
            }

            @Override public int fieldId(int typeId, String fieldName) {
                return 0;
            }
        });

        BinaryTypeConfiguration customType5 = new BinaryTypeConfiguration(DateClass1.class.getName());

        customType5.setIdMapper(new BinaryBasicIdMapper(false));

        BinaryTypeConfiguration customType6 = new BinaryTypeConfiguration(MyTestClass.class.getName());

        customType6.setIdMapper(new BinaryBasicIdMapper(true));
        customType6.setNameMapper(new BinaryBasicNameMapper(true));

        BinaryMarshaller marsh = binaryMarshaller(new BinaryBasicNameMapper(false), new BinaryIdMapper() {
            @Override public int typeId(String clsName) {
                if ("org.blabla.NotConfiguredSpecialClass".equals(clsName))
                    return 0;
                else if (Key.class.getName().equals(clsName))
                    return 991;
                else if ("org.gridgain.NonExistentClass3".equals(clsName))
                    return 992;
                else if ("NonExistentClass4".equals(clsName))
                    return 993;

                return 999;
            }

            @Override public int fieldId(int typeId, String fieldName) {
                return 0;
            }
        }, Arrays.asList(
            new BinaryTypeConfiguration(Key.class.getName()),
            new BinaryTypeConfiguration("org.gridgain.NonExistentClass3"),
            new BinaryTypeConfiguration("NonExistentClass4"),
            customType1,
            customType2,
            customType3,
            customType4,
            customType5,
            customType6
        ));

        BinaryContext ctx = binaryContext(marsh);

        assertEquals(999, ctx.typeId("NotConfiguredClass"));
        assertEquals(999, ctx.typeId("org.blabla.NotConfiguredClass"));

        // BinaryIdMapper.typeId() contract.
        assertEquals("notconfiguredspecialclass".hashCode(), ctx.typeId("org.blabla.NotConfiguredSpecialClass"));

        assertEquals(991, ctx.typeId(Key.class.getName()));
        assertEquals(992, ctx.typeId("org.gridgain.NonExistentClass3"));
        assertEquals(993, ctx.typeId("NonExistentClass4"));

        // Custom types.
        assertEquals(300, ctx.typeId(Value.class.getName()));
        assertEquals(400, ctx.typeId("org.gridgain.NonExistentClass1"));
        assertEquals(500, ctx.typeId("NonExistentClass2"));

        // BinaryIdMapper.typeId() contract.
        assertEquals("nonexistentclass0".hashCode(), ctx.typeId("NonExistentClass0"));

        assertEquals(DateClass1.class.getName().hashCode(), ctx.typeId(DateClass1.class.getName()));
        assertEquals("mytestclass".hashCode(), ctx.typeId(MyTestClass.class.getName()));
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testCustomTypeRegistration() throws Exception {
        BinaryTypeConfiguration customType = new BinaryTypeConfiguration(Value.class.getName());

        BinaryMarshaller marsh = binaryMarshaller(Arrays.asList(customType));

        BinaryContext ctx = binaryContext(marsh);

        int typeId = ctx.typeId(Value.class.getName());

        BinaryClassDescriptor descriptor = ctx.descriptorForTypeId(true, typeId, null, true);

        assertEquals(Value.class, descriptor.describedClass());
        assertEquals(true, descriptor.registered());
        assertEquals(true, descriptor.userType());

        // Custom explicit types must be registered in 'predefinedTypes' in order not to break the interoperability.
        Field field = ctx.getClass().getDeclaredField("predefinedTypes");

        field.setAccessible(true);

        Map<Integer, BinaryClassDescriptor> map = (Map<Integer, BinaryClassDescriptor>)field.get(ctx);

        assertTrue(!map.isEmpty());

        assertNotNull(map.get(typeId));

        // Custom explicit types must NOT be registered in 'predefinedTypeNames'.
        field = ctx.getClass().getDeclaredField("predefinedTypeNames");

        field.setAccessible(true);

        Map<String, Integer> map2 = (Map<String, Integer>)field.get(ctx);

        assertTrue(!map2.isEmpty());

        assertNull(map2.get(ctx.userTypeName(Value.class.getName())));
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testFieldIdMapping() throws Exception {
        BinaryTypeConfiguration customType1 = new BinaryTypeConfiguration(Value.class.getName());

        customType1.setIdMapper(new BinaryIdMapper() {
            @Override public int typeId(String clsName) {
                return 300;
            }

            @Override public int fieldId(int typeId, String fieldName) {
                switch (fieldName) {
                    case "val1":
                        return 301;

                    case "val2":
                        return 302;

                    default:
                        return 0;
                }
            }
        });

        BinaryTypeConfiguration customType2 = new BinaryTypeConfiguration("NonExistentClass1");

        customType2.setIdMapper(new BinaryIdMapper() {
            @Override public int typeId(String clsName) {
                return 400;
            }

            @Override public int fieldId(int typeId, String fieldName) {
                switch (fieldName) {
                    case "val1":
                        return 401;

                    case "val2":
                        return 402;

                    default:
                        return 0;
                }
            }
        });

        BinaryMarshaller marsh = binaryMarshaller(Arrays.asList(new BinaryTypeConfiguration(Key.class.getName()),
            new BinaryTypeConfiguration("NonExistentClass2"),
            customType1,
            customType2));

        BinaryContext ctx = binaryContext(marsh);

        assertEquals("val".hashCode(), ctx.fieldId("key".hashCode(), "val"));
        assertEquals("val".hashCode(), ctx.fieldId("nonexistentclass2".hashCode(), "val"));
        assertEquals("val".hashCode(), ctx.fieldId("notconfiguredclass".hashCode(), "val"));
        assertEquals(301, ctx.fieldId(300, "val1"));
        assertEquals(302, ctx.fieldId(300, "val2"));
        assertEquals("val3".hashCode(), ctx.fieldId(300, "val3"));
        assertEquals(401, ctx.fieldId(400, "val1"));
        assertEquals(402, ctx.fieldId(400, "val2"));
        assertEquals("val3".hashCode(), ctx.fieldId(400, "val3"));
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testDuplicateTypeId() throws Exception {
        BinaryTypeConfiguration customType1 = new BinaryTypeConfiguration("org.gridgain.Class1");

        customType1.setIdMapper(new BinaryIdMapper() {
            @Override public int typeId(String clsName) {
                return 100;
            }

            @Override public int fieldId(int typeId, String fieldName) {
                return 0;
            }
        });

        BinaryTypeConfiguration customType2 = new BinaryTypeConfiguration("org.gridgain.Class2");

        customType2.setIdMapper(new BinaryIdMapper() {
            @Override public int typeId(String clsName) {
                return 100;
            }

            @Override public int fieldId(int typeId, String fieldName) {
                return 0;
            }
        });

        try {
            binaryMarshaller(Arrays.asList(customType1, customType2));
        }
        catch (BinaryObjectException e) {
            assertEquals("Duplicate type ID [clsName=org.gridgain.Class2, id=100]", e.getMessage());

            return;
        }

        assert false;
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testBinaryCopy() throws Exception {
        BinaryMarshaller marsh = binaryMarshaller(Arrays.asList(
            new BinaryTypeConfiguration(SimpleObject.class.getName())
        ));

        SimpleObject obj = simpleObject();

        final BinaryObject po = marshal(obj, marsh);

        assertEquals(obj, po.deserialize());

        BinaryObject copy = copy(po, null);

        assertEquals(obj, copy.deserialize());

        copy = copy(po, new HashMap<String, Object>());

        assertEquals(obj, copy.deserialize());

        Map<String, Object> map = new HashMap<>(1, 1.0f);

        map.put("i", 3);

        copy = copy(po, map);

        assertEquals((byte)2, copy.<Byte>field("b").byteValue());
        assertEquals((short)2, copy.<Short>field("s").shortValue());
        assertEquals(3, copy.<Integer>field("i").intValue());
        assertEquals(2L, copy.<Long>field("l").longValue());
        assertEquals(2.2f, copy.<Float>field("f").floatValue(), 0);
        assertEquals(2.2d, copy.<Double>field("d").doubleValue(), 0);
        assertEquals((char)2, copy.<Character>field("c").charValue());
        assertEquals(false, copy.<Boolean>field("bool").booleanValue());

        SimpleObject obj0 = copy.deserialize();

        assertEquals((byte)2, obj0.b);
        assertEquals((short)2, obj0.s);
        assertEquals(3, obj0.i);
        assertEquals(2L, obj0.l);
        assertEquals(2.2f, obj0.f, 0);
        assertEquals(2.2d, obj0.d, 0);
        assertEquals((char)2, obj0.c);
        assertEquals(false, obj0.bool);

        map = new HashMap<>(3, 1.0f);

        map.put("b", (byte)3);
        map.put("l", 3L);
        map.put("bool", true);

        copy = copy(po, map);

        assertEquals((byte)3, copy.<Byte>field("b").byteValue());
        assertEquals((short)2, copy.<Short>field("s").shortValue());
        assertEquals(2, copy.<Integer>field("i").intValue());
        assertEquals(3L, copy.<Long>field("l").longValue());
        assertEquals(2.2f, copy.<Float>field("f").floatValue(), 0);
        assertEquals(2.2d, copy.<Double>field("d").doubleValue(), 0);
        assertEquals((char)2, copy.<Character>field("c").charValue());
        assertEquals(true, copy.<Boolean>field("bool").booleanValue());

        obj0 = copy.deserialize();

        assertEquals((byte)3, obj0.b);
        assertEquals((short)2, obj0.s);
        assertEquals(2, obj0.i);
        assertEquals(3L, obj0.l);
        assertEquals(2.2f, obj0.f, 0);
        assertEquals(2.2d, obj0.d, 0);
        assertEquals((char)2, obj0.c);
        assertEquals(true, obj0.bool);

        map = new HashMap<>(8, 1.0f);

        map.put("b", (byte)3);
        map.put("s", (short)3);
        map.put("i", 3);
        map.put("l", 3L);
        map.put("f", 3.3f);
        map.put("d", 3.3d);
        map.put("c", (char)3);
        map.put("bool", true);

        copy = copy(po, map);

        assertEquals((byte)3, copy.<Byte>field("b").byteValue());
        assertEquals((short)3, copy.<Short>field("s").shortValue());
        assertEquals(3, copy.<Integer>field("i").intValue());
        assertEquals(3L, copy.<Long>field("l").longValue());
        assertEquals(3.3f, copy.<Float>field("f").floatValue(), 0);
        assertEquals(3.3d, copy.<Double>field("d").doubleValue(), 0);
        assertEquals((char)3, copy.<Character>field("c").charValue());
        assertEquals(true, copy.<Boolean>field("bool").booleanValue());

        obj0 = copy.deserialize();

        assertEquals((byte)3, obj0.b);
        assertEquals((short)3, obj0.s);
        assertEquals(3, obj0.i);
        assertEquals(3L, obj0.l);
        assertEquals(3.3f, obj0.f, 0);
        assertEquals(3.3d, obj0.d, 0);
        assertEquals((char)3, obj0.c);
        assertEquals(true, obj0.bool);

//        GridTestUtils.assertThrows(
//            log,
//            new Callable<Object>() {
//                @Override public Object call() throws Exception {
//                    po.copy(F.<String, Object>asMap("i", false));
//
//                    return null;
//                }
//            },
//            BinaryException.class,
//            "Invalid value type for field: i"
//        );
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testBinaryCopyString() throws Exception {
        BinaryMarshaller marsh = binaryMarshaller(Arrays.asList(
            new BinaryTypeConfiguration(SimpleObject.class.getName())
        ));

        SimpleObject obj = simpleObject();

        BinaryObject po = marshal(obj, marsh);

        BinaryObject copy = copy(po, F.<String, Object>asMap("str", "str3"));

        assertEquals("str3", copy.<String>field("str"));

        SimpleObject obj0 = copy.deserialize();

        assertEquals("str3", obj0.str);
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testBinaryCopyUuid() throws Exception {
        BinaryMarshaller marsh = binaryMarshaller(Arrays.asList(
            new BinaryTypeConfiguration(SimpleObject.class.getName())
        ));

        SimpleObject obj = simpleObject();

        BinaryObject po = marshal(obj, marsh);

        UUID uuid = UUID.randomUUID();

        BinaryObject copy = copy(po, F.<String, Object>asMap("uuid", uuid));

        assertEquals(uuid, copy.<UUID>field("uuid"));

        SimpleObject obj0 = copy.deserialize();

        assertEquals(uuid, obj0.uuid);
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testBinaryCopyByteArray() throws Exception {
        BinaryMarshaller marsh = binaryMarshaller(Arrays.asList(
            new BinaryTypeConfiguration(SimpleObject.class.getName())
        ));

        SimpleObject obj = simpleObject();

        BinaryObject po = marshal(obj, marsh);

        BinaryObject copy = copy(po, F.<String, Object>asMap("bArr", new byte[] {1, 2, 3}));

        assertArrayEquals(new byte[] {1, 2, 3}, copy.<byte[]>field("bArr"));

        SimpleObject obj0 = copy.deserialize();

        assertArrayEquals(new byte[] {1, 2, 3}, obj0.bArr);
    }

    /**
     * @param po Binary object.
     * @param fields Fields.
     * @return Copy.
     */
    private BinaryObject copy(BinaryObject po, Map<String, Object> fields) {
        BinaryObjectBuilder builder = BinaryObjectBuilderImpl.wrap(po);

        if (fields != null) {
            for (Map.Entry<String, Object> e : fields.entrySet())
                builder.setField(e.getKey(), e.getValue());
        }

        return builder.build();
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testBinaryCopyShortArray() throws Exception {
        BinaryMarshaller marsh = binaryMarshaller(Arrays.asList(
            new BinaryTypeConfiguration(SimpleObject.class.getName())
        ));

        SimpleObject obj = simpleObject();

        BinaryObject po = marshal(obj, marsh);

        BinaryObject copy = copy(po, F.<String, Object>asMap("sArr", new short[] {1, 2, 3}));

        assertArrayEquals(new short[] {1, 2, 3}, copy.<short[]>field("sArr"));

        SimpleObject obj0 = copy.deserialize();

        assertArrayEquals(new short[] {1, 2, 3}, obj0.sArr);
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testBinaryCopyIntArray() throws Exception {
        BinaryMarshaller marsh = binaryMarshaller(Arrays.asList(
            new BinaryTypeConfiguration(SimpleObject.class.getName())
        ));

        SimpleObject obj = simpleObject();

        BinaryObject po = marshal(obj, marsh);

        BinaryObject copy = copy(po, F.<String, Object>asMap("iArr", new int[] {1, 2, 3}));

        assertArrayEquals(new int[] {1, 2, 3}, copy.<int[]>field("iArr"));

        SimpleObject obj0 = copy.deserialize();

        assertArrayEquals(new int[] {1, 2, 3}, obj0.iArr);
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testBinaryCopyLongArray() throws Exception {
        BinaryMarshaller marsh = binaryMarshaller(Arrays.asList(
            new BinaryTypeConfiguration(SimpleObject.class.getName())
        ));

        SimpleObject obj = simpleObject();

        BinaryObject po = marshal(obj, marsh);

        BinaryObject copy = copy(po, F.<String, Object>asMap("lArr", new long[] {1, 2, 3}));

        assertArrayEquals(new long[] {1, 2, 3}, copy.<long[]>field("lArr"));

        SimpleObject obj0 = copy.deserialize();

        assertArrayEquals(new long[] {1, 2, 3}, obj0.lArr);
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testBinaryCopyFloatArray() throws Exception {
        BinaryMarshaller marsh = binaryMarshaller(Arrays.asList(
            new BinaryTypeConfiguration(SimpleObject.class.getName())
        ));

        SimpleObject obj = simpleObject();

        BinaryObject po = marshal(obj, marsh);

        BinaryObject copy = copy(po, F.<String, Object>asMap("fArr", new float[] {1, 2, 3}));

        assertArrayEquals(new float[] {1, 2, 3}, copy.<float[]>field("fArr"), 0);

        SimpleObject obj0 = copy.deserialize();

        assertArrayEquals(new float[] {1, 2, 3}, obj0.fArr, 0);
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testBinaryCopyDoubleArray() throws Exception {
        BinaryMarshaller marsh = binaryMarshaller(Arrays.asList(
            new BinaryTypeConfiguration(SimpleObject.class.getName())
        ));

        SimpleObject obj = simpleObject();

        BinaryObject po = marshal(obj, marsh);

        BinaryObject copy = copy(po, F.<String, Object>asMap("dArr", new double[] {1, 2, 3}));

        assertArrayEquals(new double[] {1, 2, 3}, copy.<double[]>field("dArr"), 0);

        SimpleObject obj0 = copy.deserialize();

        assertArrayEquals(new double[] {1, 2, 3}, obj0.dArr, 0);
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testBinaryCopyCharArray() throws Exception {
        BinaryMarshaller marsh = binaryMarshaller(Arrays.asList(
            new BinaryTypeConfiguration(SimpleObject.class.getName())
        ));

        SimpleObject obj = simpleObject();

        BinaryObject po = marshal(obj, marsh);

        BinaryObject copy = copy(po, F.<String, Object>asMap("cArr", new char[] {1, 2, 3}));

        assertArrayEquals(new char[] {1, 2, 3}, copy.<char[]>field("cArr"));

        SimpleObject obj0 = copy.deserialize();

        assertArrayEquals(new char[] {1, 2, 3}, obj0.cArr);
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testBinaryCopyStringArray() throws Exception {
        BinaryMarshaller marsh = binaryMarshaller(Arrays.asList(
            new BinaryTypeConfiguration(SimpleObject.class.getName())
        ));

        SimpleObject obj = simpleObject();

        BinaryObject po = marshal(obj, marsh);

        BinaryObject copy = copy(po, F.<String, Object>asMap("strArr", new String[] {"str1", "str2"}));

        assertArrayEquals(new String[] {"str1", "str2"}, copy.<String[]>field("strArr"));

        SimpleObject obj0 = copy.deserialize();

        assertArrayEquals(new String[] {"str1", "str2"}, obj0.strArr);
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testBinaryCopyObject() throws Exception {
        BinaryMarshaller marsh = binaryMarshaller(Arrays.asList(
            new BinaryTypeConfiguration(SimpleObject.class.getName())
        ));

        SimpleObject obj = simpleObject();

        BinaryObject po = marshal(obj, marsh);

        SimpleObject newObj = new SimpleObject();

        newObj.i = 12345;
        newObj.fArr = new float[] {5, 8, 0};
        newObj.str = "newStr";

        BinaryObject copy = copy(po, F.<String, Object>asMap("inner", newObj));

        assertEquals(newObj, copy.<BinaryObject>field("inner").deserialize());

        SimpleObject obj0 = copy.deserialize();

        assertEquals(newObj, obj0.inner);
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testBinaryCopyNonPrimitives() throws Exception {
        BinaryMarshaller marsh = binaryMarshaller(Arrays.asList(
            new BinaryTypeConfiguration(SimpleObject.class.getName())
        ));

        SimpleObject obj = simpleObject();

        BinaryObject po = marshal(obj, marsh);

        Map<String, Object> map = new HashMap<>(3, 1.0f);

        SimpleObject newObj = new SimpleObject();

        newObj.i = 12345;
        newObj.fArr = new float[] {5, 8, 0};
        newObj.str = "newStr";

        map.put("str", "str555");
        map.put("inner", newObj);
        map.put("bArr", new byte[] {6, 7, 9});

        BinaryObject copy = copy(po, map);

        assertEquals("str555", copy.<String>field("str"));
        assertEquals(newObj, copy.<BinaryObject>field("inner").deserialize());
        assertArrayEquals(new byte[] {6, 7, 9}, copy.<byte[]>field("bArr"));

        SimpleObject obj0 = copy.deserialize();

        assertEquals("str555", obj0.str);
        assertEquals(newObj, obj0.inner);
        assertArrayEquals(new byte[] {6, 7, 9}, obj0.bArr);
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testBinaryCopyMixed() throws Exception {
        BinaryMarshaller marsh = binaryMarshaller(Arrays.asList(new BinaryTypeConfiguration(SimpleObject.class.getName())));

        SimpleObject obj = simpleObject();

        BinaryObject po = marshal(obj, marsh);

        Map<String, Object> map = new HashMap<>(3, 1.0f);

        SimpleObject newObj = new SimpleObject();

        newObj.i = 12345;
        newObj.fArr = new float[] {5, 8, 0};
        newObj.str = "newStr";

        map.put("i", 1234);
        map.put("str", "str555");
        map.put("inner", newObj);
        map.put("s", (short)2323);
        map.put("bArr", new byte[] {6, 7, 9});
        map.put("b", (byte)111);

        BinaryObject copy = copy(po, map);

        assertEquals(1234, copy.<Integer>field("i").intValue());
        assertEquals("str555", copy.<String>field("str"));
        assertEquals(newObj, copy.<BinaryObject>field("inner").deserialize());
        assertEquals((short)2323, copy.<Short>field("s").shortValue());
        assertArrayEquals(new byte[] {6, 7, 9}, copy.<byte[]>field("bArr"));
        assertEquals((byte)111, copy.<Byte>field("b").byteValue());

        SimpleObject obj0 = copy.deserialize();

        assertEquals(1234, obj0.i);
        assertEquals("str555", obj0.str);
        assertEquals(newObj, obj0.inner);
        assertEquals((short)2323, obj0.s);
        assertArrayEquals(new byte[] {6, 7, 9}, obj0.bArr);
        assertEquals((byte)111, obj0.b);
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testKeepDeserialized() throws Exception {
        BinaryMarshaller marsh = binaryMarshaller(Arrays.asList(new BinaryTypeConfiguration(SimpleObject.class.getName())));

        BinaryObjectImpl po = marshal(simpleObject(), marsh);

        CacheObjectContext coCtx = new CacheObjectContext(newContext(), null, null, false, false, true, false, false);

        assert po.value(coCtx, false) == po.value(coCtx, false);

        po = marshal(simpleObject(), marsh);

        assert po.deserialize() != po.deserialize();
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testOffheapBinary() throws Exception {
        BinaryMarshaller marsh = binaryMarshaller(Arrays.asList(new BinaryTypeConfiguration(SimpleObject.class.getName())));

        BinaryContext ctx = binaryContext(marsh);

        SimpleObject simpleObj = simpleObject();

        BinaryObjectImpl obj = marshal(simpleObj, marsh);

        long ptr = 0;

        long ptr1 = 0;

        long ptr2 = 0;

        try {
            ptr = copyOffheap(obj);

            BinaryObjectOffheapImpl offheapObj = new BinaryObjectOffheapImpl(ctx,
                ptr,
                0,
                obj.array().length);

            assertTrue(offheapObj.equals(offheapObj));
            assertEquals(offheapObj.size(), obj.size());
            assertFalse(offheapObj.equals(null));
            assertFalse(offheapObj.equals("str"));
            assertTrue(offheapObj.equals(obj));
            assertTrue(obj.equals(offheapObj));

            ptr1 = copyOffheap(obj);

            BinaryObjectOffheapImpl offheapObj1 = new BinaryObjectOffheapImpl(ctx,
                ptr1,
                0,
                obj.array().length);

            assertTrue(offheapObj.equals(offheapObj1));
            assertTrue(offheapObj1.equals(offheapObj));

            assertEquals(obj.type().typeId(), offheapObj.type().typeId());
            assertEquals(obj.hashCode(), offheapObj.hashCode());

            checkSimpleObjectData(simpleObj, offheapObj);

            BinaryObjectOffheapImpl innerOffheapObj = offheapObj.field("inner");

            assertNotNull(innerOffheapObj);

            checkSimpleObjectData(simpleObj.inner, innerOffheapObj);

            obj = (BinaryObjectImpl)offheapObj.heapCopy();

            assertEquals(obj.type().typeId(), offheapObj.type().typeId());
            assertEquals(obj.hashCode(), offheapObj.hashCode());

            checkSimpleObjectData(simpleObj, obj);

            BinaryObjectImpl innerObj = obj.field("inner");

            assertNotNull(innerObj);

            checkSimpleObjectData(simpleObj.inner, innerObj);

            simpleObj.d = 0;

            obj = marshal(simpleObj, marsh);

            assertFalse(offheapObj.equals(obj));
            assertFalse(obj.equals(offheapObj));

            ptr2 = copyOffheap(obj);

            BinaryObjectOffheapImpl offheapObj2 = new BinaryObjectOffheapImpl(ctx,
                ptr2,
                0,
                obj.array().length);

            assertFalse(offheapObj.equals(offheapObj2));
            assertFalse(offheapObj2.equals(offheapObj));
        }
        finally {
            GridUnsafe.freeMemory(ptr);

            if (ptr1 > 0)
                GridUnsafe.freeMemory(ptr1);

            if (ptr2 > 0)
                GridUnsafe.freeMemory(ptr2);
        }
    }

    /**
     *
     */
    @Test
    public void testReadResolve() throws Exception {
        BinaryMarshaller marsh = binaryMarshaller(Arrays.asList(
            new BinaryTypeConfiguration(MySingleton.class.getName()),
            new BinaryTypeConfiguration(SingletonMarker.class.getName())));

        BinaryObjectImpl binaryObj = marshal(MySingleton.INSTANCE, marsh);

        assertTrue(binaryObj.array().length <= 1024); // Check that big string was not serialized.

        MySingleton singleton = binaryObj.deserialize();

        assertSame(MySingleton.INSTANCE, singleton);
    }

    /**
     *
     */
    @Test
    public void testReadResolveOnBinaryAware() throws Exception {
        BinaryMarshaller marsh = binaryMarshaller(Collections.singletonList(
            new BinaryTypeConfiguration(MyTestClass.class.getName())));

        BinaryObjectImpl binaryObj = marshal(new MyTestClass(), marsh);

        MyTestClass obj = binaryObj.deserialize();

        assertEquals("readResolve", obj.s);
    }

    /**
     *
     */
    @Test
    public void testDecimalFields() throws Exception {
        Collection<BinaryTypeConfiguration> clsNames = new ArrayList<>();

        clsNames.add(new BinaryTypeConfiguration(DecimalReflective.class.getName()));
        clsNames.add(new BinaryTypeConfiguration(DecimalMarshalAware.class.getName()));

        BinaryMarshaller marsh = binaryMarshaller(clsNames);

        // 1. Test reflective stuff.
        DecimalReflective obj1 = new DecimalReflective();

        BigDecimal valArr[] = new BigDecimal[] {BigDecimal.ONE, BigDecimal.TEN, new BigDecimal("-100.5"),
            BigDecimal.valueOf(Long.MAX_VALUE, 0), BigDecimal.valueOf(Long.MIN_VALUE, 0),
            BigDecimal.valueOf(Long.MAX_VALUE, 8), BigDecimal.valueOf(Long.MIN_VALUE, 8)};

        obj1.val = BigDecimal.ZERO;
        obj1.valArr = valArr;
        BinaryObjectImpl portObj = marshal(obj1, marsh);

        assertArrayEquals(obj1.valArr, portObj.<BigDecimal[]>field("valArr"));
        assertArrayEquals(obj1.valArr, portObj.<DecimalReflective>deserialize().valArr);
        assertArrayEquals(obj1.valArr, (BigDecimal[])portObj.type().field("valArr").value(portObj));

        obj1.valArr = null;

        for (BigDecimal v: valArr) {
            obj1.val = v;

            portObj = marshal(obj1, marsh);

            assertEquals(obj1.val, portObj.field("val"));
            assertEquals(obj1.val, portObj.<DecimalReflective>deserialize().val);
            assertEquals(obj1.val, portObj.type().field("val").value(portObj));
        }

        // 2. Test marshal aware stuff.
        DecimalMarshalAware obj2 = new DecimalMarshalAware();

        obj2.val = BigDecimal.ZERO;
        obj2.valArr = new BigDecimal[] {BigDecimal.ONE, BigDecimal.TEN.negate()};
        obj2.rawVal = BigDecimal.TEN;
        obj2.rawValArr = new BigDecimal[] {BigDecimal.ZERO, BigDecimal.ONE};

        portObj = marshal(obj2, marsh);

        assertEquals(obj2.val, portObj.field("val"));
        assertArrayEquals(obj2.valArr, portObj.<BigDecimal[]>field("valArr"));

        assertEquals(obj2.val, portObj.<DecimalMarshalAware>deserialize().val);
        assertArrayEquals(obj2.valArr, portObj.<DecimalMarshalAware>deserialize().valArr);
        assertEquals(obj2.rawVal, portObj.<DecimalMarshalAware>deserialize().rawVal);
        assertArrayEquals(obj2.rawValArr, portObj.<DecimalMarshalAware>deserialize().rawValArr);

        assertEquals(obj2.val, portObj.type().field("val").value(portObj));
        assertArrayEquals(obj2.valArr, (BigDecimal[])portObj.type().field("valArr").value(portObj));

        for (BigDecimal v: valArr) {
            obj2.val = v;

            portObj = marshal(obj2, marsh);

            assertEquals(obj2.val, portObj.field("val"));
            assertEquals(obj2.val, portObj.<DecimalMarshalAware>deserialize().val);
            assertEquals(obj2.val, portObj.type().field("val").value(portObj));
        }
    }

    /**
     * @throws IgniteCheckedException If failed.
     */
    @Test
    public void testFinalField() throws IgniteCheckedException {
        BinaryMarshaller marsh = binaryMarshaller();

        SimpleObjectWithFinal obj = new SimpleObjectWithFinal();

        SimpleObjectWithFinal po0 = marshalUnmarshal(obj, marsh);

        assertEquals(obj.time, po0.time);
    }

    /**
     * @throws IgniteCheckedException If failed.
     */
    @Test
    public void testThreadLocalArrayReleased() throws Exception {
        // Checking the writer directly.
        assertEquals(false, THREAD_LOCAL.isAcquired());

        BinaryMarshaller marsh = binaryMarshaller();

        try (BinaryWriterExImpl writer = new BinaryWriterExImpl(binaryContext(marsh))) {
            assertEquals(true, THREAD_LOCAL.isAcquired());

            writer.writeString("Thread local test");

            writer.array();

            assertEquals(true, THREAD_LOCAL.isAcquired());
        }

        // Checking the binary marshaller.
        assertEquals(false, THREAD_LOCAL.isAcquired());

        marsh = binaryMarshaller();

        marsh.marshal(new SimpleObject());

        assertEquals(false, THREAD_LOCAL.isAcquired());

        marsh = binaryMarshaller();

        // Checking the builder.
        BinaryObjectBuilder builder = new BinaryObjectBuilderImpl(binaryContext(marsh),
            "org.gridgain.foo.bar.TestClass");

        builder.setField("a", "1");

        BinaryObject binaryObj = builder.build();

        assertEquals(false, THREAD_LOCAL.isAcquired());
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testDuplicateNameSimpleNameMapper() throws Exception {
        BinaryMarshaller marsh = binaryMarshaller(new BinaryBasicNameMapper(true),
            new BinaryBasicIdMapper(true), null, null, null);

        Test1.Job job1 = new Test1().new Job();
        Test2.Job job2 = new Test2().new Job();

        marsh.marshal(job1);

        try {
            marsh.marshal(job2);
        }
        catch (BinaryObjectException e) {
            assertEquals(true, e.getMessage().contains("Failed to register class"));

            return;
        }

        assert false;
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testDuplicateNameFullNameMapper() throws Exception {
        BinaryMarshaller marsh = binaryMarshaller(new BinaryBasicNameMapper(false),
            new BinaryBasicIdMapper(false), null, null, null);

        Test1.Job job1 = new Test1().new Job();
        Test2.Job job2 = new Test2().new Job();

        marsh.marshal(job1);

        marsh.marshal(job2);
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testClass() throws Exception {
        BinaryMarshaller marsh = binaryMarshaller();

        Class cls = BinaryMarshallerSelfTest.class;

        Class unmarshalledCls = marshalUnmarshal(cls, marsh);

        Assert.assertEquals(cls, unmarshalledCls);
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testClassFieldsMarshalling() throws Exception {
        BinaryMarshaller marsh = binaryMarshaller();

        ObjectWithClassFields obj = new ObjectWithClassFields();
        obj.cls1 = BinaryMarshallerSelfTest.class;

        byte[] marshal = marsh.marshal(obj);

        ObjectWithClassFields obj2 = marsh.unmarshal(marshal, null);

        assertEquals(obj.cls1, obj2.cls1);
        assertNull(obj2.cls2);

        BinaryObject portObj = marshal(obj, marsh);

        Class cls1 = portObj.field("cls1");

        assertEquals(obj.cls1, cls1);
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testReadDetachedMap() throws Exception {
        Map<Key, Value> map = IntStream.range(0, 1000).mapToObj(i -> new T2<>(new Key(i), new Value(i)))
            .collect(Collectors.toMap(T2::getKey, T2::getValue));

        testReadDetachObjectProperly(map, obj -> {
            Map<BinaryObject, BinaryObject> desMap = (Map<BinaryObject, BinaryObject>)obj;

            assertEquals(map.size(), desMap.size());

            desMap.forEach((k, v) -> {
                Key key = new Key(k.field("key"));
                Value val = new Value(v.field("val"));

                assertTrue(map.containsKey(key));
                assertEquals(val, map.get(key));
            });
        }, false);
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testReadDetachedCollection() throws Exception {
        Collection<Value> col = IntStream.range(0, 1000).mapToObj(Value::new).collect(Collectors.toSet());

        testReadDetachObjectProperly(col, obj -> {
            Collection<BinaryObject> desCol = (Collection<BinaryObject>)obj;

            assertEquals(col.size(), desCol.size());

            desCol.forEach(v -> {
                Value val = new Value(v.field("val"));

                assertTrue(col.contains(val));
            });
        }, false);
    }

    /** @throws Exception If failed. */
    @Test
    public void testReadDetachedTypedArray() throws Exception {
        Value[] arr = IntStream.range(0, 1000).mapToObj(Value::new).toArray(Value[]::new);

        testReadDetachObjectProperly(arr, obj -> {
            assertArrayEquals(arr, (Value[])obj);

            Object[] args = new Object[] {obj};

            assertTrue(args[0] instanceof Value[]);

            args = PlatformUtils.unwrapBinariesInArray(args);

            assertTrue(args[0] instanceof Value[]);
            assertArrayEquals(arr, (Value[])args[0]);
        }, true);
    }

    /** @throws Exception If failed. */
    @Test
    public void testReadArrayOfCollections() throws Exception {
        Collection[] arr = new Collection[] { Arrays.asList(new Value(1), new Value(2), new Value(3)) };
        testReadDetachObjectProperly(arr, obj -> {
            assertArrayEquals(arr, (Collection[])obj);

            Object[] args = new Object[] {obj};

            assertTrue(args[0] instanceof Collection[]);

            args = PlatformUtils.unwrapBinariesInArray(args);

            assertTrue(args[0] instanceof Collection[]);
            assertArrayEquals(arr, (Collection[])args[0]);
        }, true);
    }


    /** @throws Exception If failed. */
    @Test
    public void testReadArrayOfBinaryCollections() throws Exception {
        Collection[] arr = new Collection[] { new ArrayList<>(Arrays.asList(new Value(1), new Value(2), new Value(3))) };

        testReadDetachObjectProperly(arr, obj -> {
            Object[] args = PlatformUtils.unwrapBinariesInArray(new Object[] {obj});

            Collection deserVals = (Collection)((Object[])args[0])[0];

            assertEqualsCollections(arr[0], deserVals);
        }, false);
    }

    /**
     * Perform action on binary object after unmarshalling from offheap data, when offheap memory chunk cleared.
     *
     * @param obj Object to marshal-unmarshal.
     * @param action Action to perform on object.
     * @throws Exception If failed.
     */
    private void testReadDetachObjectProperly(Object obj, IgniteThrowableConsumer<Object> action, boolean deserialize) throws Exception {
        BinaryMarshaller marsh = binaryMarshaller();

        BinaryHeapOutputStream os = new BinaryHeapOutputStream(1024);

        BinaryWriterExImpl writer = marsh.binaryMarshaller().writer(os);

        writer.writeObject(obj);

        BinaryHeapInputStream is = new BinaryHeapInputStream(os.array());

        BinaryReaderExImpl reader = marsh.binaryMarshaller().reader(is);

        Object bObj = reader.readObjectDetached(deserialize);

        Arrays.fill(os.array(), (byte)0);

        action.accept(bObj);
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testMarshallingThroughJdk() throws Exception {
        BinaryMarshaller marsh = binaryMarshaller();

        InetSocketAddress addr = new InetSocketAddress("192.168.0.2", 4545);

        byte[] arr = marsh.marshal(addr);

        InetSocketAddress addr2 = marsh.unmarshal(arr, null);

        assertEquals(addr.getHostString(), addr2.getHostString());
        assertEquals(addr.getPort(), addr2.getPort());

        TestAddress testAddr = new TestAddress();
        testAddr.addr = addr;
        testAddr.str1 = "Hello World";

        SimpleObject simpleObj = new SimpleObject();
        simpleObj.c = 'g';
        simpleObj.date = new Date();

        testAddr.obj = simpleObj;

        arr = marsh.marshal(testAddr);

        TestAddress testAddr2 = marsh.unmarshal(arr, null);

        assertEquals(testAddr.addr.getHostString(), testAddr2.addr.getHostString());
        assertEquals(testAddr.addr.getPort(), testAddr2.addr.getPort());
        assertEquals(testAddr.str1, testAddr2.str1);
        assertEquals(testAddr.obj.c, testAddr2.obj.c);
        assertEquals(testAddr.obj.date, testAddr2.obj.date);
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testPredefinedTypeIds() throws Exception {
        BinaryMarshaller marsh = binaryMarshaller();

        BinaryContext bCtx = binaryContext(marsh);

        Field field = bCtx.getClass().getDeclaredField("predefinedTypeNames");

        field.setAccessible(true);

        Map<String, Integer> map = (Map<String, Integer>)field.get(bCtx);

        assertTrue(!map.isEmpty());

        for (Map.Entry<String, Integer> entry : map.entrySet()) {
            int id = entry.getValue();

            if (id == GridBinaryMarshaller.UNREGISTERED_TYPE_ID)
                continue;

            BinaryClassDescriptor desc = bCtx.descriptorForTypeId(false, entry.getValue(), null, true);

            assertEquals(desc.typeId(), bCtx.typeId(desc.describedClass().getName()));
        }
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testProxy() throws Exception {
        BinaryMarshaller marsh = binaryMarshaller();

        SomeItf inItf = (SomeItf)Proxy.newProxyInstance(
            BinaryMarshallerSelfTest.class.getClassLoader(), new Class[] {SomeItf.class},
            new InvocationHandler() {
                private NonSerializable obj = new NonSerializable(null);

                @Override public Object invoke(Object proxy, Method mtd, Object[] args) throws Throwable {
                    if ("hashCode".equals(mtd.getName()))
                        return obj.hashCode();

                    obj.checkAfterUnmarshalled();

                    return 17;
                }
            }
        );

        SomeItf outItf = marsh.unmarshal(marsh.marshal(inItf), null);

        assertEquals(outItf.checkAfterUnmarshalled(), 17);
    }

    /**
     * Test object with {@link Proxy} field.
     *
     * @throws Exception If fails.
     */
    @Test
    public void testObjectContainingProxy() throws Exception {
        BinaryMarshaller marsh = binaryMarshaller();

        SomeItf inItf = (SomeItf)Proxy.newProxyInstance(
            BinaryMarshallerSelfTest.class.getClassLoader(), new Class[] {SomeItf.class},
            new InvocationHandler() {
                private NonSerializable obj = new NonSerializable(null);

                @Override public Object invoke(Object proxy, Method mtd, Object[] args) throws Throwable {
                    if ("hashCode".equals(mtd.getName()))
                        return obj.hashCode();

                    obj.checkAfterUnmarshalled();

                    return 17;
                }
            }
        );

        SomeItf outItf = marsh.unmarshal(marsh.marshal(inItf), null);

        assertEquals(outItf.checkAfterUnmarshalled(), 17);
    }

    /**
     * Test duplicate fields.
     *
     * @throws Exception If failed.
     */
    @Test
    public void testDuplicateFields() throws Exception {
        BinaryMarshaller marsh = binaryMarshaller();

        DuplicateFieldsB obj = new DuplicateFieldsB(1, 2);

        BinaryObjectImpl objBin = marshal(obj, marsh);

        String fieldName = "x";
        String fieldNameA = DuplicateFieldsA.class.getName() + "." + fieldName;
        String fieldNameB = DuplicateFieldsB.class.getName() + "." + fieldName;

        // Check "hasField".
        assert !objBin.hasField(fieldName);
        assert objBin.hasField(fieldNameA);
        assert objBin.hasField(fieldNameB);

        // Check direct field access.
        assertNull(objBin.field(fieldName));
        assertEquals(Integer.valueOf(1), objBin.field(fieldNameA));
        assertEquals(Integer.valueOf(2), objBin.field(fieldNameB));

        // Check metadata.
        BinaryType type = objBin.type();

        Collection<String> fieldNames = type.fieldNames();

        assertEquals(2, fieldNames.size());

        assert !fieldNames.contains(fieldName);
        assert fieldNames.contains(fieldNameA);
        assert fieldNames.contains(fieldNameB);

        // Check field access through type.
        BinaryField field = type.field(fieldName);
        BinaryField fieldA = type.field(fieldNameA);
        BinaryField fieldB = type.field(fieldNameB);

        assert !field.exists(objBin);
        assert fieldA.exists(objBin);
        assert fieldB.exists(objBin);

        assertNull(field.value(objBin));
        assertEquals(Integer.valueOf(1), fieldA.value(objBin));
        assertEquals(Integer.valueOf(2), fieldB.value(objBin));

        // Check object deserialization.
        DuplicateFieldsB deserialized = objBin.deserialize();

        assertEquals(obj.xA(), deserialized.xA());
        assertEquals(obj.xB(), deserialized.xB());
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testSingleHandle() throws Exception {
        SingleHandleA a = new SingleHandleA(new SingleHandleB());

        BinaryObjectImpl bo = marshal(a, binaryMarshaller());

        Map<String, BinaryObject> map = bo.field("map");

        BinaryObject innerBo = map.get("key");

        assertEquals(SingleHandleB.class, innerBo.deserialize().getClass());
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testUnregisteredClass() throws Exception {
        BinaryMarshaller m = binaryMarshaller(null, Collections.singletonList(Value.class.getName()));

        ClassFieldObject res = m.unmarshal(m.marshal(new ClassFieldObject(Value.class)), null);

        assertEquals(Value.class, res.cls);
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testMixedRawCollections() throws Exception {
        Collection<String> excludedClasses = Arrays.asList(
            ObjectRaw.class.getName(),
            ObjectWithRaw.class.getName(),
            Value.class.getName());

        BinaryMarshaller m0 = binaryMarshaller(null, excludedClasses);
        BinaryMarshaller m1 = binaryMarshaller();

        Value obj = new Value(27);
        ObjectWithRaw objectWithRaw = new ObjectWithRaw(27, 13);
        ObjectRaw objectRaw = new ObjectRaw(27, 13);

        Value objOther = new Value(26);
        ObjectWithRaw objectWithRawOther = new ObjectWithRaw(26, 13);
        ObjectRaw objectRawOther = new ObjectRaw(26, 13);

        ArrayList collection = new ArrayList(Arrays.asList(
            obj, objectWithRawOther, objectRaw, objectWithRaw, objectRawOther, objOther));

        marshalUnmarshal(collection, m0);
        marshalUnmarshal(collection, m1);
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testBinaryEquals() throws Exception {
        Collection<String> excludedClasses = Arrays.asList(
            ObjectRaw.class.getName(),
            ObjectWithRaw.class.getName(),
            Value.class.getName());

        BinaryMarshaller m0 = binaryMarshaller(null, excludedClasses);
        BinaryMarshaller m1 = binaryMarshaller();

        Value obj = new Value(27);
        ObjectWithRaw objectWithRaw = new ObjectWithRaw(27, 13);
        ObjectRaw objectRaw = new ObjectRaw(27, 13);

        Value objOther = new Value(26);
        ObjectWithRaw objectWithRawOther = new ObjectWithRaw(26, 13);
        ObjectRaw objectRawOther = new ObjectRaw(26, 13);

        BinaryObjectImpl binObj0 = marshal(obj, m0);
        BinaryObjectImpl binObj1 = marshal(obj, m1);
        BinaryObjectImpl binObjWithRaw0 = marshal(objectWithRaw, m0);
        BinaryObjectImpl binObjWithRaw1 = marshal(objectWithRaw, m1);
        BinaryObjectImpl binObjRaw0 = marshal(objectRaw, m0);
        BinaryObjectImpl binObjRaw1 = marshal(objectRaw, m1);

        assertNotEquals(binObj0.array().length, binObj1.array().length);
        assertNotEquals(binObjWithRaw0.array().length, binObjWithRaw1.array().length);
        assertNotEquals(binObjRaw0.array().length, binObjRaw1.array().length);

        checkEquals(binObj0, binObj1);

        checkEquals(binObjWithRaw0, binObjWithRaw1);

        checkEquals(binObjRaw0, binObjRaw1);

        BinaryObjectOffheapImpl binObjOffheap0 = null;
        BinaryObjectOffheapImpl binObjOffheap1 = null;
        BinaryObjectOffheapImpl binObjWithRawOffheap0 = null;
        BinaryObjectOffheapImpl binObjWithRawOffheap1 = null;
        BinaryObjectOffheapImpl binObjRawOffheap0 = null;
        BinaryObjectOffheapImpl binObjRawOffheap1 = null;

        BinaryObjectImpl binObjOther0 = marshal(objOther, m0);
        BinaryObjectImpl binObjOther1 = marshal(objOther, m1);
        BinaryObjectImpl binObjWithRawOther0 = marshal(objectWithRawOther, m0);
        BinaryObjectImpl binObjWithRawOther1 = marshal(objectWithRawOther, m1);
        BinaryObjectImpl binObjRawOther0 = marshal(objectRawOther, m0);
        BinaryObjectImpl binObjRawOther1 = marshal(objectRawOther, m1);

        assertEquals(binObjOther0.length(), binObj0.length());
        assertEquals(binObjOther1.length(), binObj1.length());
        assertEquals(binObjWithRawOther0.length(), binObjWithRaw0.length());
        assertEquals(binObjWithRawOther1.length(), binObjWithRaw1.length());
        assertEquals(binObjRawOther0.length(), binObjRaw0.length());
        assertEquals(binObjRawOther1.length(), binObjRaw1.length());

        assertNotEquals(binObjOther0, binObj0);
        assertNotEquals(binObjOther1, binObj1);
        assertNotEquals(binObjWithRawOther0, binObjWithRaw0);
        assertNotEquals(binObjWithRawOther1, binObjWithRaw1);
        assertNotEquals(binObjRawOther0, binObjRaw0);
        assertNotEquals(binObjRawOther1, binObjRaw1);

        try {
            binObjOffheap0 = marshalOffHeap(binObj0, m0);
            binObjOffheap1 = marshalOffHeap(binObj1, m1);
            binObjWithRawOffheap0 = marshalOffHeap(binObjWithRaw0, m0);
            binObjWithRawOffheap1 = marshalOffHeap(binObjWithRaw1, m1);
            binObjRawOffheap0 = marshalOffHeap(binObjRaw0, m0);
            binObjRawOffheap1 = marshalOffHeap(binObjRaw1, m1);

            checkEquals(binObj0, binObjOffheap0);
            checkEquals(binObj1, binObjOffheap0);
            checkEquals(binObj0, binObjOffheap1);
            checkEquals(binObj1, binObjOffheap1);
            checkEquals(binObjOffheap0, binObjOffheap1);

            checkEquals(binObjWithRaw0, binObjWithRawOffheap0);
            checkEquals(binObjWithRaw0, binObjWithRawOffheap1);
            checkEquals(binObjWithRaw1, binObjWithRawOffheap0);
            checkEquals(binObjWithRaw1, binObjWithRawOffheap1);
            checkEquals(binObjWithRawOffheap0, binObjWithRawOffheap1);

            checkEquals(binObjRaw0, binObjRawOffheap0);
            checkEquals(binObjRaw1, binObjRawOffheap0);
            checkEquals(binObjRaw0, binObjRawOffheap1);
            checkEquals(binObjRaw1, binObjRawOffheap1);
            checkEquals(binObjRawOffheap0, binObjRawOffheap1);
        }
        finally {
            if (binObjOffheap0 != null) {
                GridUnsafe.freeMemory(binObjOffheap0.offheapAddress());
                binObjOffheap0 = null;
            }

            if (binObjOffheap1 != null) {
                GridUnsafe.freeMemory(binObjOffheap1.offheapAddress());
                binObjOffheap1 = null;
            }

            if (binObjWithRawOffheap0 != null) {
                GridUnsafe.freeMemory(binObjWithRawOffheap0.offheapAddress());
                binObjOffheap1 = null;
            }

            if (binObjWithRawOffheap1 != null) {
                GridUnsafe.freeMemory(binObjWithRawOffheap1.offheapAddress());
                binObjOffheap1 = null;
            }

            if (binObjRawOffheap0 != null) {
                GridUnsafe.freeMemory(binObjRawOffheap0.offheapAddress());
                binObjOffheap1 = null;
            }

            if (binObjRawOffheap1 != null) {
                GridUnsafe.freeMemory(binObjRawOffheap1.offheapAddress());
                binObjOffheap1 = null;
            }
        }
    }

    /**
     * @param binObj0 Object #0.
     * @param binObj1 Object #1.
     */
    private void checkEquals(Object binObj0, Object binObj1) {
        assertEquals(binObj0, binObj1);
        assertEquals(binObj1, binObj0);
        assertEquals(binObj0, binObj0);
        assertEquals(binObj1, binObj1);
    }

    /**
     * @throws Exception If failed.
     */
    @Test
    public void testBinaryEqualsComplexObject() throws Exception {
        List<String> excludedClasses = Arrays.asList(
            TestClass0.class.getName(),
            TestClass1.class.getName(),
            TestClass2.class.getName());

        BinaryMarshaller m0 = binaryMarshaller(null, excludedClasses);
        BinaryMarshaller m1 = binaryMarshaller(null);

        TestClass0 obj0 = new TestClass0();
        TestClass1 obj1 = new TestClass1();
        TestClass2 obj2 = new TestClass2();

        BinaryObjectImpl binObj00 = marshal(obj0, m0);
        BinaryObjectImpl binObj01 = marshal(obj1, m0);
        BinaryObjectImpl binObj02 = marshal(obj2, m0);

        // The length of array must be equal. Object are different only by the class.
        assertEquals(binObj00.array().length, binObj01.array().length);
        assertEquals(binObj00.array().length, binObj02.array().length);

        BinaryObjectImpl binObj10 = marshal(obj0, m1);
        BinaryObjectImpl binObj11 = marshal(obj1, m1);
        BinaryObjectImpl binObj12 = marshal(obj2, m1);

        // The length of array must be equal. Object are different only by the class.
        assertEquals(binObj10.array().length, binObj11.array().length);
        assertEquals(binObj10.array().length, binObj12.array().length);

        assertNotEquals(binObj10.array().length, binObj00.array().length);

        assertEquals(binObj00, binObj10);
        assertEquals(binObj01, binObj11);
        assertEquals(binObj02, binObj12);

        assertNotEquals(binObj00, binObj01);
        assertNotEquals(binObj00, binObj02);
        assertNotEquals(binObj00, binObj11);
        assertNotEquals(binObj00, binObj12);

        assertNotEquals(binObj01, binObj00);
        assertNotEquals(binObj01, binObj02);
        assertNotEquals(binObj01, binObj10);
        assertNotEquals(binObj01, binObj12);

        assertNotEquals(binObj02, binObj00);
        assertNotEquals(binObj02, binObj01);
        assertNotEquals(binObj02, binObj00);
        assertNotEquals(binObj02, binObj11);
    }


    /**
     * The test must be refactored after {@link IgniteSystemProperties#IGNITE_BINARY_SORT_OBJECT_FIELDS}
     * is removed.
     *
     * @throws Exception If failed.
     */
    @Test
    public void testFieldOrder() throws Exception {
        if (BinaryUtils.FIELDS_SORTED_ORDER)
            return;

        BinaryMarshaller m = binaryMarshaller();

        BinaryObjectImpl binObj = marshal(simpleObject(), m);

        Collection<String> fieldsBin = binObj.type().fieldNames();

        Field[] fields = SimpleObject.class.getDeclaredFields();

        assertEquals(fields.length, fieldsBin.size());

        int i = 0;

        for (String fieldName : fieldsBin) {
            assertEquals(fields[i].getName(), fieldName);

            ++i;
        }
    }

    /**
     * The test must be refactored after {@link IgniteSystemProperties#IGNITE_BINARY_SORT_OBJECT_FIELDS}
     * is removed.
     *
     * @throws Exception If failed.
     */
    @Test
    public void testFieldOrderByBuilder() throws Exception {
        if (BinaryUtils.FIELDS_SORTED_ORDER)
            return;

        BinaryMarshaller m = binaryMarshaller();

        BinaryObjectBuilder builder = new BinaryObjectBuilderImpl(binaryContext(m), "MyFakeClass");

        String[] fieldNames = {"field9", "field8", "field0", "field1", "field2"};

        for (String fieldName : fieldNames)
            builder.setField(fieldName, 0);

        BinaryObject binObj = builder.build();

        Collection<String> fieldsBin = binObj.type().fieldNames();

        assertEquals(fieldNames.length, fieldsBin.size());

        int i = 0;

        for (String fieldName : fieldsBin) {
            assertEquals(fieldNames[i], fieldName);

            ++i;
        }
    }

    /**
     * @param obj Instance of the BinaryObjectImpl to offheap marshalling.
     * @param marsh Binary marshaller.
     * @return Instance of BinaryObjectOffheapImpl.
     */
    private BinaryObjectOffheapImpl marshalOffHeap(BinaryObjectImpl obj, BinaryMarshaller marsh) {
        long ptr = copyOffheap(obj);

        return new BinaryObjectOffheapImpl(binaryContext(marsh),
            ptr,
            0,
            obj.array().length);
    }

    /**
     *
     */
    private static interface SomeItf {
        /**
         * @return Check result.
         */
        int checkAfterUnmarshalled();
    }

    /**
     * Some non-serializable class.
     */
    @SuppressWarnings({"PublicField", "TransientFieldInNonSerializableClass", "FieldMayBeStatic"})
    private static class NonSerializableA {
        /** */
        private final long longVal = 0x33445566778899AAL;

        /** */
        protected Short shortVal = (short)0xAABB;

        /** */
        public String[] strArr = {"AA", "BB"};

        /** */
        public boolean flag1 = true;

        /** */
        public boolean flag2;

        /** */
        public Boolean flag3;

        /** */
        public Boolean flag4 = true;

        /** */
        public Boolean flag5 = false;

        /** */
        private transient int intVal = 0xAABBCCDD;

        /**
         * @param strArr Array.
         * @param shortVal Short value.
         */
        private NonSerializableA(@Nullable String[] strArr, @Nullable Short shortVal) {
            // No-op.
        }

        /**
         * Checks correctness of the state after unmarshalling.
         */
        void checkAfterUnmarshalled() {
            assertEquals(longVal, 0x33445566778899AAL);

            assertEquals(shortVal.shortValue(), (short)0xAABB);

            assertTrue(Arrays.equals(strArr, new String[] {"AA", "BB"}));

            assertEquals(0, intVal);

            assertTrue(flag1);
            assertFalse(flag2);
            assertNull(flag3);
            assertTrue(flag4);
            assertFalse(flag5);
        }
    }

    /**
     * Some non-serializable class.
     */
    @SuppressWarnings({"PublicField", "TransientFieldInNonSerializableClass", "PackageVisibleInnerClass"})
    static class NonSerializableB extends NonSerializableA {
        /** */
        public Short shortValue = 0x1122;

        /** */
        public long longValue = 0x8877665544332211L;

        /** */
        private transient NonSerializableA[] aArr = {
            new NonSerializableA(null, null),
            new NonSerializableA(null, null),
            new NonSerializableA(null, null)
        };

        /** */
        protected Double doubleVal = 123.456;

        /**
         * Just to eliminate the default constructor.
         */
        private NonSerializableB() {
            super(null, null);
        }

        /**
         * Checks correctness of the state after unmarshalling.
         */
        @Override void checkAfterUnmarshalled() {
            super.checkAfterUnmarshalled();

            assertEquals(shortValue.shortValue(), 0x1122);

            assertEquals(longValue, 0x8877665544332211L);

            assertNull(aArr);

            assertEquals(doubleVal, 123.456);
        }
    }

    /**
     * Some non-serializable class.
     */
    @SuppressWarnings({"TransientFieldInNonSerializableClass", "PublicField"})
    private static class NonSerializable extends NonSerializableB {
        /** */
        private int idVal = -17;

        /** */
        private final NonSerializableA aVal = new NonSerializableB();

        /** */
        private transient NonSerializableB bVal = new NonSerializableB();

        /** */
        private NonSerializableA[] bArr = new NonSerializableA[] {
            new NonSerializableB(),
            new NonSerializableA(null, null)
        };

        /** */
        public float floatVal = 567.89F;

        /**
         * Just to eliminate the default constructor.
         *
         * @param aVal Unused.
         */
        private NonSerializable(NonSerializableA aVal) {
        }

        /**
         * Checks correctness of the state after unmarshalling.
         */
        @Override void checkAfterUnmarshalled() {
            super.checkAfterUnmarshalled();

            assertEquals(idVal, -17);

            aVal.checkAfterUnmarshalled();

            assertNull(bVal);

            for (NonSerializableA a : bArr)
                a.checkAfterUnmarshalled();

            assertEquals(floatVal, 567.89F, 0);
        }
    }

    /**
     * Object with class fields.
     */
    private static class ObjectWithClassFields {
        /** */
        private Class<?> cls1;

        /** */
        private Class<?> cls2;
    }

    /**
     *
     */
    private static class TestAddress {
        /** */
        private SimpleObject obj;

        /** */
        private InetSocketAddress addr;

        /** */
        private String str1;
    }

    /**
     *
     */
    private static class Test1 {
        /**
         *
         */
        private class Job {

        }
    }

    /**
     *
     */
    private static class Test2 {
        /**
         *
         */
        private class Job {

        }
    }

    /**
     * @param obj Object.
     * @return Offheap address.
     */
    private long copyOffheap(BinaryObjectImpl obj) {
        byte[] arr = obj.array();

        long ptr = GridUnsafe.allocateMemory(arr.length);

        GridUnsafe.copyHeapOffheap(arr, GridUnsafe.BYTE_ARR_OFF, ptr, arr.length);

        return ptr;
    }

    /**
     * @param enumArr Enum array.
     * @return Ordinals.
     */
    private <T extends Enum<?>> Integer[] ordinals(T[] enumArr) {
        Integer[] ords = new Integer[enumArr.length];

        for (int i = 0; i < enumArr.length; i++)
            ords[i] = enumArr[i].ordinal();

        return ords;
    }

    /**
     * @param fld Field value.
     * @return Ordinals.
     */
    private <T extends Enum<?>> Integer[] binaryOrdinals(Object fld) {
        Object[] enumArr;

        if (useBinaryArrays) {
            assertTrue(fld instanceof BinaryEnumArray);

            enumArr = ((BinaryEnumArray)fld).array();
        }
        else {
            assertTrue(fld instanceof BinaryObject[]);

            enumArr = (Object[])fld;
        }

        Integer[] ords = new Integer[enumArr.length];

        for (int i = 0; i < enumArr.length; i++)
            ords[i] = ((BinaryObject)enumArr[i]).enumOrdinal();

        return ords;
    }

    /**
     * @param po Binary object.
     * @param off Offset.
     * @return Value.
     */
    private int intFromBinary(BinaryObject po, int off) {
        byte[] arr = U.field(po, "arr");

        return Integer.reverseBytes(U.bytesToInt(arr, off));
    }

    /**
     * @param obj Original object.
     * @return Result object.
     */
    private <T> T marshalUnmarshal(T obj) throws IgniteCheckedException {
        return marshalUnmarshal(obj, binaryMarshaller());
    }

    /**
     * @param obj Original object.
     * @param marsh Marshaller.
     * @return Result object.
     */
    private <T> T marshalUnmarshal(Object obj, BinaryMarshaller marsh) throws IgniteCheckedException {
        byte[] bytes = marsh.marshal(obj);

        return marsh.unmarshal(bytes, null);
    }

    /**
     * @param bo Binary object to deserialize.
     * @param marsh Marshaller.
     * @return Result object.
     */
    private <T> T unmarshal(BinaryObjectImpl bo, BinaryMarshaller marsh) throws IgniteCheckedException {
        return marsh.unmarshal(bo.array(), null);
    }

    /**
     * @param obj Object.
     * @param marsh Marshaller.
     * @return Binary object.
     */
    private <T> BinaryObjectImpl marshal(T obj, BinaryMarshaller marsh) throws IgniteCheckedException {
        byte[] bytes = marsh.marshal(obj);

        return new BinaryObjectImpl(U.<GridBinaryMarshaller>field(marsh, "impl").context(),
            bytes, 0);
    }

    /**
     * @return Whether to use compact footers or not.
     */
    protected boolean compactFooter() {
        return true;
    }

    /**
     * @param marsh Marshaller.
     * @return Binary context.
     */
    protected BinaryContext binaryContext(BinaryMarshaller marsh) {
        GridBinaryMarshaller impl = U.field(marsh, "impl");

        return impl.context();
    }

    /**
     *
     */
    protected BinaryMarshaller binaryMarshaller() throws IgniteCheckedException {
        return binaryMarshaller(null, null, null, null, null);
    }

    /**
     *
     */
    protected BinaryMarshaller binaryMarshaller(Collection<BinaryTypeConfiguration> cfgs)
        throws IgniteCheckedException {
        return binaryMarshaller(null, null, null, cfgs, null);
    }

    /**
     *
     */
    protected BinaryMarshaller binaryMarshaller(Collection<BinaryTypeConfiguration> cfgs,
        Collection<String> excludedClasses) throws IgniteCheckedException {
        return binaryMarshaller(null, null, null, cfgs, excludedClasses);
    }

    /**
     *
     */
    protected BinaryMarshaller binaryMarshaller(BinaryNameMapper nameMapper, BinaryIdMapper mapper,
        Collection<BinaryTypeConfiguration> cfgs)
        throws IgniteCheckedException {
        return binaryMarshaller(nameMapper, mapper, null, cfgs, null);
    }

    /**
     *
     */
    protected BinaryMarshaller binaryMarshaller(BinarySerializer serializer, Collection<BinaryTypeConfiguration> cfgs)
        throws IgniteCheckedException {
        return binaryMarshaller(null, null, serializer, cfgs, null);
    }

    /**
     * @return Binary marshaller.
     */
    protected BinaryMarshaller binaryMarshaller(
        BinaryNameMapper nameMapper,
        BinaryIdMapper mapper,
        BinarySerializer serializer,
        Collection<BinaryTypeConfiguration> cfgs,
        Collection<String> excludedClasses
    ) throws IgniteCheckedException {
        IgniteConfiguration iCfg = new IgniteConfiguration();

        BinaryConfiguration bCfg = new BinaryConfiguration();

        bCfg.setNameMapper(nameMapper);
        bCfg.setIdMapper(mapper);
        bCfg.setSerializer(serializer);
        bCfg.setCompactFooter(compactFooter());

        bCfg.setTypeConfigurations(cfgs);

        iCfg.setBinaryConfiguration(bCfg);
        iCfg.setClientMode(false);
        iCfg.setDiscoverySpi(new TcpDiscoverySpi() {
            @Override public void sendCustomEvent(DiscoverySpiCustomMessage msg) throws IgniteException {
                //No-op.
            }
        });
        iCfg.setSystemViewExporterSpi(new JmxSystemViewExporterSpi());

        BinaryContext ctx = new BinaryContext(BinaryCachingMetadataHandler.create(), iCfg, new NullLogger());

        BinaryMarshaller marsh = new BinaryMarshaller();

        MarshallerContextTestImpl marshCtx = new MarshallerContextTestImpl(null, excludedClasses);

        GridTestKernalContext kernCtx = new GridTestKernalContext(log, iCfg);

        kernCtx.add(new GridSystemViewManager(kernCtx));
        kernCtx.add(new GridDiscoveryManager(kernCtx));

        marshCtx.onMarshallerProcessorStarted(kernCtx, null);

        marsh.setContext(marshCtx);

        marsh.setBinaryContext(ctx, iCfg);

        return marsh;
    }

    /**
     * @return Binary object builder.
     */
    protected BinaryObjectBuilder builder(
        String typeName
    ) throws IgniteCheckedException {
        return builder(typeName, null, null, null, null, null);
    }

    /**
     * @return Binary object builder.
     */
    protected BinaryObjectBuilder builder(
        String typeName,
        BinaryNameMapper nameMapper,
        BinaryIdMapper mapper,
        BinarySerializer serializer,
        Collection<BinaryTypeConfiguration> cfgs,
        Collection<String> excludedClasses
    ) throws IgniteCheckedException {
        IgniteConfiguration iCfg = new IgniteConfiguration();

        BinaryConfiguration bCfg = new BinaryConfiguration();

        bCfg.setNameMapper(nameMapper);
        bCfg.setIdMapper(mapper);
        bCfg.setSerializer(serializer);
        bCfg.setCompactFooter(compactFooter());

        bCfg.setTypeConfigurations(cfgs);

        iCfg.setBinaryConfiguration(bCfg);
        iCfg.setClientMode(false);
        iCfg.setDiscoverySpi(new TcpDiscoverySpi() {
            @Override public void sendCustomEvent(DiscoverySpiCustomMessage msg) throws IgniteException {
                //No-op.
            }
        });
        iCfg.setSystemViewExporterSpi(new JmxSystemViewExporterSpi());

        BinaryContext ctx = new BinaryContext(BinaryCachingMetadataHandler.create(), iCfg, new NullLogger());

        BinaryMarshaller marsh = new BinaryMarshaller();

        MarshallerContextTestImpl marshCtx = new MarshallerContextTestImpl(null, excludedClasses);

        GridTestKernalContext kernCtx = new GridTestKernalContext(log, iCfg);

        kernCtx.add(new GridSystemViewManager(kernCtx));
        kernCtx.add(new GridDiscoveryManager(kernCtx));

        marshCtx.onMarshallerProcessorStarted(kernCtx, null);

        marsh.setContext(marshCtx);

        marsh.setBinaryContext(ctx, iCfg);

        return new BinaryObjectBuilderImpl(ctx, typeName);
    }

    /**
     * @param exp Expected.
     * @param act Actual.
     */
    private void assertBooleanArrayEquals(boolean[] exp, boolean[] act) {
        assertEquals(exp.length, act.length);

        for (int i = 0; i < act.length; i++)
            assertEquals(exp[i], act[i]);
    }

    /**
     *
     */
    private static class SimpleObjectWithFinal {
        /** */
        private final long time = System.currentTimeMillis();
    }

    /**
     * @return Simple object.
     */
    private static SimpleObject simpleObject() {
        SimpleObject inner = new SimpleObject();

        inner.b = 1;
        inner.s = 1;
        inner.i = 1;
        inner.l = 1;
        inner.f = 1.1f;
        inner.d = 1.1d;
        inner.c = 1;
        inner.bool = true;
        inner.str = "str1";
        inner.uuid = UUID.randomUUID();
        inner.date = new Date();
        inner.ts = new Timestamp(System.currentTimeMillis());
        inner.bArr = new byte[] {1, 2, 3};
        inner.sArr = new short[] {1, 2, 3};
        inner.iArr = new int[] {1, 2, 3};
        inner.lArr = new long[] {1, 2, 3};
        inner.fArr = new float[] {1.1f, 2.2f, 3.3f};
        inner.dArr = new double[] {1.1d, 2.2d, 3.3d};
        inner.cArr = new char[] {1, 2, 3};
        inner.boolArr = new boolean[] {true, false, true};
        inner.strArr = new String[] {"str1", "str2", "str3"};
        inner.uuidArr = new UUID[] {UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID()};
        inner.dateArr = new Date[] {new Date(11111), new Date(22222), new Date(33333)};
        inner.objArr = new Object[] {UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID()};
        inner.col = new ArrayList<>();
        inner.map = new HashMap<>();
        inner.enumVal = TestEnum.A;
        inner.enumArr = new TestEnum[] {TestEnum.A, TestEnum.B};
        inner.bdArr = new BigDecimal[] {new BigDecimal(1000), BigDecimal.ONE};

        inner.col.add("str1");
        inner.col.add("str2");
        inner.col.add("str3");

        inner.map.put(1, "str1");
        inner.map.put(2, "str2");
        inner.map.put(3, "str3");

        SimpleObject outer = new SimpleObject();

        outer.b = 2;
        outer.s = 2;
        outer.i = 2;
        outer.l = 2;
        outer.f = 2.2f;
        outer.d = 2.2d;
        outer.c = 2;
        outer.bool = false;
        outer.str = "str2";
        outer.uuid = UUID.randomUUID();
        outer.date = new Date();
        outer.ts = new Timestamp(System.currentTimeMillis());
        outer.bArr = new byte[] {10, 20, 30};
        outer.sArr = new short[] {10, 20, 30};
        outer.iArr = new int[] {10, 20, 30};
        outer.lArr = new long[] {10, 20, 30};
        outer.fArr = new float[] {10.01f, 20.02f, 30.03f};
        outer.dArr = new double[] {10.01d, 20.02d, 30.03d};
        outer.cArr = new char[] {10, 20, 30};
        outer.boolArr = new boolean[] {false, true, false};
        outer.strArr = new String[] {"str10", "str20", "str30"};
        outer.uuidArr = new UUID[] {UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID()};
        outer.dateArr = new Date[] {new Date(44444), new Date(55555), new Date(66666)};
        outer.objArr = new Object[] {UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID()};
        outer.col = new ArrayList<>();
        outer.map = new HashMap<>();
        outer.enumVal = TestEnum.B;
        outer.enumArr = new TestEnum[] {TestEnum.B, TestEnum.C};
        outer.inner = inner;
        outer.bdArr = new BigDecimal[] {new BigDecimal(5000), BigDecimal.TEN};

        outer.col.add("str4");
        outer.col.add("str5");
        outer.col.add("str6");

        outer.map.put(4, "str4");
        outer.map.put(5, "str5");
        outer.map.put(6, "str6");

        return outer;
    }

    /**
     * @return Binary object.
     */
    private TestBinary binaryObject() {
        SimpleObject innerSimple = new SimpleObject();

        innerSimple.b = 1;
        innerSimple.s = 1;
        innerSimple.i = 1;
        innerSimple.l = 1;
        innerSimple.f = 1.1f;
        innerSimple.d = 1.1d;
        innerSimple.c = 1;
        innerSimple.bool = true;
        innerSimple.str = "str1";
        innerSimple.uuid = UUID.randomUUID();
        innerSimple.date = new Date();
        innerSimple.ts = new Timestamp(System.currentTimeMillis());
        innerSimple.bArr = new byte[] {1, 2, 3};
        innerSimple.sArr = new short[] {1, 2, 3};
        innerSimple.iArr = new int[] {1, 2, 3};
        innerSimple.lArr = new long[] {1, 2, 3};
        innerSimple.fArr = new float[] {1.1f, 2.2f, 3.3f};
        innerSimple.dArr = new double[] {1.1d, 2.2d, 3.3d};
        innerSimple.cArr = new char[] {1, 2, 3};
        innerSimple.boolArr = new boolean[] {true, false, true};
        innerSimple.strArr = new String[] {"str1", "str2", "str3"};
        innerSimple.uuidArr = new UUID[] {UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID()};
        innerSimple.dateArr = new Date[] {new Date(11111), new Date(22222), new Date(33333)};
        innerSimple.objArr = new UUID[] {UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID()};
        innerSimple.col = new ArrayList<>();
        innerSimple.map = new HashMap<>();
        innerSimple.enumVal = TestEnum.A;
        innerSimple.enumArr = new TestEnum[] {TestEnum.A, TestEnum.B};

        innerSimple.col.add("str1");
        innerSimple.col.add("str2");
        innerSimple.col.add("str3");

        innerSimple.map.put(1, "str1");
        innerSimple.map.put(2, "str2");
        innerSimple.map.put(3, "str3");

        TestBinary innerBinary = new TestBinary();

        innerBinary.b = 2;
        innerBinary.s = 2;
        innerBinary.i = 2;
        innerBinary.l = 2;
        innerBinary.f = 2.2f;
        innerBinary.d = 2.2d;
        innerBinary.c = 2;
        innerBinary.bool = true;
        innerBinary.str = "str2";
        innerBinary.uuid = UUID.randomUUID();
        innerBinary.date = new Date();
        innerBinary.ts = new Timestamp(System.currentTimeMillis());
        innerBinary.bArr = new byte[] {10, 20, 30};
        innerBinary.sArr = new short[] {10, 20, 30};
        innerBinary.iArr = new int[] {10, 20, 30};
        innerBinary.lArr = new long[] {10, 20, 30};
        innerBinary.fArr = new float[] {10.01f, 20.02f, 30.03f};
        innerBinary.dArr = new double[] {10.01d, 20.02d, 30.03d};
        innerBinary.cArr = new char[] {10, 20, 30};
        innerBinary.boolArr = new boolean[] {true, false, true};
        innerBinary.strArr = new String[] {"str10", "str20", "str30"};
        innerBinary.uuidArr = new UUID[] {UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID()};
        innerBinary.dateArr = new Date[] {new Date(44444), new Date(55555), new Date(66666)};
        innerBinary.objArr = new Object[] {UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID()};
        innerBinary.bRaw = 3;
        innerBinary.sRaw = 3;
        innerBinary.iRaw = 3;
        innerBinary.lRaw = 3;
        innerBinary.fRaw = 3.3f;
        innerBinary.dRaw = 3.3d;
        innerBinary.cRaw = 3;
        innerBinary.boolRaw = true;
        innerBinary.strRaw = "str3";
        innerBinary.uuidRaw = UUID.randomUUID();
        innerBinary.dateRaw = new Date();
        innerBinary.tsRaw = new Timestamp(System.currentTimeMillis());
        innerBinary.bArrRaw = new byte[] {11, 21, 31};
        innerBinary.sArrRaw = new short[] {11, 21, 31};
        innerBinary.iArrRaw = new int[] {11, 21, 31};
        innerBinary.lArrRaw = new long[] {11, 21, 31};
        innerBinary.fArrRaw = new float[] {11.11f, 21.12f, 31.13f};
        innerBinary.dArrRaw = new double[] {11.11d, 21.12d, 31.13d};
        innerBinary.cArrRaw = new char[] {11, 21, 31};
        innerBinary.boolArrRaw = new boolean[] {true, false, true};
        innerBinary.strArrRaw = new String[] {"str11", "str21", "str31"};
        innerBinary.uuidArrRaw = new UUID[] {UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID()};
        innerBinary.dateArrRaw = new Date[] {new Date(77777), new Date(88888), new Date(99999)};
        innerBinary.objArrRaw = new Object[] {UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID()};
        innerBinary.col = new ArrayList<>();
        innerBinary.colRaw = new ArrayList<>();
        innerBinary.map = new HashMap<>();
        innerBinary.mapRaw = new HashMap<>();
        innerBinary.enumVal = TestEnum.B;
        innerBinary.enumValRaw = TestEnum.C;
        innerBinary.enumArr = new TestEnum[] {TestEnum.B, TestEnum.C};
        innerBinary.enumArrRaw = new TestEnum[] {TestEnum.C, TestEnum.D};

        innerBinary.col.add("str4");
        innerBinary.col.add("str5");
        innerBinary.col.add("str6");

        innerBinary.map.put(4, "str4");
        innerBinary.map.put(5, "str5");
        innerBinary.map.put(6, "str6");

        innerBinary.colRaw.add("str7");
        innerBinary.colRaw.add("str8");
        innerBinary.colRaw.add("str9");

        innerBinary.mapRaw.put(7, "str7");
        innerBinary.mapRaw.put(8, "str8");
        innerBinary.mapRaw.put(9, "str9");

        TestBinary outer = new TestBinary();

        outer.b = 4;
        outer.s = 4;
        outer.i = 4;
        outer.l = 4;
        outer.f = 4.4f;
        outer.d = 4.4d;
        outer.c = 4;
        outer.bool = true;
        outer.str = "str4";
        outer.uuid = UUID.randomUUID();
        outer.date = new Date();
        outer.ts = new Timestamp(System.currentTimeMillis());
        outer.bArr = new byte[] {12, 22, 32};
        outer.sArr = new short[] {12, 22, 32};
        outer.iArr = new int[] {12, 22, 32};
        outer.lArr = new long[] {12, 22, 32};
        outer.fArr = new float[] {12.21f, 22.22f, 32.23f};
        outer.dArr = new double[] {12.21d, 22.22d, 32.23d};
        outer.cArr = new char[] {12, 22, 32};
        outer.boolArr = new boolean[] {true, false, true};
        outer.strArr = new String[] {"str12", "str22", "str32"};
        outer.uuidArr = new UUID[] {UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID()};
        outer.dateArr = new Date[] {new Date(10101), new Date(20202), new Date(30303)};
        outer.objArr = new Object[] {UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID()};
        outer.simple = innerSimple;
        outer.binary = innerBinary;
        outer.bRaw = 5;
        outer.sRaw = 5;
        outer.iRaw = 5;
        outer.lRaw = 5;
        outer.fRaw = 5.5f;
        outer.dRaw = 5.5d;
        outer.cRaw = 5;
        outer.boolRaw = true;
        outer.strRaw = "str5";
        outer.uuidRaw = UUID.randomUUID();
        outer.dateRaw = new Date();
        outer.tsRaw = new Timestamp(System.currentTimeMillis());
        outer.bArrRaw = new byte[] {13, 23, 33};
        outer.sArrRaw = new short[] {13, 23, 33};
        outer.iArrRaw = new int[] {13, 23, 33};
        outer.lArrRaw = new long[] {13, 23, 33};
        outer.fArrRaw = new float[] {13.31f, 23.32f, 33.33f};
        outer.dArrRaw = new double[] {13.31d, 23.32d, 33.33d};
        outer.cArrRaw = new char[] {13, 23, 33};
        outer.boolArrRaw = new boolean[] {true, false, true};
        outer.strArrRaw = new String[] {"str13", "str23", "str33"};
        outer.uuidArrRaw = new UUID[] {UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID()};
        outer.dateArr = new Date[] {new Date(40404), new Date(50505), new Date(60606)};
        outer.objArrRaw = new Object[] {UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID()};
        outer.col = new ArrayList<>();
        outer.colRaw = new ArrayList<>();
        outer.map = new HashMap<>();
        outer.mapRaw = new HashMap<>();
        outer.enumVal = TestEnum.D;
        outer.enumValRaw = TestEnum.E;
        outer.enumArr = new TestEnum[] {TestEnum.D, TestEnum.E};
        outer.enumArrRaw = new TestEnum[] {TestEnum.E, TestEnum.A};
        outer.simpleRaw = innerSimple;
        outer.binaryRaw = innerBinary;

        outer.col.add("str10");
        outer.col.add("str11");
        outer.col.add("str12");

        outer.map.put(10, "str10");
        outer.map.put(11, "str11");
        outer.map.put(12, "str12");

        outer.colRaw.add("str13");
        outer.colRaw.add("str14");
        outer.colRaw.add("str15");

        outer.mapRaw.put(16, "str16");
        outer.mapRaw.put(17, "str16");
        outer.mapRaw.put(18, "str17");

        return outer;
    }

    /**
     */
    private enum TestEnum {
        /** */
        A,

        /** */
        B,

        /** */
        C,

        /** */
        D,

        /** */
        E
    }

    /** */
    public static class SimpleObject {
        /** */
        private byte b;

        /** */
        private short s;

        /** */
        private int i;

        /** */
        private long l;

        /** */
        private float f;

        /** */
        private double d;

        /** */
        private char c;

        /** */
        private boolean bool;

        /** */
        private String str;

        /** */
        private UUID uuid;

        /** */
        private Date date;

        /** */
        private Timestamp ts;

        /** */
        private Time time;

        /** */
        private byte[] bArr;

        /** */
        private short[] sArr;

        /** */
        private int[] iArr;

        /** */
        private long[] lArr;

        /** */
        private float[] fArr;

        /** */
        private double[] dArr;

        /** */
        private char[] cArr;

        /** */
        private boolean[] boolArr;

        /** */
        private String[] strArr;

        /** */
        private UUID[] uuidArr;

        /** */
        private Date[] dateArr;

        /** */
        private Time[] timeArr;

        /** */
        private Object[] objArr;

        /** */
        private BigDecimal[] bdArr;

        /** */
        private Collection<String> col;

        /** */
        private Map<Integer, String> map;

        /** */
        private TestEnum enumVal;

        /** */
        private TestEnum[] enumArr;

        /** */
        private SimpleObject inner;

        /** {@inheritDoc} */
        @Override public boolean equals(Object other) {
            if (this == other)
                return true;

            if (other == null || getClass() != other.getClass())
                return false;

            SimpleObject obj = (SimpleObject)other;

            return GridTestUtils.deepEquals(this, obj);
        }

        /** {@inheritDoc} */
        @Override public String toString() {
            return S.toString(SimpleObject.class, this);
        }
    }

    /** */
    private static class TestBinary implements Binarylizable {
        /** */
        private byte b;

        /** */
        private byte bRaw;

        /** */
        private short s;

        /** */
        private short sRaw;

        /** */
        private int i;

        /** */
        private int iRaw;

        /** */
        private long l;

        /** */
        private long lRaw;

        /** */
        private float f;

        /** */
        private float fRaw;

        /** */
        private double d;

        /** */
        private double dRaw;

        /** */
        private char c;

        /** */
        private char cRaw;

        /** */
        private boolean bool;

        /** */
        private boolean boolRaw;

        /** */
        private String str;

        /** */
        private String strRaw;

        /** */
        private UUID uuid;

        /** */
        private UUID uuidRaw;

        /** */
        private Date date;

        /** */
        private Date dateRaw;

        /** */
        private Timestamp ts;

        /** */
        private Timestamp tsRaw;

        /** */
        private Time time;

        /** */
        private Time timeRaw;

        /** */
        private byte[] bArr;

        /** */
        private byte[] bArrRaw;

        /** */
        private short[] sArr;

        /** */
        private short[] sArrRaw;

        /** */
        private int[] iArr;

        /** */
        private int[] iArrRaw;

        /** */
        private long[] lArr;

        /** */
        private long[] lArrRaw;

        /** */
        private float[] fArr;

        /** */
        private float[] fArrRaw;

        /** */
        private double[] dArr;

        /** */
        private double[] dArrRaw;

        /** */
        private char[] cArr;

        /** */
        private char[] cArrRaw;

        /** */
        private boolean[] boolArr;

        /** */
        private boolean[] boolArrRaw;

        /** */
        private String[] strArr;

        /** */
        private String[] strArrRaw;

        /** */
        private UUID[] uuidArr;

        /** */
        private UUID[] uuidArrRaw;

        /** */
        private Date[] dateArr;

        /** */
        private Date[] dateArrRaw;

        /** */
        private Time[] timeArr;

        /** */
        private Time[] timeArrRaw;

        /** */
        private Object[] objArr;

        /** */
        private Object[] objArrRaw;

        /** */
        private Collection<String> col;

        /** */
        private Collection<String> colRaw;

        /** */
        private Map<Integer, String> map;

        /** */
        private Map<Integer, String> mapRaw;

        /** */
        private TestEnum enumVal;

        /** */
        private TestEnum enumValRaw;

        /** */
        private TestEnum[] enumArr;

        /** */
        private TestEnum[] enumArrRaw;

        /** */
        private SimpleObject simple;

        /** */
        private SimpleObject simpleRaw;

        /** */
        private TestBinary binary;

        /** */
        private TestBinary binaryRaw;

        /** {@inheritDoc} */
        @Override public void writeBinary(BinaryWriter writer) throws BinaryObjectException {
            writer.writeByte("_b", b);
            writer.writeShort("_s", s);
            writer.writeInt("_i", i);
            writer.writeLong("_l", l);
            writer.writeFloat("_f", f);
            writer.writeDouble("_d", d);
            writer.writeChar("_c", c);
            writer.writeBoolean("_bool", bool);
            writer.writeString("_str", str);
            writer.writeUuid("_uuid", uuid);
            writer.writeDate("_date", date);
            writer.writeTimestamp("_ts", ts);
            writer.writeTime("_time", time);
            writer.writeByteArray("_bArr", bArr);
            writer.writeShortArray("_sArr", sArr);
            writer.writeIntArray("_iArr", iArr);
            writer.writeLongArray("_lArr", lArr);
            writer.writeFloatArray("_fArr", fArr);
            writer.writeDoubleArray("_dArr", dArr);
            writer.writeCharArray("_cArr", cArr);
            writer.writeBooleanArray("_boolArr", boolArr);
            writer.writeStringArray("_strArr", strArr);
            writer.writeUuidArray("_uuidArr", uuidArr);
            writer.writeDateArray("_dateArr", dateArr);
            writer.writeTimeArray("_timeArr", timeArr);
            writer.writeObjectArray("_objArr", objArr);
            writer.writeCollection("_col", col);
            writer.writeMap("_map", map);
            writer.writeEnum("_enumVal", enumVal);
            writer.writeEnumArray("_enumArr", enumArr);
            writer.writeObject("_simple", simple);
            writer.writeObject("_binary", binary);

            BinaryRawWriter raw = writer.rawWriter();

            raw.writeByte(bRaw);
            raw.writeShort(sRaw);
            raw.writeInt(iRaw);
            raw.writeLong(lRaw);
            raw.writeFloat(fRaw);
            raw.writeDouble(dRaw);
            raw.writeChar(cRaw);
            raw.writeBoolean(boolRaw);
            raw.writeString(strRaw);
            raw.writeUuid(uuidRaw);
            raw.writeDate(dateRaw);
            raw.writeTimestamp(tsRaw);
            raw.writeTime(timeRaw);
            raw.writeByteArray(bArrRaw);
            raw.writeShortArray(sArrRaw);
            raw.writeIntArray(iArrRaw);
            raw.writeLongArray(lArrRaw);
            raw.writeFloatArray(fArrRaw);
            raw.writeDoubleArray(dArrRaw);
            raw.writeCharArray(cArrRaw);
            raw.writeBooleanArray(boolArrRaw);
            raw.writeStringArray(strArrRaw);
            raw.writeUuidArray(uuidArrRaw);
            raw.writeDateArray(dateArrRaw);
            raw.writeTimeArray(timeArrRaw);
            raw.writeObjectArray(objArrRaw);
            raw.writeCollection(colRaw);
            raw.writeMap(mapRaw);
            raw.writeEnum(enumValRaw);
            raw.writeEnumArray(enumArrRaw);
            raw.writeObject(simpleRaw);
            raw.writeObject(binaryRaw);
        }

        /** {@inheritDoc} */
        @Override public void readBinary(BinaryReader reader) throws BinaryObjectException {
            b = reader.readByte("_b");
            s = reader.readShort("_s");
            i = reader.readInt("_i");
            l = reader.readLong("_l");
            f = reader.readFloat("_f");
            d = reader.readDouble("_d");
            c = reader.readChar("_c");
            bool = reader.readBoolean("_bool");
            str = reader.readString("_str");
            uuid = reader.readUuid("_uuid");
            date = reader.readDate("_date");
            ts = reader.readTimestamp("_ts");
            time = reader.readTime("_time");
            bArr = reader.readByteArray("_bArr");
            sArr = reader.readShortArray("_sArr");
            iArr = reader.readIntArray("_iArr");
            lArr = reader.readLongArray("_lArr");
            fArr = reader.readFloatArray("_fArr");
            dArr = reader.readDoubleArray("_dArr");
            cArr = reader.readCharArray("_cArr");
            boolArr = reader.readBooleanArray("_boolArr");
            strArr = reader.readStringArray("_strArr");
            uuidArr = reader.readUuidArray("_uuidArr");
            dateArr = reader.readDateArray("_dateArr");
            timeArr = reader.readTimeArray("_timeArr");
            objArr = reader.readObjectArray("_objArr");
            col = reader.readCollection("_col");
            map = reader.readMap("_map");
            enumVal = reader.readEnum("_enumVal");
            enumArr = reader.readEnumArray("_enumArr");
            simple = reader.readObject("_simple");
            binary = reader.readObject("_binary");

            BinaryRawReader raw = reader.rawReader();

            bRaw = raw.readByte();
            sRaw = raw.readShort();
            iRaw = raw.readInt();
            lRaw = raw.readLong();
            fRaw = raw.readFloat();
            dRaw = raw.readDouble();
            cRaw = raw.readChar();
            boolRaw = raw.readBoolean();
            strRaw = raw.readString();
            uuidRaw = raw.readUuid();
            dateRaw = raw.readDate();
            tsRaw = raw.readTimestamp();
            timeRaw = raw.readTime();
            bArrRaw = raw.readByteArray();
            sArrRaw = raw.readShortArray();
            iArrRaw = raw.readIntArray();
            lArrRaw = raw.readLongArray();
            fArrRaw = raw.readFloatArray();
            dArrRaw = raw.readDoubleArray();
            cArrRaw = raw.readCharArray();
            boolArrRaw = raw.readBooleanArray();
            strArrRaw = raw.readStringArray();
            uuidArrRaw = raw.readUuidArray();
            dateArrRaw = raw.readDateArray();
            timeArrRaw = raw.readTimeArray();
            objArrRaw = raw.readObjectArray();
            colRaw = raw.readCollection();
            mapRaw = raw.readMap();
            enumValRaw = raw.readEnum();
            enumArrRaw = raw.readEnumArray();
            simpleRaw = raw.readObject();
            binaryRaw = raw.readObject();
        }

        /** {@inheritDoc} */
        @Override public boolean equals(Object other) {
            if (this == other)
                return true;

            if (other == null || getClass() != other.getClass())
                return false;

            TestBinary obj = (TestBinary)other;

            return GridTestUtils.deepEquals(this, obj);
        }

        /** {@inheritDoc} */
        @Override public String toString() {
            return S.toString(TestBinary.class, this);
        }
    }

    /**
     */
    private static class CustomSerializedObject1 implements Binarylizable {
        /** */
        private int val;

        /**
         */
        private CustomSerializedObject1() {
            // No-op.
        }

        /**
         * @param val Value.
         */
        private CustomSerializedObject1(int val) {
            this.val = val;
        }

        /** {@inheritDoc} */
        @Override public void writeBinary(BinaryWriter writer) throws BinaryObjectException {
            // No-op.
        }

        /** {@inheritDoc} */
        @Override public void readBinary(BinaryReader reader) throws BinaryObjectException {
            // No-op.
        }
    }

    /**
     */
    private static class CustomSerializedObject2 implements Binarylizable {
        /** */
        private int val;

        /**
         */
        private CustomSerializedObject2() {
            // No-op.
        }

        /**
         * @param val Value.
         */
        private CustomSerializedObject2(int val) {
            this.val = val;
        }

        /** {@inheritDoc} */
        @Override public void writeBinary(BinaryWriter writer) throws BinaryObjectException {
            // No-op.
        }

        /** {@inheritDoc} */
        @Override public void readBinary(BinaryReader reader) throws BinaryObjectException {
            // No-op.
        }
    }

    /**
     */
    private static class CustomSerializer1 implements BinarySerializer {
        /** {@inheritDoc} */
        @Override public void writeBinary(Object obj, BinaryWriter writer) throws BinaryObjectException {
            CustomSerializedObject1 o = (CustomSerializedObject1)obj;

            writer.writeInt("val", o.val * 2);
        }

        /** {@inheritDoc} */
        @Override public void readBinary(Object obj, BinaryReader reader) throws BinaryObjectException {
            CustomSerializedObject1 o = (CustomSerializedObject1)obj;

            o.val = reader.readInt("val");
        }
    }

    /**
     */
    private static class CustomSerializer2 implements BinarySerializer {
        /** {@inheritDoc} */
        @Override public void writeBinary(Object obj, BinaryWriter writer) throws BinaryObjectException {
            CustomSerializedObject2 o = (CustomSerializedObject2)obj;

            writer.writeInt("val", o.val * 3);
        }

        /** {@inheritDoc} */
        @Override public void readBinary(Object obj, BinaryReader reader) throws BinaryObjectException {
            CustomSerializedObject2 o = (CustomSerializedObject2)obj;

            o.val = reader.readInt("val");
        }
    }

    /**
     */
    private static class CustomMappedObject1 {
        /** */
        private int val1;

        /** */
        private String val2;

        /**
         */
        private CustomMappedObject1() {
            // No-op.
        }

        /**
         * @param val1 Value 1.
         * @param val2 Value 2.
         */
        private CustomMappedObject1(int val1, String val2) {
            this.val1 = val1;
            this.val2 = val2;
        }
    }

    /**
     */
    private static class InnerMappedObject extends CustomMappedObject1 {
        /**
         * @param val1 Val1
         * @param val2 Val2
         */
        InnerMappedObject(int val1, String val2) {
            super(val1, val2);
        }
    }

    /**
     */
    private static class CustomMappedObject2 {
        /** */
        private int val1;

        /** */
        private String val2;

        /**
         */
        private CustomMappedObject2() {
            // No-op.
        }

        /**
         * @param val1 Value 1.
         * @param val2 Value 2.
         */
        private CustomMappedObject2(int val1, String val2) {
            this.val1 = val1;
            this.val2 = val2;
        }
    }

    /**
     */
    private static class DynamicObject implements Binarylizable {
        /** */
        private int idx;

        /** */
        private int val1;

        /** */
        private int val2;

        /** */
        private int val3;

        /**
         */
        private DynamicObject() {
            // No-op.
        }

        /**
         * @param val1 Value 1.
         * @param val2 Value 2.
         * @param val3 Value 3.
         */
        private DynamicObject(int idx, int val1, int val2, int val3) {
            this.idx = idx;
            this.val1 = val1;
            this.val2 = val2;
            this.val3 = val3;
        }

        /** {@inheritDoc} */
        @Override public void writeBinary(BinaryWriter writer) throws BinaryObjectException {
            writer.writeInt("val1", val1);

            if (idx > 0)
                writer.writeInt("val2", val2);

            if (idx > 1)
                writer.writeInt("val3", val3);
        }

        /** {@inheritDoc} */
        @Override public void readBinary(BinaryReader reader) throws BinaryObjectException {
            val1 = reader.readInt("val1");
            val2 = reader.readInt("val2");
            val3 = reader.readInt("val3");
        }
    }

    /**
     * Custom array list.
     */
    private static class CustomArrayList extends ArrayList {
        // No-op.
    }

    /**
     * Custom hash map.
     */
    private static class CustomHashMap extends HashMap {
        // No-op.
    }

    /**
     * Holder for non-stadard collections.
     */
    private static class CustomCollections {
        /** */
        public List list = new ArrayList();

        /** */
        public List customList = new CustomArrayList();
    }

    /** */
    @SuppressWarnings("unchecked")
    private static class CustomCollectionsWithFactory implements Binarylizable {
        /** */
        public List list = new CustomArrayList();

        /** */
        public Map map = new CustomHashMap();

        /** {@inheritDoc} */
        @Override public void writeBinary(BinaryWriter writer) throws BinaryObjectException {
            writer.writeCollection("list", list);
            writer.writeMap("map", map);
        }

        /** {@inheritDoc} */
        @Override public void readBinary(BinaryReader reader) throws BinaryObjectException {
            list = (List)reader.readCollection("list", new BinaryCollectionFactory<Object>() {
                @Override public Collection<Object> create(int size) {
                    return new CustomArrayList();
                }
            });

            map = reader.readMap("map", new BinaryMapFactory<Object, Object>() {
                @Override public Map<Object, Object> create(int size) {
                    return new CustomHashMap();
                }
            });
        }
    }

    /**
     * Dummy value holder.
     */
    private static class DummyHolder {
        /** Value. */
        public int val;

        /**
         * Constructor.
         *
         * @param val Value.
         */
        public DummyHolder(int val) {
            this.val = val;
        }

        /** {@inheritDoc} */
        @Override public boolean equals(Object o) {
            return o != null && o instanceof DummyHolder && ((DummyHolder)o).val == val;
        }

        /** {@inheritDoc} */
        @Override public int hashCode() {
            return val;
        }
    }

    /**
     */
    private static class CycleLinkObject {
        /** */
        private CycleLinkObject self;
    }

    /**
     */
    private static class DetachedTestObject implements Binarylizable {
        /** */
        private DetachedInnerTestObject inner1;

        /** */
        private Object inner2;

        /** */
        private Object inner3;

        /** */
        private DetachedInnerTestObject inner4;

        /**
         */
        private DetachedTestObject() {
            // No-op.
        }

        /**
         * @param inner Inner object.
         */
        private DetachedTestObject(DetachedInnerTestObject inner) {
            inner1 = inner;
            inner2 = inner;
            inner3 = new DetachedInnerTestObject(inner, inner.id);
            inner4 = inner;
        }

        /** {@inheritDoc} */
        @Override public void writeBinary(BinaryWriter writer) throws BinaryObjectException {
            BinaryRawWriterEx raw = (BinaryRawWriterEx)writer.rawWriter();

            raw.writeObject(inner1);
            raw.writeObjectDetached(inner2);
            raw.writeObjectDetached(inner3);
            raw.writeObject(inner4);
        }

        /** {@inheritDoc} */
        @Override public void readBinary(BinaryReader reader) throws BinaryObjectException {
            BinaryRawReaderEx raw = (BinaryRawReaderEx)reader.rawReader();

            inner1 = (DetachedInnerTestObject)raw.readObject();
            inner2 = raw.readObjectDetached();
            inner3 = raw.readObjectDetached();
            inner4 = (DetachedInnerTestObject)raw.readObject();
        }
    }

    /**
     */
    private static class DetachedInnerTestObject {
        /** */
        private DetachedInnerTestObject inner;

        /** */
        private UUID id;

        /**
         */
        private DetachedInnerTestObject() {
            // No-op.
        }

        /**
         * @param inner Inner object.
         * @param id ID.
         */
        private DetachedInnerTestObject(DetachedInnerTestObject inner, UUID id) {
            this.inner = inner;
            this.id = id;
        }
    }

    /**
     */
    private static class CollectionFieldsObject {
        /** */
        private Object[] arr;

        /** */
        private Collection<Value> col;

        /** */
        private Map<Key, Value> map;

        /**
         */
        private CollectionFieldsObject() {
            // No-op.
        }

        /**
         * @param arr Array.
         * @param col Collection.
         * @param map Map.
         */
        private CollectionFieldsObject(Object[] arr, Collection<Value> col, Map<Key, Value> map) {
            this.arr = arr;
            this.col = col;
            this.map = map;
        }
    }

    /**
     */
    private static class Key {
        /** */
        private int key;

        /**
         */
        private Key() {
            // No-op.
        }

        /**
         * n
         *
         * @param key Key.
         */
        private Key(int key) {
            this.key = key;
        }

        /** {@inheritDoc} */
        @Override public boolean equals(Object o) {
            if (this == o)
                return true;

            if (o == null || getClass() != o.getClass())
                return false;

            Key key0 = (Key)o;

            return key == key0.key;

        }

        /** {@inheritDoc} */
        @Override public int hashCode() {
            return key;
        }
    }

    /**
     */
    private static class Value {
        /** */
        private int val;

        /**
         */
        private Value() {
            // No-op.
        }

        /**
         * @param val Value.
         */
        private Value(int val) {
            this.val = val;
        }

        /** {@inheritDoc} */
        @Override public boolean equals(Object o) {
            if (this == o)
                return true;

            if (!(o instanceof Value))
                return false;

            Value value = (Value)o;

            return val == value.val;
        }

        /** {@inheritDoc} */
        @Override public int hashCode() {
            return val;
        }
    }

    /**
     */
    private static class DateClass1 {
        /** */
        private Date date;

        /** */
        private Timestamp ts;

        /** */
        private Time time;

        /** */
        private Time[] timeArr;
    }

    /**
     *
     */
    private static class NoPublicConstructor {
        /** */
        private String val = "test";

        /**
         * @return Value.
         */
        public String getVal() {
            return val;
        }
    }

    /**
     *
     */
    @SuppressWarnings("PublicConstructorInNonPublicClass")
    private static class NoPublicDefaultConstructor {
        /** */
        private int val;

        /**
         * @param val Value.
         */
        public NoPublicDefaultConstructor(int val) {
            this.val = val;
        }
    }

    /**
     *
     */
    private static class ProtectedConstructor {
        /**
         * Protected constructor.
         */
        protected ProtectedConstructor() {
            // No-op.
        }
    }

    /**
     *
     */
    private static class MyTestClass implements Binarylizable {
        /** */
        private boolean readyToSerialize;

        /** */
        private String s;

        /**
         * @return Object.
         */
        Object writeReplace() {
            readyToSerialize = true;

            return this;
        }

        /**
         * @return Object.
         */
        Object readResolve() {
            s = "readResolve";

            return this;
        }

        /** {@inheritDoc} */
        @Override public void writeBinary(BinaryWriter writer) throws BinaryObjectException {
            if (!readyToSerialize)
                fail();
        }

        /** {@inheritDoc} */
        @Override public void readBinary(BinaryReader reader) throws BinaryObjectException {
            s = "readBinary";
        }
    }

    /**
     *
     */
    private static class MySingleton {
        /** */
        public static final MySingleton INSTANCE = new MySingleton();

        /** */
        private String s;

        /** Initializer. */ {
            StringBuilder builder = new StringBuilder();

            for (int i = 0; i < 10000; i++) {
                builder.append("+");
            }

            s = builder.toString();
        }

        /**
         * @return Object.
         */
        Object writeReplace() {
            return new SingletonMarker();
        }
    }

    /**
     *
     */
    private static class SingletonMarker {
        /**
         * @return Object.
         */
        Object readResolve() {
            return MySingleton.INSTANCE;
        }
    }

    /**
     *
     */
    public static class SimpleEnclosingObject {
        /** */
        private Object simpl;
    }

    /**
     *
     */
    public static class SimpleExternalizable implements Externalizable {
        /** */
        private String field;

        /**
         * {@link Externalizable} support.
         */
        public SimpleExternalizable() {
            // No-op.
        }

        /**
         * @param field Field.
         */
        public SimpleExternalizable(String field) {
            this.field = field;
        }

        /** {@inheritDoc} */
        @Override public void writeExternal(ObjectOutput out) throws IOException {
            U.writeString(out, field);
        }

        /** {@inheritDoc} */
        @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
            field = U.readString(in);
        }

        /** {@inheritDoc} */
        @Override public boolean equals(Object o) {
            if (this == o)
                return true;

            if (o == null || getClass() != o.getClass())
                return false;

            SimpleExternalizable that = (SimpleExternalizable)o;

            return field.equals(that.field);
        }

        /** {@inheritDoc} */
        @Override public int hashCode() {
            return field.hashCode();
        }
    }

    /**
     * Class B for duplicate fields test.
     */
    private static class DuplicateFieldsA {
        /** Field. */
        int x;

        /**
         * Constructor.
         *
         * @param x Field.
         */
        protected DuplicateFieldsA(int x) {
            this.x = x;
        }

        /**
         * @return A's field.
         */
        public int xA() {
            return x;
        }
    }

    /**
     * Class B for duplicate fields test.
     */
    private static class DuplicateFieldsB extends DuplicateFieldsA {
        /** Field. */
        int x;

        /**
         * Constructor.
         *
         * @param xA Field for parent class.
         * @param xB Field for current class.
         */
        public DuplicateFieldsB(int xA, int xB) {
            super(xA);

            this.x = xB;
        }

        /**
         * @return B's field.
         */
        public int xB() {
            return x;
        }
    }

    /**
     *
     */
    private static class DecimalReflective {
        /** */
        public BigDecimal val;

        /** */
        public BigDecimal[] valArr;
    }

    /**
     *
     */
    private static class DecimalMarshalAware extends DecimalReflective implements Binarylizable {
        /** */
        public BigDecimal rawVal;

        /** */
        public BigDecimal[] rawValArr;

        /** {@inheritDoc} */
        @Override public void writeBinary(BinaryWriter writer) throws BinaryObjectException {
            writer.writeDecimal("val", val);
            writer.writeDecimalArray("valArr", valArr);

            BinaryRawWriter rawWriter = writer.rawWriter();

            rawWriter.writeDecimal(rawVal);
            rawWriter.writeDecimalArray(rawValArr);
        }

        /** {@inheritDoc} */
        @Override public void readBinary(BinaryReader reader) throws BinaryObjectException {
            val = reader.readDecimal("val");
            valArr = reader.readDecimalArray("valArr");

            BinaryRawReader rawReader = reader.rawReader();

            rawVal = rawReader.readDecimal();
            rawValArr = rawReader.readDecimalArray();
        }
    }

    /**
     */
    private static class SingleHandleA {
        /** */
        private SingleHandleB b;

        /** */
        private Map<Object, SingleHandleB> map = new HashMap<>();

        /**
         * @param b B.
         */
        SingleHandleA(SingleHandleB b) {
            this.b = b;

            map.put("key", b);
        }
    }

    /**
     */
    private static class SingleHandleB {
    }

    /**
     */
    private static class ClassFieldObject {
        /** */
        private Class<?> cls;

        /**
         * @param cls Class field.
         */
        public ClassFieldObject(Class<?> cls) {
            this.cls = cls;
        }
    }

    /**
     *
     */
    private static class TestClass0 {
        /** */
        private int intVal = 33;

        /** */
        private String strVal = "Test string value";

        /** */
        private SimpleObject obj = constSimpleObject();

        /**
         * @return Constant value of the SimpleObject.
         */
        public static SimpleObject constSimpleObject() {
            SimpleObject obj = simpleObject();

            obj.uuid = null;
            obj.date = new Date(33);
            obj.ts = new Timestamp(22);
            obj.uuidArr = new UUID[] {null, null, null};
            obj.dateArr = new Date[] {new Date(11111), new Date(22222), new Date(33333)};
            obj.objArr = new Object[] {null, null, null};

            obj.inner.uuid = null;
            obj.inner.date = new Date(33);
            obj.inner.ts = new Timestamp(22);
            obj.inner.uuidArr = new UUID[] {null, null, null};
            obj.inner.dateArr = new Date[] {new Date(11111), new Date(22222), new Date(33333)};
            obj.inner.objArr = new Object[] {null, null, null};

            return obj;
        }
    }

    /** */
    public static class ArrayFieldClass {
        /** */
        protected TestClass1[] arr1;

        /** */
        protected TestClass1[] arr2;
    }

    /**
     *
     */
    public static class TestClass1 {
        /** */
        private int intVal = 33;

        /** */
        private String strVal = "Test string value";

        /** */
        private SimpleObject obj = TestClass0.constSimpleObject();

        /** {@inheritDoc} */
        @Override public boolean equals(Object o) {
            if (this == o)
                return true;
            if (o == null || getClass() != o.getClass())
                return false;
            TestClass1 class1 = (TestClass1)o;
            return intVal == class1.intVal && Objects.equals(strVal, class1.strVal) && Objects.equals(obj, class1.obj);
        }

        /** {@inheritDoc} */
        @Override public int hashCode() {
            return Objects.hash(intVal, strVal, obj);
        }
    }

    /**
     *
     */
    private static class TestClass2 extends TestClass0 {
    }

    /** */
    private static class ObjectWithRaw implements Binarylizable {
        /** */
        private int val;

        /** */
        private int rawVal;

        /**
         *
         */
        public ObjectWithRaw() {
        }

        /**
         * @param val Value.
         * @param rawVal Raw value.
         */
        public ObjectWithRaw(int val, int rawVal) {
            this.val = val;
            this.rawVal = rawVal;
        }

        /** {@inheritDoc} */
        @Override public void writeBinary(BinaryWriter writer) throws BinaryObjectException {
            writer.writeInt("val", val);

            writer.rawWriter().writeInt(rawVal);
        }

        /** {@inheritDoc} */
        @Override public void readBinary(BinaryReader reader) throws BinaryObjectException {
            val = reader.readInt("val");

            rawVal = reader.rawReader().readInt();
        }
    }

    /** */
    private static class ObjectRaw implements Binarylizable {
        /** */
        private int val0;

        /** */
        private int val1;

        /**
         *
         */
        public ObjectRaw() {
        }

        /**
         * @param val0 Value.
         * @param val1 Raw value.
         */
        public ObjectRaw(int val0, int val1) {
            this.val0 = val0;
            this.val1 = val1;
        }

        /** {@inheritDoc} */
        @Override public void writeBinary(BinaryWriter writer) throws BinaryObjectException {
            writer.rawWriter().writeInt(val0);
            writer.rawWriter().writeInt(val1);
        }

        /** {@inheritDoc} */
        @Override public void readBinary(BinaryReader reader) throws BinaryObjectException {
            BinaryRawReader rawReader = reader.rawReader();

            val0 = rawReader.readInt();
            val1 = rawReader.readInt();
        }
    }

    /** */
    private static class EnumObject implements Serializable {
        /** */
        private long id;

        /** */
        private String name;

        /** */
        private DeclaredBodyEnum type;

        /** */
        EnumObject(final long id, final String name, final DeclaredBodyEnum type) {
            this.id = id;
            this.name = name;
            this.type = type;
        }
    }

    /** */
    public enum DeclaredBodyEnum {
        /** */
        ONE {
            /** {@inheritDoc} */
            @Override boolean isSupported() {
                return false;
            }
        },

        /** */
        TWO {
            /** {@inkheritDoc} */
            @Override boolean isSupported() {
                return false;
            }
        };

        /** */
        abstract boolean isSupported();
    }

    /** */
    interface Intf {
        /** */
        long value();
    }

    /** */
    static class TestObject {
        /** Value. */
        Intf val = new IntfImpl();

        /** {@inheritDoc} */
        @Override public boolean equals(Object o) {
            if (this == o)
                return true;
            if (o == null || getClass() != o.getClass())
                return false;

            TestObject obj = (TestObject)o;

            return val.equals(obj.val);
        }
    }

    /** */
    static class IntfImpl extends Cls implements Intf {
        /** {@inheritDoc} */
        @Override public long value() {
            return longValue();
        }
    }

    /** */
    static class Cls implements Serializable {
        /** Value. */
        long val;

        /** */
        public long longValue() {
            return val;
        }

        /** {@inheritDoc} */
        @Override public boolean equals(Object o) {
            if (this == o)
                return true;
            if (o == null || getClass() != o.getClass())
                return false;

            Cls cls = (Cls)o;

            return val == cls.val;
        }

        /** */
        private Object writeReplace() {
            return new SerializationProxy(this);
        }

        /** */
        private static class SerializationProxy implements Serializable {
            /** Value. */
            private final long val;

            /** */
            SerializationProxy(Cls a) {
                val = a.longValue();
            }

            /** */
            private Object readResolve() {
                Cls a = new Cls();

                a.val = val;

                return a;
            }
        }
    }

    /** */
    public static class HandleToCollections {
        /** */
        int a;

        /** */
        List<Value> lst;

        /** */
        List<Value> hndLst;

        /** */
        List<Value> linkedLst;

        /** */
        List<Value> hndLinkedLst;

        /** */
        Map<Integer, Value> map;

        /** */
        Map<Integer, Value> hndMap;

        /** */
        Map<Integer, Value> linkedMap;

        /** */
        Map<Integer, Value> hndLinkedMap;

        /** */
        public HandleToCollections() {
            lst = new ArrayList<>(Arrays.asList(new Value(0), new Value(1)));
            hndLst = lst;

            linkedLst = new LinkedList<>(Arrays.asList(new Value(0), new Value(1)));
            hndLinkedLst = linkedLst;

            map = IntStream.range(0, 1).boxed()
                .collect(Collectors.toMap(Function.identity(), Value::new));
            hndMap = map;

            linkedMap = IntStream.range(0, 1).boxed()
                .collect(Collectors.toMap(Function.identity(), Value::new, (a, b) -> a, LinkedHashMap::new));
            hndLinkedMap = linkedMap;
        }
    }

    /** */
    public static class TwoCollections {
        /** */
        int a;

        /** */
        List<String> lst0;

        /** */
        List<String> lst1;

        /** */
        Value v;

        /** */
        public TwoCollections() {
            a = 0;

            lst0 = new ArrayList<>(Arrays.asList("a", "b"));
            lst1 = new ArrayList<>(Arrays.asList("c", "d"));;

            v = new Value(127);
        }
    }
}
