/*******************************************************************************
 * Copyright 2010 Cees De Groot, Alex Boisvert, Jan Kotek
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/
package org.mapdb;

import org.junit.Test;

import java.io.*;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.AbstractMap.SimpleEntry;
import java.util.*;

import static org.junit.Assert.*;

@SuppressWarnings({ "unchecked", "rawtypes" })
public class SerializerBaseTest{


    @Test public void testInt() throws IOException{
        int[] vals = {
                Integer.MIN_VALUE,
                2*Short.MIN_VALUE,
                -1+Short.MIN_VALUE,
                256*Short.MIN_VALUE,
                Short.MIN_VALUE,
                -10, -9, -8, -7, -6, -5, -4, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
                127, 254, 255, 256, Short.MAX_VALUE, Short.MAX_VALUE + 1,
                Short.MAX_VALUE * 2, Integer.MAX_VALUE,256*Short.MIN_VALUE,
                0x80FFFFFF //Issue #202
        };
        for (Integer i : vals) {
            Object l2 = clone(i);
            assertEquals(i, l2);
            assertTrue(l2.getClass() == Integer.class);
        }
    }

    void serSize(int expected, Object val) throws IOException {
        DataOutput2 out = new DataOutput2();
        Serializer.BASIC.serialize(out,val);
        assertEquals(expected, out.pos);
    }

    @Test public void testIntSize() throws IOException {
        serSize(1,Integer.MIN_VALUE);
        serSize(1,Integer.MAX_VALUE);
        for(int i=-9;i<=16;i++)
            serSize(1,i);
        serSize(2, 100);
        serSize(2, -100);
        serSize(3, 0xFFF);
        serSize(3, -0xFFF);
        serSize(4, 0xFFFFF);
        serSize(4, -0xFFFFF);
        serSize(5, 0xFFFFFFF);
        serSize(5, -0xFFFFFFF);
    }

    @Test public void testShort() throws IOException{
        for (int i = Short.MIN_VALUE;i<=Short.MAX_VALUE;i++) {
            Short ii = (short)i;
            Object l2 = clone(ii);
            assertEquals(ii,l2);
            assertTrue(l2.getClass() == Short.class);
        }
    }

    @Test public void testDouble() throws IOException{
        double[] vals = {
                1f, 0f, -1f, Math.PI, 255, 256, Short.MAX_VALUE, Short.MAX_VALUE + 1, -100
        };
        for (double i : vals) {
            Object l2 = clone(i);
            assertTrue(l2.getClass() == Double.class);
            assertEquals(l2, i);
        }
    }


    @Test public void testFloat() throws IOException{
        float[] vals = {
                1f, 0f, -1f, (float) Math.PI, 255, 256, Short.MAX_VALUE, Short.MAX_VALUE + 1, -100
        };
        for (float i : vals) {
            Object l2 = clone(i);
            assertTrue(l2.getClass() == Float.class);
            assertEquals(l2, i);
        }
    }

    @Test public void testChar() throws IOException{
        for (int ii = Character.MIN_VALUE;ii<=Character.MAX_VALUE;ii++) {
            Character i = (char)ii;
            Object l2 = clone(i);
            assertEquals(l2.getClass(), Character.class);
            assertEquals(l2, i);
        }
    }


    @Test public void testLong() throws IOException{
        long[] vals = {
                65536,
                Long.MIN_VALUE,
                Integer.MIN_VALUE, (long)Integer.MIN_VALUE - 1, (long)Integer.MIN_VALUE + 1,
                2* Short.MIN_VALUE * 2,
                -1 + Short.MIN_VALUE,
                Short.MIN_VALUE,
                -10, -9, -8, -7, -6, -5, -4, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
                127, 254, 255, 256, Short.MAX_VALUE, Short.MAX_VALUE + 1,
                Short.MAX_VALUE * 2, Integer.MAX_VALUE, (long)Integer.MAX_VALUE + 1, Long.MAX_VALUE,
                0x80FFFFFFFFFFFFFFL, //Issue #202
                0x8000000000000000L,
                0x7F00000000000001L

        };
        for (long i : vals) {
            Object l2 = clone(i);
            assertTrue(l2.getClass() == Long.class);
            assertEquals(l2, i);
        }
    }

    @Test public void testLongSize() throws IOException {
        serSize(1,Long.MIN_VALUE);
        serSize(1,Long.MAX_VALUE);
        for(long i=-9;i<=16;i++)
            serSize(1,i);
        serSize(2, 100L);
        serSize(2, -100L);
        serSize(3, 0xFFFL);
        serSize(3, -0xFFFL);
        serSize(4, 0xFFFFFL);
        serSize(4, -0xFFFFFL);
        serSize(5, 0xFFFFFFFL);
        serSize(5, -0xFFFFFFFL);
        serSize(6, 0xFFFFFFFFFL);
        serSize(6, -0xFFFFFFFFFL);
        serSize(7, 0xFFFFFFFFFFFL);
        serSize(7, -0xFFFFFFFFFFFL);
        serSize(8, 0xFFFFFFFFFFFFFL);
        serSize(8, -0xFFFFFFFFFFFFFL);
        serSize(9, 0xFFFFFFFFFFFFFFFL);
        serSize(9, -0xFFFFFFFFFFFFFFFL);
    }

    @Test public void testBoolean1() throws IOException{
        Object l2 = clone(true);
        assertTrue(l2.getClass() == Boolean.class);
        assertEquals(l2, true);

        Object l22 = clone(false);
        assertTrue(l22.getClass() == Boolean.class);
        assertEquals(l22, false);

    }

    @Test public void testString() throws IOException{
        String l2 = (String) clone("Abcd");
        assertEquals(l2, "Abcd");
    }

    @Test public void testBigString() throws IOException{
        String bigString = "";
        for (int i = 0; i < 1e4; i++)
            bigString += i % 10;
        String l2 = clone(bigString);
        assertEquals(l2, bigString);
    }


    @Test public void testNoArgumentConstructorInJavaSerialization() throws ClassNotFoundException, IOException {
        SimpleEntry a = new SimpleEntry(1, "11");
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        new ObjectOutputStream(out).writeObject(a);
        ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(out.toByteArray()));
        SimpleEntry a2 = (SimpleEntry) in.readObject();
        assertEquals(a, a2);
    }


    @Test public void testArrayList() throws ClassNotFoundException, IOException {
        Collection c = new ArrayList();
        for (int i = 0; i < 200; i++)
            c.add(i);
        assertEquals(c, clone((c)));
        for (int i = 0; i < 2000; i++)
            c.add(i);
        assertEquals(c, clone((c)));
    }

    @Test public void testLinkedList() throws ClassNotFoundException, IOException {
        Collection c = new java.util.LinkedList();
        for (int i = 0; i < 200; i++)
            c.add(i);
        assertEquals(c, clone((c)));
        for (int i = 0; i < 2000; i++)
            c.add(i);
        assertEquals(c, clone((c)));
    }



    @Test public void testTreeSet() throws ClassNotFoundException, IOException {
        Collection c = new TreeSet();
        for (int i = 0; i < 200; i++)
            c.add(i);
        assertEquals(c, clone((c)));
        for (int i = 0; i < 2000; i++)
            c.add(i);
        assertEquals(c, clone((c)));
    }

    @Test public void testHashSet() throws ClassNotFoundException, IOException {
        Collection c = new HashSet();
        for (int i = 0; i < 200; i++)
            c.add(i);
        assertEquals(c, clone((c)));
        for (int i = 0; i < 2000; i++)
            c.add(i);
        assertEquals(c, clone((c)));
    }

    @Test public void testLinkedHashSet() throws ClassNotFoundException, IOException {
        Collection c = new LinkedHashSet();
        for (int i = 0; i < 200; i++)
            c.add(i);
        assertEquals(c, clone((c)));
        for (int i = 0; i < 2000; i++)
            c.add(i);
        assertEquals(c, clone((c)));
    }

    @Test public void testHashMap() throws ClassNotFoundException, IOException {
        Map c = new HashMap();
        for (int i = 0; i < 200; i++)
            c.put(i, i + 10000);
        assertEquals(c, clone((c)));
        for (int i = 0; i < 2000; i++)
            c.put(i, i + 10000);
        assertEquals(c, clone((c)));
    }

    @Test public void testTreeMap() throws ClassNotFoundException, IOException {
        Map c = new TreeMap();
        for (int i = 0; i < 200; i++)
            c.put(i, i + 10000);
        assertEquals(c, clone((c)));
        for (int i = 0; i < 2000; i++)
            c.put(i, i + 10000);
        assertEquals(c, clone((c)));
    }

    @Test public void testLinkedHashMap() throws ClassNotFoundException, IOException {
        Map c = new LinkedHashMap();
        for (int i = 0; i < 200; i++)
            c.put(i, i + 10000);
        assertEquals(c, clone((c)));
        for (int i = 0; i < 2000; i++)
            c.put(i, i + 10000);
        assertEquals(c, clone((c)));
    }


    @Test public void testProperties() throws ClassNotFoundException, IOException {
        Properties c = new Properties();
        for (int i = 0; i < 200; i++)
            c.put(i, i + 10000);
        assertEquals(c, clone((c)));
        for (int i = 0; i < 2000; i++)
            c.put(i, i + 10000);
        assertEquals(c, clone((c)));
    }


    @Test public void testClass() throws IOException{
        assertEquals(clone(String.class), String.class);
        assertEquals(clone(long[].class), long[].class);
    }


    @Test public void testUnicodeString() throws ClassNotFoundException, IOException {
        String s = "Ciudad Bolíva";
        assertEquals(clone(s), s);
    }

    @Test public void testPackedLongCollection() throws ClassNotFoundException, IOException {
        ArrayList l1 = new ArrayList();
        l1.add(0L);
        l1.add(1L);
        l1.add(0L);
        assertEquals(l1, clone((l1)));
        l1.add(-1L);
        assertEquals(l1, clone((l1)));
    }

    @Test public void testNegativeLongsArray() throws ClassNotFoundException, IOException {
       long[] l = new long[] { -12 };
       Object deserialize = clone((l));
       assertTrue(Arrays.equals(l, (long[]) deserialize));
     }


    @Test public void testNegativeIntArray() throws ClassNotFoundException, IOException {
       int[] l = new int[] { -12 };
       Object deserialize = clone((l));
       assertTrue(Arrays.equals(l, (int[]) deserialize));
     }


    @Test public void testNegativeShortArray() throws ClassNotFoundException, IOException {
       short[] l = new short[] { -12 };
       Object deserialize = clone((l));
        assertTrue(Arrays.equals(l, (short[]) deserialize));
     }

    @Test public void testBooleanArray() throws ClassNotFoundException, IOException {
        boolean[] l = new boolean[] { true,false };
        Object deserialize = clone((l));
        assertTrue(Arrays.equals(l, (boolean[]) deserialize));
    }

    @Test public void testDoubleArray() throws ClassNotFoundException, IOException {
        double[] l = new double[] { Math.PI, 1D };
        Object deserialize = clone((l));
        assertTrue(Arrays.equals(l, (double[]) deserialize));
    }

    @Test public void testFloatArray() throws ClassNotFoundException, IOException {
        float[] l = new float[] { 1F, 1.234235F };
        Object deserialize = clone((l));
        assertTrue(Arrays.equals(l, (float[]) deserialize));
    }

    @Test public void testByteArray() throws ClassNotFoundException, IOException {
        byte[] l = new byte[] { 1,34,-5 };
        Object deserialize = clone((l));
        assertTrue(Arrays.equals(l, (byte[]) deserialize));
    }

    @Test public void testCharArray() throws ClassNotFoundException, IOException {
        char[] l = new char[] { '1','a','&' };
        Object deserialize = clone((l));
        assertTrue(Arrays.equals(l, (char[]) deserialize));
    }


    @Test public void testDate() throws IOException{
        Date d = new Date(6546565565656L);
        assertEquals(d, clone((d)));
        d = new Date(System.currentTimeMillis());
        assertEquals(d, clone((d)));
    }

    @Test public void testBigDecimal() throws IOException{
        BigDecimal d = new BigDecimal("445656.7889889895165654423236");
        assertEquals(d, clone((d)));
        d = new BigDecimal("-53534534534534445656.7889889895165654423236");
        assertEquals(d, clone((d)));
    }

    @Test public void testBigInteger() throws IOException{
        BigInteger d = new BigInteger("4456567889889895165654423236");
        assertEquals(d, clone((d)));
        d = new BigInteger("-535345345345344456567889889895165654423236");
        assertEquals(d, clone((d)));
    }


    @Test public void testUUID() throws IOException, ClassNotFoundException {
        //try a bunch of UUIDs.
        for(int i = 0; i < 1000;i++)
        {
            UUID uuid = UUID.randomUUID();
            assertEquals(uuid, clone((uuid)));
        }
    }

    @Test public void testArray() throws IOException {
        Object[] o = new Object[]{"A",Long.valueOf(1),Long.valueOf(2),Long.valueOf(3), Long.valueOf(3)};
        Object[] o2 = (Object[]) clone(o);
        assertArrayEquals(o,o2);
    }


    @Test public void test_issue_38() throws IOException {
        String[] s = new String[5];
        String[] s2 = (String[]) clone(s);
        assertArrayEquals(s, s2);
        assertTrue(s2.toString().contains("[Ljava.lang.String"));
    }

    @Test public void test_multi_dim_array() throws IOException {
        int[][] arr = new int[][]{{11,22,44},{1,2,34}};
        int[][] arr2= (int[][]) clone(arr);
        assertArrayEquals(arr,arr2);
    }

    @Test public void test_multi_dim_large_array() throws IOException {
        int[][] arr1 = new int[3000][];
        double[][] arr2 = new double[3000][];
        for(int i=0;i<3000;i++){
            arr1[i]= new int[]{i,i+1};
            arr2[i]= new double[]{i,i+1};
        }
        assertArrayEquals(arr1, (Object[]) clone(arr1));
        assertArrayEquals(arr2, (Object[]) clone(arr2));
    }


    @Test public void test_multi_dim_array2() throws IOException {
        Object[][] arr = new Object[][]{{11,22,44},{1,2,34}};
        Object[][] arr2= (Object[][]) clone(arr);
        assertArrayEquals(arr,arr2);
    }


    @Test public void test_static_objects() throws IOException {
        for(Object o:SerializerBase.singletons.all.keySet()){
            assertTrue(o==clone(o));
        }
    }

    @Test public void test_singleton_reverse() throws IOException {
        assertEquals(SerializerBase.singletons.all.size(), SerializerBase.singletons.reverse.size());
    }


    @Test public void test_tuple_key_serializer() throws IOException {
        assertEquals(BTreeKeySerializer.TUPLE2, clone(BTreeKeySerializer.TUPLE2));
        assertEquals(BTreeKeySerializer.TUPLE3, clone(BTreeKeySerializer.TUPLE3));
        assertEquals(BTreeKeySerializer.TUPLE4, clone(BTreeKeySerializer.TUPLE4));
    }





    @Test public void test_strings_var_sizes() throws IOException {
        for(int i=0;i<50;i++){
            String s = UtilsTest.randomString(i);
            assertEquals(s, clone((s)));
        }
    }


    @Test public void test_extended_chars() throws IOException {
        String s = "人口, 日本、人口, 日本の公式統計";
        assertEquals(s,clone((s)));
    }

    @Test public void testBooleanArray2() throws IOException {
        for(int i=0;i<1000;i++){
            boolean[] b = new boolean[i];
            for(int j=0;j<i;j++) b[j] = Math.random()<0.5;

            boolean[] b2 = (boolean[]) clone((b));

            for(int j=0;j<i;j++) assertEquals(b[j], b2[j]);
        }
    }

    /** clone value using serialization */
    <E> E clone(E value) throws IOException {
        return clone2(value,(Serializer<E>)Serializer.BASIC);
    }

    /** clone value using serialization */
    public static <E> E clone2(E value, Serializer<E> serializer) {
        try{
            DataOutput2 out = new DataOutput2();
            serializer.serialize(out, value);
            DataInput2 in = new DataInput2(ByteBuffer.wrap(out.copyBytes()), 0);

            return serializer.deserialize(in,out.pos);
        }catch(IOException ee){
            throw new IOError(ee);
        }
    }

    public static class SerializerBaseTestWithJUDataStreams extends SerializerBaseTest{
        @Override
        <E> E clone(E value) throws IOException {
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            Serializer.BASIC.serialize(new DataOutputStream(out), value);

            return (E) Serializer.BASIC.deserialize(new DataInputStream(new ByteArrayInputStream(out.toByteArray())),-1);
        }
    }

    @SuppressWarnings({  "rawtypes" })
    @Test public void testHeaderUnique() throws IllegalAccessException {
        Class c = SerializerBase.Header.class;
        Set<Integer> s = new TreeSet<Integer>();
        for (Field f : c.getDeclaredFields()) {
            f.setAccessible(true);
            int value = f.getInt(null);

            assertTrue("Value already used: " + value, !s.contains(value));
            s.add(value);
        }
        assertTrue(!s.isEmpty());
    }

    @SuppressWarnings({  "rawtypes" })
    @Test public void testHeaderUniqueMapDB() throws IllegalAccessException {
        Class c = SerializerBase.HeaderMapDB.class;
        Set<Integer> s = new TreeSet<Integer>();
        for (Field f : c.getDeclaredFields()) {
            f.setAccessible(true);
            int value = f.getInt(null);

            assertTrue("Value already used: " + value, !s.contains(value));
            s.add(value);
        }
        assertTrue(!s.isEmpty());
    }


    @Test public void test_All_Serializer_Fields_Serializable() throws IllegalAccessException, IOException {
        for(Field f:Serializer.class.getDeclaredFields()){
            Object a = f.get(null);
            assertTrue("field: "+f.getName(), SerializerBase.singletons.all.containsKey(a));
            assertTrue("field: "+f.getName(),a == clone(a));
        }
    }

    @Test public void test_All_Hasher_Fields_Serializable() throws IllegalAccessException, IOException {
        for(Field f:Hasher.class.getDeclaredFields()){
            Object a = f.get(null);
            assertTrue("field: "+f.getName(), SerializerBase.singletons.all.containsKey(a));
            assertTrue("field: "+f.getName(),a == clone(a));
        }
    }


    @Test public void test_All_Fun_Fields_Serializable() throws IllegalAccessException, IOException {
        for(Field f:Fun.class.getDeclaredFields()){
            Object a = f.get(null);
            assertTrue("field: "+f.getName(), SerializerBase.singletons.all.containsKey(a));
            assertTrue("field: "+f.getName(),a == clone(a));
        }
    }

    @Test public void test_Named(){
        File f = UtilsTest.tempDbFile();
        DB db = DBMaker.newFileDB(f).make();
        Map map = db.getTreeMap("map");

        Map map2 = db.getTreeMap("map2");
        map2.put("some","stuff");
        map.put("map2_",map2);

        Queue<Object> stack = db.getStack("stack");
        stack.add("stack");
        map.put("stack_",stack);

        Atomic.Long along = db.getAtomicLong("along");
        along.set(111L);
        map.put("along_",along);

        db.commit();
        db.close();

        db = DBMaker.newFileDB(f).deleteFilesAfterClose().make();
        map = db.getTreeMap("map");

        map2 = (Map) map.get("map2_");
        assertEquals(map2.get("some"),"stuff");

        stack = (Queue<Object>) map.get("stack_");
        assertEquals("stack",stack.poll());

        along = (Atomic.Long) map.get("along_");
        assertEquals(111L,along.get());
    }

    @Test public void test_atomic_ref_serializable(){
        File f = UtilsTest.tempDbFile();
        DB db = DBMaker.newFileDB(f).make();
        Map map = db.getTreeMap("map");

        long recid = db.getEngine().put(11L, Serializer.LONG);
        Atomic.Long l = new Atomic.Long(db.getEngine(),recid);
        map.put("long",l);

        recid = db.getEngine().put(11, Serializer.INTEGER);
        Atomic.Integer i = new Atomic.Integer(db.getEngine(),recid);
        map.put("int",i);

        recid = db.getEngine().put(true, Serializer.BOOLEAN);
        Atomic.Boolean b = new Atomic.Boolean(db.getEngine(),recid);
        map.put("bool",b);

        recid = db.getEngine().put("aa", Serializer.STRING_NOSIZE);
        Atomic.String s = new Atomic.String(db.getEngine(),recid);
        map.put("str",s);

        recid = db.getEngine().put("hovnocuc", db.getDefaultSerializer());
        Atomic.Var v = new Atomic.Var(db.getEngine(),recid,db.getDefaultSerializer());
        map.put("var",v);

        db.commit();
        db.close();
        db = DBMaker.newFileDB(f).deleteFilesAfterClose().make();
        map = db.getTreeMap("map");

        l = (Atomic.Long) map.get("long");
        assertEquals(11L, l.get());

        i = (Atomic.Integer) map.get("int");
        assertEquals(11, i.get());

        b = (Atomic.Boolean) map.get("bool");
        assertEquals(true, b.get());

        s = (Atomic.String) map.get("str");
        assertEquals("aa", s.get());

        v = (Atomic.Var) map.get("var");
        assertEquals("hovnocuc", v.get());
        assertEquals(db.getDefaultSerializer(), v.serializer);
    }

    @Test
    public void tuple2_comparators() throws IOException {
        Object a = new Fun.Tuple2Comparator(Fun.REVERSE_COMPARATOR,null);
        assertEquals(a,clone(a));
        a = new Fun.Tuple2Comparator(null,Fun.REVERSE_COMPARATOR);
        assertEquals(a,clone(a));
        a = new Fun.Tuple2Comparator(Fun.REVERSE_COMPARATOR,Fun.REVERSE_COMPARATOR);
        assertEquals(a,clone(a));
    }

    @Test
    public void tuple3_comparators() throws IOException {
        Object a = new Fun.Tuple3Comparator(Fun.REVERSE_COMPARATOR,null,null);
        assertEquals(a,clone(a));
        a = new Fun.Tuple3Comparator(null,Fun.REVERSE_COMPARATOR,null);
        assertEquals(a,clone(a));
        a = new Fun.Tuple3Comparator(null,null,Fun.REVERSE_COMPARATOR);
        assertEquals(a,clone(a));
        a = new Fun.Tuple3Comparator(Fun.REVERSE_COMPARATOR,Fun.REVERSE_COMPARATOR,Fun.REVERSE_COMPARATOR);
        assertEquals(a,clone(a));
    }

    @Test
    public void tuple4_comparators() throws IOException {
        Object a = new Fun.Tuple4Comparator(Fun.REVERSE_COMPARATOR,null,null,null);
        assertEquals(a,clone(a));
        a = new Fun.Tuple4Comparator(null,Fun.REVERSE_COMPARATOR,null,null);
        assertEquals(a,clone(a));
        a = new Fun.Tuple4Comparator(null,null,Fun.REVERSE_COMPARATOR,null);
        assertEquals(a,clone(a));
        a = new Fun.Tuple4Comparator(null,null,null,Fun.REVERSE_COMPARATOR);
        assertEquals(a,clone(a));
        a = new Fun.Tuple4Comparator(Fun.REVERSE_COMPARATOR,Fun.REVERSE_COMPARATOR,Fun.REVERSE_COMPARATOR,Fun.REVERSE_COMPARATOR);
        assertEquals(a,clone(a));
    }

    @Test
    public void tuple5_comparators() throws IOException {
        Object a = new Fun.Tuple5Comparator(Fun.REVERSE_COMPARATOR,null,null,null,null);
        assertEquals(a,clone(a));
        a = new Fun.Tuple5Comparator(null,Fun.REVERSE_COMPARATOR,null,null,null);
        assertEquals(a,clone(a));
        a = new Fun.Tuple5Comparator(null,null,Fun.REVERSE_COMPARATOR,null,null);
        assertEquals(a,clone(a));
        a = new Fun.Tuple5Comparator(null,null,null,Fun.REVERSE_COMPARATOR,null);
        assertEquals(a,clone(a));
        a = new Fun.Tuple5Comparator(null,null,null,null,Fun.REVERSE_COMPARATOR);
        assertEquals(a,clone(a));
        a = new Fun.Tuple5Comparator(Fun.REVERSE_COMPARATOR,Fun.REVERSE_COMPARATOR,Fun.REVERSE_COMPARATOR,Fun.REVERSE_COMPARATOR,Fun.REVERSE_COMPARATOR);
        assertEquals(a,clone(a));
    }

    @Test
    public void tuple6_comparators() throws IOException {
        Object a = new Fun.Tuple6Comparator(Fun.REVERSE_COMPARATOR,null,null,null,null,null);
        assertEquals(a,clone(a));
        a = new Fun.Tuple6Comparator(null,Fun.REVERSE_COMPARATOR,null,null,null,null);
        assertEquals(a,clone(a));
        a = new Fun.Tuple6Comparator(null,null,Fun.REVERSE_COMPARATOR,null,null,null);
        assertEquals(a,clone(a));
        a = new Fun.Tuple6Comparator(null,null,null,Fun.REVERSE_COMPARATOR,null,null);
        assertEquals(a,clone(a));
        a = new Fun.Tuple6Comparator(null,null,null,null,Fun.REVERSE_COMPARATOR,null);
        assertEquals(a,clone(a));
        a = new Fun.Tuple6Comparator(null,null,null,null,null,Fun.REVERSE_COMPARATOR);
        assertEquals(a,clone(a));
        a = new Fun.Tuple6Comparator(Fun.REVERSE_COMPARATOR,Fun.REVERSE_COMPARATOR,Fun.REVERSE_COMPARATOR,Fun.REVERSE_COMPARATOR,Fun.REVERSE_COMPARATOR,Fun.REVERSE_COMPARATOR);
        assertEquals(a,clone(a));
    }

    @Test public void array_comparator() throws IOException {
        Fun.ArrayComparator c = new Fun.ArrayComparator(new Comparator[]{Fun.REVERSE_COMPARATOR, Fun.COMPARATOR, Fun.COMPARATOR});
        assertEquals(c,clone(c));
    }



    @Test public void object_stack_issue232_n2() throws IOException {
        Integer i = 1;
        Fun.Tuple2 t = Fun.t2(i,i);
        assertEquals(t,clone(t));
    }

    Long one = 10000L;
    Long two = 20000L;

    @Test public void object_stack_array() throws IOException {
        Object[] c = new Object[4];
        c[0]=c;
        c[1]=one;
        c[2]=two;
        c[3]=one;
        c = clone(c);
        assertTrue(c==c[0]);
        assertEquals(one, c[1]);
        assertEquals(two, c[2]);
        assertEquals(one, c[3]);
        assertTrue(c[1]==c[3]);
    }

    @Test public void object_stack_list() throws IOException {
        for(List c : Arrays.asList(new ArrayList(), new LinkedList())){
            c.add(c);
            c.add(one);
            c.add(two);
            c.add(one);
            c = clone(c);
            assertTrue(c==c.get(0));
            assertEquals(one, c.get(1));
            assertEquals(two, c.get(2));
            assertEquals(one, c.get(3));
            assertTrue(c.get(1)==c.get(3));
        }
    }

    @Test public void object_stack_set() throws IOException {
        for(Set c : Arrays.asList(new HashSet(), new LinkedHashSet())){
            c.add(c);
            c = clone(c);
            assertTrue(c.iterator().next()==c);
        }
    }


    @Test public void object_stack_map() throws IOException {
        for(Map c : Arrays.asList(new HashMap(), new LinkedHashMap(), new TreeMap(), new Properties())){
            c.put(one,c);
            c.put(two,one);
            c = clone(c);
            assertTrue(c.get(one)==c);
            assertEquals(one,c.get(two));
            Iterator i = c.keySet().iterator();
            Object one_ = i.next();
            if(one_!=c.get(two))
                one_ = i.next();
            assertTrue(one_==c.get(two));
        }
    }

    @Test public void serializer_compression_wrapper() throws IOException {
        Object o = new Serializer.CompressionWrapper(Serializer.LONG);
        assertEquals(o, clone(o));
    }

}