/**
 * 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.hadoop.hive.serde2.dynamic_type;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Random;
import java.util.Map.Entry;

import junit.framework.TestCase;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hive.serde.serdeConstants;
import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector;
import org.apache.hadoop.hive.serde2.thrift.TCTLSeparatedProtocol;
import org.apache.hadoop.io.BytesWritable;

/**
 * TestDynamicSerDe.
 *
 */
public class TestDynamicSerDe extends TestCase {

  public static HashMap<String, String> makeHashMap(String... params) {
    HashMap<String, String> r = new HashMap<String, String>();
    for (int i = 0; i < params.length; i += 2) {
      r.put(params[i], params[i + 1]);
    }
    return r;
  }

  public void testDynamicSerDe() throws Throwable {
    try {

      // Try to construct an object
      ArrayList<String> bye = new ArrayList<String>();
      bye.add("firstString");
      bye.add("secondString");
      HashMap<String, Integer> another = new HashMap<String, Integer>();
      another.put("firstKey", 1);
      another.put("secondKey", 2);
      ArrayList<Object> struct = new ArrayList<Object>();
      struct.add(Integer.valueOf(234));
      struct.add(bye);
      struct.add(another);
      struct.add(Integer.valueOf(-234));
      struct.add(Double.valueOf(1.0));
      struct.add(Double.valueOf(-2.5));

      // All protocols
      ArrayList<String> protocols = new ArrayList<String>();
      ArrayList<Boolean> isBinaries = new ArrayList<Boolean>();
      ArrayList<HashMap<String, String>> additionalParams = new ArrayList<HashMap<String, String>>();

      protocols
          .add(org.apache.hadoop.hive.serde2.thrift.TBinarySortableProtocol.class
          .getName());
      isBinaries.add(true);
      additionalParams.add(makeHashMap("serialization.sort.order", "++++++"));
      protocols
          .add(org.apache.hadoop.hive.serde2.thrift.TBinarySortableProtocol.class
          .getName());
      isBinaries.add(true);
      additionalParams.add(makeHashMap("serialization.sort.order", "------"));

      protocols.add(org.apache.thrift.protocol.TBinaryProtocol.class.getName());
      isBinaries.add(true);
      additionalParams.add(null);

      protocols.add(org.apache.thrift.protocol.TJSONProtocol.class.getName());
      isBinaries.add(false);
      additionalParams.add(null);

      // TSimpleJSONProtocol does not support deserialization.
      // protocols.add(org.apache.thrift.protocol.TSimpleJSONProtocol.class.getName());
      // isBinaries.add(false);
      // additionalParams.add(null);

      // TCTLSeparatedProtocol is not done yet.
      protocols
          .add(org.apache.hadoop.hive.serde2.thrift.TCTLSeparatedProtocol.class
          .getName());
      isBinaries.add(false);
      additionalParams.add(null);

      System.out.println("input struct = " + struct);

      for (int pp = 0; pp < protocols.size(); pp++) {

        String protocol = protocols.get(pp);
        boolean isBinary = isBinaries.get(pp);

        System.out.println("Testing protocol: " + protocol);
        Properties schema = new Properties();
        schema.setProperty(serdeConstants.SERIALIZATION_FORMAT, protocol);
        schema.setProperty(
            org.apache.hadoop.hive.metastore.api.hive_metastoreConstants.META_TABLE_NAME,
            "test");
        schema
            .setProperty(
                serdeConstants.SERIALIZATION_DDL,
                "struct test { i32 _hello, list<string> 2bye, map<string,i32> another, i32 nhello, double d, double nd}");
        schema.setProperty(serdeConstants.SERIALIZATION_LIB, new DynamicSerDe()
            .getClass().toString());
        HashMap<String, String> p = additionalParams.get(pp);
        if (p != null) {
          for (Entry<String, String> e : p.entrySet()) {
            schema.setProperty(e.getKey(), e.getValue());
          }
        }

        DynamicSerDe serde = new DynamicSerDe();
        serde.initialize(new Configuration(), schema);

        // Try getObjectInspector
        ObjectInspector oi = serde.getObjectInspector();
        System.out.println("TypeName = " + oi.getTypeName());

        // Try to serialize
        BytesWritable bytes = (BytesWritable) serde.serialize(struct, oi);
        System.out.println("bytes =" + hexString(bytes));

        if (!isBinary) {
          System.out.println("bytes in text ="
              + new String(bytes.get(), 0, bytes.getSize()));
        }

        // Try to deserialize
        Object o = serde.deserialize(bytes);
        System.out.println("o class = " + o.getClass());
        List<?> olist = (List<?>) o;
        System.out.println("o size = " + olist.size());
        System.out.println("o[0] class = " + olist.get(0).getClass());
        System.out.println("o[1] class = " + olist.get(1).getClass());
        System.out.println("o[2] class = " + olist.get(2).getClass());
        System.out.println("o = " + o);

        assertEquals(struct, o);
      }

    } catch (Throwable e) {
      e.printStackTrace();
      throw e;
    }

  }

  public String hexString(BytesWritable bytes) {
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < bytes.getSize(); i++) {
      byte b = bytes.get()[i];
      int v = (b < 0 ? 256 + b : b);
      sb.append(String.format("x%02x", v));
    }
    return sb.toString();
  }

  private void testTBinarySortableProtocol(Object[] structs, String ddl,
      boolean ascending) throws Throwable {
    int fields = ((List) structs[structs.length - 1]).size();
    String order = "";
    for (int i = 0; i < fields; i++) {
      order = order + (ascending ? "+" : "-");
    }

    Properties schema = new Properties();
    schema.setProperty(serdeConstants.SERIALIZATION_FORMAT,
        org.apache.hadoop.hive.serde2.thrift.TBinarySortableProtocol.class
        .getName());
    schema.setProperty(
        org.apache.hadoop.hive.metastore.api.hive_metastoreConstants.META_TABLE_NAME, "test");
    schema.setProperty(serdeConstants.SERIALIZATION_DDL, ddl);
    schema.setProperty(serdeConstants.SERIALIZATION_LIB, DynamicSerDe.class
        .getName());
    schema.setProperty(serdeConstants.SERIALIZATION_SORT_ORDER, order);

    DynamicSerDe serde = new DynamicSerDe();
    serde.initialize(new Configuration(), schema);

    ObjectInspector oi = serde.getObjectInspector();

    // Try to serialize
    BytesWritable bytes[] = new BytesWritable[structs.length];
    for (int i = 0; i < structs.length; i++) {
      bytes[i] = new BytesWritable();
      BytesWritable s = (BytesWritable) serde.serialize(structs[i], oi);
      bytes[i].set(s);
      if (i > 0) {
        int compareResult = bytes[i - 1].compareTo(bytes[i]);
        if ((compareResult < 0 && !ascending)
            || (compareResult > 0 && ascending)) {
          System.out.println("Test failed in "
              + (ascending ? "ascending" : "descending") + " order.");
          System.out.println("serialized data of " + structs[i - 1] + " = "
              + hexString(bytes[i - 1]));
          System.out.println("serialized data of " + structs[i] + " = "
              + hexString(bytes[i]));
          fail("Sort order of serialized " + structs[i - 1] + " and "
              + structs[i] + " are reversed!");
        }
      }
    }

    // Try to deserialize
    Object[] deserialized = new Object[structs.length];
    for (int i = 0; i < structs.length; i++) {
      deserialized[i] = serde.deserialize(bytes[i]);
      if (!structs[i].equals(deserialized[i])) {
        System.out.println("structs[i] = " + structs[i]);
        System.out.println("deserialized[i] = " + deserialized[i]);
        System.out.println("serialized[i] = " + hexString(bytes[i]));
        assertEquals(structs[i], deserialized[i]);
      }
    }
  }

  static int compare(Object a, Object b) {
    if (a == null && b == null) {
      return 0;
    }
    if (a == null) {
      return -1;
    }
    if (b == null) {
      return 1;
    }
    if (a instanceof List) {
      List la = (List) a;
      List lb = (List) b;
      assert (la.size() == lb.size());
      for (int i = 0; i < la.size(); i++) {
        int r = compare(la.get(i), lb.get(i));
        if (r != 0) {
          return r;
        }
      }
      return 0;
    } else if (a instanceof Number) {
      Number na = (Number) a;
      Number nb = (Number) b;
      if (na.doubleValue() < nb.doubleValue()) {
        return -1;
      }
      if (na.doubleValue() > nb.doubleValue()) {
        return 1;
      }
      return 0;
    } else if (a instanceof String) {
      String sa = (String) a;
      String sb = (String) b;
      return sa.compareTo(sb);
    }
    return 0;
  }

  private void sort(Object[] structs) {
    for (int i = 0; i < structs.length; i++) {
      for (int j = i + 1; j < structs.length; j++) {
        if (compare(structs[i], structs[j]) > 0) {
          Object t = structs[i];
          structs[i] = structs[j];
          structs[j] = t;
        }
      }
    }
  }

  public void testTBinarySortableProtocol() throws Throwable {
    try {

      System.out.println("Beginning Test testTBinarySortableProtocol:");

      int num = 100;
      Random r = new Random(1234);
      Object structs[] = new Object[num];
      String ddl;

      // Test double
      for (int i = 0; i < num; i++) {
        ArrayList<Object> struct = new ArrayList<Object>();
        if (i == 0) {
          struct.add(null);
        } else {
          struct.add(Double.valueOf((r.nextDouble() - 0.5) * 10));
        }
        structs[i] = struct;
      }
      sort(structs);
      ddl = "struct test { double hello}";
      System.out.println("Testing " + ddl);
      testTBinarySortableProtocol(structs, ddl, true);
      testTBinarySortableProtocol(structs, ddl, false);

      // Test integer
      for (int i = 0; i < num; i++) {
        ArrayList<Object> struct = new ArrayList<Object>();
        if (i == 0) {
          struct.add(null);
        } else {
          struct.add((int) ((r.nextDouble() - 0.5) * 1.5 * Integer.MAX_VALUE));
        }
        structs[i] = struct;
      }
      sort(structs);
      // Null should be smaller than any other value, so put a null at the front
      // end
      // to test whether that is held.
      ((List) structs[0]).set(0, null);
      ddl = "struct test { i32 hello}";
      System.out.println("Testing " + ddl);
      testTBinarySortableProtocol(structs, ddl, true);
      testTBinarySortableProtocol(structs, ddl, false);

      // Test long
      for (int i = 0; i < num; i++) {
        ArrayList<Object> struct = new ArrayList<Object>();
        if (i == 0) {
          struct.add(null);
        } else {
          struct.add((long) ((r.nextDouble() - 0.5) * 1.5 * Long.MAX_VALUE));
        }
        structs[i] = struct;
      }
      sort(structs);
      // Null should be smaller than any other value, so put a null at the front
      // end
      // to test whether that is held.
      ((List) structs[0]).set(0, null);
      ddl = "struct test { i64 hello}";
      System.out.println("Testing " + ddl);
      testTBinarySortableProtocol(structs, ddl, true);
      testTBinarySortableProtocol(structs, ddl, false);

      // Test string
      for (int i = 0; i < num; i++) {
        ArrayList<Object> struct = new ArrayList<Object>();
        if (i == 0) {
          struct.add(null);
        } else {
          struct.add(String.valueOf((r.nextDouble() - 0.5) * 1000));
        }
        structs[i] = struct;
      }
      sort(structs);
      // Null should be smaller than any other value, so put a null at the front
      // end
      // to test whether that is held.
      ((List) structs[0]).set(0, null);
      ddl = "struct test { string hello}";
      System.out.println("Testing " + ddl);
      testTBinarySortableProtocol(structs, ddl, true);
      testTBinarySortableProtocol(structs, ddl, false);

      // Test string + double
      for (int i = 0; i < num; i++) {
        ArrayList<Object> struct = new ArrayList<Object>();
        if (i % 9 == 0) {
          struct.add(null);
        } else {
          struct.add("str" + (i / 5));
        }
        if (i % 7 == 0) {
          struct.add(null);
        } else {
          struct.add(Double.valueOf((r.nextDouble() - 0.5) * 10));
        }
        structs[i] = struct;
      }
      sort(structs);
      // Null should be smaller than any other value, so put a null at the front
      // end
      // to test whether that is held.
      ((List) structs[0]).set(0, null);
      ddl = "struct test { string hello, double another}";
      System.out.println("Testing " + ddl);
      testTBinarySortableProtocol(structs, ddl, true);
      testTBinarySortableProtocol(structs, ddl, false);

      System.out.println("Test testTBinarySortableProtocol passed!");
    } catch (Throwable e) {
      e.printStackTrace();
      throw e;
    }
  }

  public void testConfigurableTCTLSeparated() throws Throwable {
    try {

      // Try to construct an object
      ArrayList<String> bye = new ArrayList<String>();
      bye.add("firstString");
      bye.add("secondString");
      LinkedHashMap<String, Integer> another = new LinkedHashMap<String, Integer>();
      another.put("firstKey", 1);
      another.put("secondKey", 2);
      ArrayList<Object> struct = new ArrayList<Object>();
      struct.add(Integer.valueOf(234));
      struct.add(bye);
      struct.add(another);

      Properties schema = new Properties();
      schema.setProperty(serdeConstants.SERIALIZATION_FORMAT,
          org.apache.hadoop.hive.serde2.thrift.TCTLSeparatedProtocol.class
          .getName());
      schema.setProperty(
          org.apache.hadoop.hive.metastore.api.hive_metastoreConstants.META_TABLE_NAME,
          "test");
      schema
          .setProperty(serdeConstants.SERIALIZATION_DDL,
          "struct test { i32 hello, list<string> bye, map<string,i32> another}");
      schema.setProperty(serdeConstants.SERIALIZATION_LIB, new DynamicSerDe()
          .getClass().toString());

      schema.setProperty(serdeConstants.FIELD_DELIM, "9");
      schema.setProperty(serdeConstants.COLLECTION_DELIM, "1");
      schema.setProperty(serdeConstants.LINE_DELIM, "2");
      schema.setProperty(serdeConstants.MAPKEY_DELIM, "4");

      DynamicSerDe serde = new DynamicSerDe();
      serde.initialize(new Configuration(), schema);

      TCTLSeparatedProtocol prot = (TCTLSeparatedProtocol) serde.oprot_;
      assertTrue(prot.getPrimarySeparator().equals("\u0009"));

      ObjectInspector oi = serde.getObjectInspector();

      // Try to serialize
      BytesWritable bytes = (BytesWritable) serde.serialize(struct, oi);

      hexString(bytes);

      String compare = "234" + "\u0009" + "firstString" + "\u0001"
          + "secondString" + "\u0009" + "firstKey" + "\u0004" + "1" + "\u0001"
          + "secondKey" + "\u0004" + "2";

      System.out.println("bytes in text ="
          + new String(bytes.get(), 0, bytes.getSize()) + ">");
      System.out.println("compare to    =" + compare + ">");

      assertTrue(compare.equals(new String(bytes.get(), 0, bytes.getSize())));

      // Try to deserialize
      Object o = serde.deserialize(bytes);
      System.out.println("o class = " + o.getClass());
      List<?> olist = (List<?>) o;
      System.out.println("o size = " + olist.size());
      System.out.println("o[0] class = " + olist.get(0).getClass());
      System.out.println("o[1] class = " + olist.get(1).getClass());
      System.out.println("o[2] class = " + olist.get(2).getClass());
      System.out.println("o = " + o);

      assertEquals(o, struct);

    } catch (Throwable e) {
      e.printStackTrace();
      throw e;
    }

  }

  /**
   * Tests a single null list within a struct with return nulls on.
   */

  public void testNulls1() throws Throwable {
    try {

      // Try to construct an object
      ArrayList<String> bye = null;
      HashMap<String, Integer> another = new HashMap<String, Integer>();
      another.put("firstKey", 1);
      another.put("secondKey", 2);
      ArrayList<Object> struct = new ArrayList<Object>();
      struct.add(Integer.valueOf(234));
      struct.add(bye);
      struct.add(another);

      Properties schema = new Properties();
      schema.setProperty(serdeConstants.SERIALIZATION_FORMAT,
          org.apache.hadoop.hive.serde2.thrift.TCTLSeparatedProtocol.class
          .getName());
      schema.setProperty(
          org.apache.hadoop.hive.metastore.api.hive_metastoreConstants.META_TABLE_NAME,
          "test");
      schema
          .setProperty(serdeConstants.SERIALIZATION_DDL,
          "struct test { i32 hello, list<string> bye, map<string,i32> another}");
      schema.setProperty(serdeConstants.SERIALIZATION_LIB, new DynamicSerDe()
          .getClass().toString());
      schema.setProperty(TCTLSeparatedProtocol.ReturnNullsKey, "true");

      DynamicSerDe serde = new DynamicSerDe();
      serde.initialize(new Configuration(), schema);

      ObjectInspector oi = serde.getObjectInspector();

      // Try to serialize
      BytesWritable bytes = (BytesWritable) serde.serialize(struct, oi);

      hexString(bytes);

      // Try to deserialize
      Object o = serde.deserialize(bytes);
      assertEquals(struct, o);

    } catch (Throwable e) {
      e.printStackTrace();
      throw e;
    }

  }

  /**
   * Tests all elements of a struct being null with return nulls on.
   */

  public void testNulls2() throws Throwable {
    try {

      // Try to construct an object
      ArrayList<String> bye = null;
      HashMap<String, Integer> another = null;
      ArrayList<Object> struct = new ArrayList<Object>();
      struct.add(null);
      struct.add(bye);
      struct.add(another);

      Properties schema = new Properties();
      schema.setProperty(serdeConstants.SERIALIZATION_FORMAT,
          org.apache.hadoop.hive.serde2.thrift.TCTLSeparatedProtocol.class
          .getName());
      schema.setProperty(
          org.apache.hadoop.hive.metastore.api.hive_metastoreConstants.META_TABLE_NAME,
          "test");
      schema
          .setProperty(serdeConstants.SERIALIZATION_DDL,
          "struct test { i32 hello, list<string> bye, map<string,i32> another}");
      schema.setProperty(serdeConstants.SERIALIZATION_LIB, new DynamicSerDe()
          .getClass().toString());
      schema.setProperty(TCTLSeparatedProtocol.ReturnNullsKey, "true");

      DynamicSerDe serde = new DynamicSerDe();
      serde.initialize(new Configuration(), schema);

      ObjectInspector oi = serde.getObjectInspector();

      // Try to serialize
      BytesWritable bytes = (BytesWritable) serde.serialize(struct, oi);

      hexString(bytes);

      // Try to deserialize
      Object o = serde.deserialize(bytes);
      List<?> olist = (List<?>) o;

      assertTrue(olist.size() == 3);
      assertEquals(null, olist.get(0));
      assertEquals(null, olist.get(1));
      assertEquals(null, olist.get(2));

      // assertEquals(o, struct); Cannot do this because types of null lists are
      // wrong.

    } catch (Throwable e) {
      e.printStackTrace();
      throw e;
    }

  }

  /**
   * Tests map and list being empty with return nulls on.
   */

  public void testNulls3() throws Throwable {
    try {

      // Try to construct an object
      ArrayList<String> bye = new ArrayList<String>();
      HashMap<String, Integer> another = null;
      ArrayList<Object> struct = new ArrayList<Object>();
      struct.add(null);
      struct.add(bye);
      struct.add(another);

      Properties schema = new Properties();
      schema.setProperty(serdeConstants.SERIALIZATION_FORMAT,
          org.apache.hadoop.hive.serde2.thrift.TCTLSeparatedProtocol.class
          .getName());
      schema.setProperty(
          org.apache.hadoop.hive.metastore.api.hive_metastoreConstants.META_TABLE_NAME,
          "test");
      schema
          .setProperty(serdeConstants.SERIALIZATION_DDL,
          "struct test { i32 hello, list<string> bye, map<string,i32> another}");
      schema.setProperty(serdeConstants.SERIALIZATION_LIB, new DynamicSerDe()
          .getClass().toString());

      schema.setProperty(TCTLSeparatedProtocol.ReturnNullsKey, "true");
      DynamicSerDe serde = new DynamicSerDe();
      serde.initialize(new Configuration(), schema);

      ObjectInspector oi = serde.getObjectInspector();

      // Try to serialize
      BytesWritable bytes = (BytesWritable) serde.serialize(struct, oi);

      hexString(bytes);

      // Try to deserialize
      Object o = serde.deserialize(bytes);
      List<?> olist = (List<?>) o;

      assertTrue(olist.size() == 3);
      assertEquals(null, olist.get(0));
      assertEquals(0, ((List<?>) olist.get(1)).size());
      assertEquals(null, olist.get(2));

      // assertEquals(o, struct); Cannot do this because types of null lists are
      // wrong.

    } catch (Throwable e) {
      e.printStackTrace();
      throw e;
    }

  }

  /**
   * Tests map and list null/empty with return nulls *off*.
   */

  public void testNulls4() throws Throwable {
    try {

      // Try to construct an object
      ArrayList<String> bye = new ArrayList<String>();
      HashMap<String, Integer> another = null;
      ArrayList<Object> struct = new ArrayList<Object>();
      struct.add(null);
      struct.add(bye);
      struct.add(another);

      Properties schema = new Properties();
      schema.setProperty(serdeConstants.SERIALIZATION_FORMAT,
          org.apache.hadoop.hive.serde2.thrift.TCTLSeparatedProtocol.class
          .getName());
      schema.setProperty(
          org.apache.hadoop.hive.metastore.api.hive_metastoreConstants.META_TABLE_NAME,
          "test");
      schema
          .setProperty(serdeConstants.SERIALIZATION_DDL,
          "struct test { i32 hello, list<string> bye, map<string,i32> another}");
      schema.setProperty(serdeConstants.SERIALIZATION_LIB, new DynamicSerDe()
          .getClass().toString());

      schema.setProperty(TCTLSeparatedProtocol.ReturnNullsKey, "false");
      DynamicSerDe serde = new DynamicSerDe();
      serde.initialize(new Configuration(), schema);

      ObjectInspector oi = serde.getObjectInspector();

      // Try to serialize
      BytesWritable bytes = (BytesWritable) serde.serialize(struct, oi);

      hexString(bytes);

      // Try to deserialize
      Object o = serde.deserialize(bytes);
      List<?> olist = (List<?>) o;

      assertTrue(olist.size() == 3);
      assertEquals(new Integer(0), (Integer) olist.get(0));
      List<?> num1 = (List<?>) olist.get(1);
      assertTrue(num1.size() == 0);
      Map<?, ?> num2 = (Map<?, ?>) olist.get(2);
      assertTrue(num2.size() == 0);

      // assertEquals(o, struct); Cannot do this because types of null lists are
      // wrong.

    } catch (Throwable e) {
      e.printStackTrace();
      throw e;
    }

  }

  /**
   * Tests map and list null/empty with return nulls *off*.
   */

  public void testStructsinStructs() throws Throwable {
    try {

      Properties schema = new Properties();
      // schema.setProperty(serdeConstants.SERIALIZATION_FORMAT,
      // org.apache.thrift.protocol.TJSONProtocol.class.getName());
      schema.setProperty(serdeConstants.SERIALIZATION_FORMAT,
          org.apache.thrift.protocol.TBinaryProtocol.class.getName());
      schema.setProperty(
          org.apache.hadoop.hive.metastore.api.hive_metastoreConstants.META_TABLE_NAME,
          "test");
      schema.setProperty(
          serdeConstants.SERIALIZATION_DDL,
      "struct inner { i32 field1, string field2 },struct  test {inner foo,  i32 hello, list<string> bye, map<string,i32> another}");
      schema.setProperty(serdeConstants.SERIALIZATION_LIB, new DynamicSerDe()
          .getClass().toString());

      //
      // construct object of above type
      //

      // construct the inner struct
      ArrayList<Object> innerStruct = new ArrayList<Object>();
      innerStruct.add(new Integer(22));
      innerStruct.add(new String("hello world"));

      // construct outer struct
      ArrayList<String> bye = new ArrayList<String>();
      bye.add("firstString");
      bye.add("secondString");
      HashMap<String, Integer> another = new HashMap<String, Integer>();
      another.put("firstKey", 1);
      another.put("secondKey", 2);

      ArrayList<Object> struct = new ArrayList<Object>();

      struct.add(innerStruct);
      struct.add(Integer.valueOf(234));
      struct.add(bye);
      struct.add(another);

      DynamicSerDe serde = new DynamicSerDe();
      serde.initialize(new Configuration(), schema);

      ObjectInspector oi = serde.getObjectInspector();

      // Try to serialize
      BytesWritable bytes = (BytesWritable) serde.serialize(struct, oi);

      // Try to deserialize
      Object o = serde.deserialize(bytes);
      List<?> olist = (List<?>) o;

      assertEquals(4, olist.size());
      assertEquals(innerStruct, olist.get(0));
      assertEquals(new Integer(234), olist.get(1));
      assertEquals(bye, olist.get(2));
      assertEquals(another, olist.get(3));

    } catch (Throwable e) {
      e.printStackTrace();
      throw e;
    }

  }

  public void testSkip() throws Throwable {
    try {

      // Try to construct an object
      ArrayList<String> bye = new ArrayList<String>();
      bye.add("firstString");
      bye.add("secondString");
      LinkedHashMap<String, Integer> another = new LinkedHashMap<String, Integer>();
      another.put("firstKey", 1);
      another.put("secondKey", 2);
      ArrayList<Object> struct = new ArrayList<Object>();
      struct.add(Integer.valueOf(234));
      struct.add(bye);
      struct.add(another);

      Properties schema = new Properties();
      schema.setProperty(serdeConstants.SERIALIZATION_FORMAT,
          org.apache.hadoop.hive.serde2.thrift.TCTLSeparatedProtocol.class
          .getName());
      schema.setProperty(
          org.apache.hadoop.hive.metastore.api.hive_metastoreConstants.META_TABLE_NAME,
          "test");
      schema
          .setProperty(serdeConstants.SERIALIZATION_DDL,
          "struct test { i32 hello, list<string> bye, map<string,i32> another}");
      schema.setProperty(serdeConstants.SERIALIZATION_LIB, new DynamicSerDe()
          .getClass().toString());

      schema.setProperty(serdeConstants.FIELD_DELIM, "9");
      schema.setProperty(serdeConstants.COLLECTION_DELIM, "1");
      schema.setProperty(serdeConstants.LINE_DELIM, "2");
      schema.setProperty(serdeConstants.MAPKEY_DELIM, "4");

      DynamicSerDe serde = new DynamicSerDe();
      serde.initialize(new Configuration(), schema);

      TCTLSeparatedProtocol prot = (TCTLSeparatedProtocol) serde.oprot_;
      assertTrue(prot.getPrimarySeparator().equals("\u0009"));

      ObjectInspector oi = serde.getObjectInspector();

      // Try to serialize
      BytesWritable bytes = (BytesWritable) serde.serialize(struct, oi);

      hexString(bytes);

      String compare = "234" + "\u0009" + "firstString" + "\u0001"
          + "secondString" + "\u0009" + "firstKey" + "\u0004" + "1" + "\u0001"
          + "secondKey" + "\u0004" + "2";

      System.out.println("bytes in text ="
          + new String(bytes.get(), 0, bytes.getSize()) + ">");
      System.out.println("compare to    =" + compare + ">");

      assertTrue(compare.equals(new String(bytes.get(), 0, bytes.getSize())));

      schema
          .setProperty(serdeConstants.SERIALIZATION_DDL,
          "struct test { i32 hello, skip list<string> bye, map<string,i32> another}");

      serde.initialize(new Configuration(), schema);

      // Try to deserialize
      Object o = serde.deserialize(bytes);
      System.out.println("o class = " + o.getClass());
      List<?> olist = (List<?>) o;
      System.out.println("o size = " + olist.size());
      System.out.println("o = " + o);

      assertEquals(null, olist.get(1));

      // set the skipped field to null
      struct.set(1, null);

      assertEquals(o, struct);

    } catch (Throwable e) {
      e.printStackTrace();
      throw e;
    }

  }

}
