THRIFT-138. java: Create deep-copy constructors for Thrift structs

- Create a copy constructor for every Thrift struct.  The constructor
  performs a deep copy on the argument, resulting in no shared state.
- Make thrift structions implement Cloneable and implement .clone()
  using the copy constructor.


git-svn-id: https://svn.apache.org/repos/asf/incubator/thrift/trunk@722332 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/compiler/cpp/src/generate/t_java_generator.cc b/compiler/cpp/src/generate/t_java_generator.cc
index 5606f81..6da8e3c 100644
--- a/compiler/cpp/src/generate/t_java_generator.cc
+++ b/compiler/cpp/src/generate/t_java_generator.cc
@@ -147,6 +147,8 @@
   void generate_java_doc                 (std::ofstream& out,
                                           t_doc*     tdoc);
 
+  void generate_deep_copy_container(std::ofstream& out, std::string source_name_p1, std::string source_name_p2, std::string result_name, t_type* type);
+  void generate_deep_copy_non_container(std::ofstream& out, std::string source_name, t_type* type);
 
   /**
    * Helper rendering functions
@@ -569,7 +571,7 @@
   if (is_exception) {
     out << "extends Exception ";
   }
-  out << "implements TBase, java.io.Serializable ";
+  out << "implements TBase, java.io.Serializable, Cloneable ";
 
   scope_up(out);
 
@@ -619,8 +621,8 @@
   indent(out) << "}" << endl << endl;
 
 
-  // Full constructor for all fields
   if (!members.empty()) {
+    // Full constructor for all fields
     indent(out) <<
       "public " << tstruct->get_name() << "(" << endl;
     indent_up();
@@ -652,6 +654,49 @@
     indent(out) << "}" << endl << endl;
   }
 
+  // copy constructor
+  indent(out) << "/**" << endl;
+  indent(out) << " * Performs a deep copy on <i>other</i>." << endl;
+  indent(out) << " */" << endl;
+  indent(out) << "public " << tstruct->get_name() << "(" << tstruct->get_name() << " other) {" << endl;
+  indent_up();
+
+  for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) {
+    t_field* field = (*m_iter);
+    std::string field_name = field->get_name();
+    t_type* type = field->get_type();
+
+    indent(out) << "__isset." << field_name << " = other.__isset." << field_name << ";" << endl;
+
+    if (type_can_be_null(type)) {
+      indent(out) << "if (other." << field_name << " != null) {" << endl;
+      indent_up();
+    }
+
+    if (type->is_container()) {
+      generate_deep_copy_container(out, "other", field_name, "__this__" + field_name, type);
+      indent(out) << "this." << field_name << " = __this__" << field_name << ";" << endl;
+    } else {
+      indent(out) << "this." << field_name << " = ";
+      generate_deep_copy_non_container(out, "other." + field_name, type);
+      out << ";" << endl;
+    }
+
+    if (type_can_be_null(type)) {
+      indent_down();
+      indent(out) << "}" << endl;
+    }
+  }
+
+  indent_down();
+  indent(out) << "}" << endl << endl;
+
+  // clone method, so that you can deep copy an object when you don't know its class.
+  indent(out) << "public " << tstruct->get_name() << " clone() {" << endl;
+  indent(out) << "  return new " << tstruct->get_name() << "(this);" << endl;
+  indent(out) << "}" << endl << endl;
+
+
   if (bean_style_) {
     generate_java_bean_boilerplate(out, tstruct);
     generate_generic_field_getters_setters(out, tstruct);
@@ -2543,6 +2588,101 @@
   }
 }
 
+void t_java_generator::generate_deep_copy_container(ofstream &out, std::string source_name_p1, std::string source_name_p2,
+                                                    std::string result_name, t_type* type) {
+
+  t_container* container = (t_container*)type;
+  std::string source_name;
+  if (source_name_p2 == "")
+      source_name = source_name_p1;
+  else
+      source_name = source_name_p1 + "." + source_name_p2;
+
+  indent(out) << type_name(type, true, false) << " " << result_name << " = new " << type_name(container, false, true) << "();" << endl;
+
+  std::string iterator_element_name = source_name_p1 + "_element";
+  std::string result_element_name = result_name + "_copy";
+
+  if(container->is_map()) {
+    t_type* key_type = ((t_map*)container)->get_key_type();
+    t_type* val_type = ((t_map*)container)->get_val_type();
+
+    indent(out) <<
+      "for (Map.Entry<" << type_name(key_type, true, false) << ", " << type_name(val_type, true, false) << "> " << iterator_element_name << " : " << source_name << ".entrySet()) {" << endl;
+    indent_up();
+
+    out << endl;
+
+    indent(out) << type_name(key_type, true, false) << " " << iterator_element_name << "_key = " << iterator_element_name << ".getKey();" << endl;
+    indent(out) << type_name(val_type, true, false) << " " << iterator_element_name << "_value = " << iterator_element_name << ".getValue();" << endl;
+
+    out << endl;
+
+    if (key_type->is_container()) {
+      generate_deep_copy_container(out, iterator_element_name + "_key", "", result_element_name + "_key", key_type);
+    } else {
+      indent(out) << type_name(key_type, true, false) << " " << result_element_name << "_key = ";
+      generate_deep_copy_non_container(out, iterator_element_name + "_key", key_type);
+      out << ";" << endl;
+    }
+
+    out << endl;
+
+    if (val_type->is_container()) {
+      generate_deep_copy_container(out, iterator_element_name + "_value", "", result_element_name + "_value", val_type);
+    } else {
+      indent(out) << type_name(val_type, true, false) << " " << result_element_name << "_value = ";
+      generate_deep_copy_non_container(out, iterator_element_name + "_value", val_type);
+      out << ";" << endl;
+    }
+
+    out << endl;
+
+    indent(out) << result_name << ".put(" << result_element_name << "_key, " << result_element_name << "_value);" << endl;
+
+    indent_down();
+    indent(out) << "}" << endl;
+
+  } else {
+    t_type* elem_type;
+
+    if (container->is_set()) {
+      elem_type = ((t_set*)container)->get_elem_type();
+    } else {
+      elem_type = ((t_list*)container)->get_elem_type();
+    }
+
+    indent(out)
+      << "for (" << type_name(elem_type, true, false) << " " << iterator_element_name << " : " << source_name << ") {" << endl;
+
+    indent_up();
+
+    if (elem_type->is_container()) {
+      // recursive deep copy
+      generate_deep_copy_container(out, iterator_element_name, "", result_element_name, elem_type);
+      indent(out) << result_name << ".add(" << result_element_name << ");" << endl;
+    } else {
+      // iterative copy
+      indent(out) << result_name << ".add(";
+      generate_deep_copy_non_container(out, iterator_element_name, elem_type);
+      out << ");" << endl;
+    }
+
+    indent_down();
+
+    indent(out) << "}" << endl;
+
+  }
+}
+
+void t_java_generator::generate_deep_copy_non_container(ofstream& out, std::string source_name, t_type* type) {
+  if (type->is_base_type() || type->is_enum() || type->is_typedef()) {
+    out << source_name;
+  } else {
+    out << "new " << type_name(type, true, true) << "(" << source_name << ")";
+  }
+}
+
 
 THRIFT_REGISTER_GENERATOR(java, "Java",
 "    beans:           Generate bean-style output files.\n"
diff --git a/test/java/build.xml b/test/java/build.xml
index 7b685fb..1ed1ff1 100644
--- a/test/java/build.xml
+++ b/test/java/build.xml
@@ -46,6 +46,8 @@
       classpath="${cpath}:${testjar}:${gen}" failonerror="true" />
     <java classname="com.facebook.thrift.test.ToStringTest"
       classpath="${cpath}:${testjar}:${gen}" failonerror="true" />
+    <java classname="com.facebook.thrift.test.DeepCopyTest"
+      classpath="${cpath}:${testjar}:${gen}" failonerror="true" />
   </target>
 
   <target name="clean">
diff --git a/test/java/src/DeepCopyTest.java b/test/java/src/DeepCopyTest.java
new file mode 100644
index 0000000..a5b19cd
--- /dev/null
+++ b/test/java/src/DeepCopyTest.java
@@ -0,0 +1,114 @@
+
+package com.facebook.thrift.test;
+
+import com.facebook.thrift.TDeserializer;
+import com.facebook.thrift.TSerializer;
+import com.facebook.thrift.protocol.TBinaryProtocol;
+import thrift.test.*;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+
+public class DeepCopyTest {
+
+  private static final byte[] kUnicodeBytes = {
+    (byte)0xd3, (byte)0x80, (byte)0xe2, (byte)0x85, (byte)0xae, (byte)0xce,
+    (byte)0x9d, (byte)0x20, (byte)0xd0, (byte)0x9d, (byte)0xce, (byte)0xbf,
+    (byte)0xe2, (byte)0x85, (byte)0xbf, (byte)0xd0, (byte)0xbe, (byte)0xc9,
+    (byte)0xa1, (byte)0xd0, (byte)0xb3, (byte)0xd0, (byte)0xb0, (byte)0xcf,
+    (byte)0x81, (byte)0xe2, (byte)0x84, (byte)0x8e, (byte)0x20, (byte)0xce,
+    (byte)0x91, (byte)0x74, (byte)0x74, (byte)0xce, (byte)0xb1, (byte)0xe2,
+    (byte)0x85, (byte)0xbd, (byte)0xce, (byte)0xba, (byte)0x83, (byte)0xe2,
+    (byte)0x80, (byte)0xbc
+  };
+
+  public static void main(String[] args) throws Exception {
+    TSerializer   binarySerializer   = new   TSerializer(new TBinaryProtocol.Factory());
+    TDeserializer binaryDeserializer = new TDeserializer(new TBinaryProtocol.Factory());
+
+    OneOfEach ooe = new OneOfEach();
+    ooe.im_true = true;
+    ooe.im_false = false;
+    ooe.a_bite = (byte) 0xd6;
+    ooe.integer16 = 27000;
+    ooe.integer32 = 1 << 24;
+    ooe.integer64 = (long) 6000 * 1000 * 1000;
+    ooe.double_precision = Math.PI;
+    ooe.some_characters = "JSON THIS! \"\1";
+    ooe.zomg_unicode = new String(kUnicodeBytes, "UTF-8");
+
+    Nesting n = new Nesting(new Bonk(), new OneOfEach());
+    n.my_ooe.integer16 = 16;
+    n.my_ooe.integer32 = 32;
+    n.my_ooe.integer64 = 64;
+    n.my_ooe.double_precision = (Math.sqrt(5) + 1) / 2;
+    n.my_ooe.some_characters = ":R (me going \"rrrr\")";
+    n.my_ooe.zomg_unicode = new String(kUnicodeBytes, "UTF-8");
+    n.my_bonk.type = 31337;
+    n.my_bonk.message = "I am a bonk... xor!";
+
+    HolyMoley hm = new HolyMoley();
+
+    hm.big = new ArrayList<OneOfEach>();
+    hm.big.add(ooe);
+    hm.big.add(n.my_ooe);
+    hm.big.get(0).a_bite = (byte) 0x22;
+    hm.big.get(1).a_bite = (byte) 0x23;
+
+    hm.contain = new HashSet<List<String>>();
+    ArrayList<String> stage1 = new ArrayList<String>(2);
+    stage1.add("and a one");
+    stage1.add("and a two");
+    hm.contain.add(stage1);
+    stage1 = new ArrayList<String>(3);
+    stage1.add("then a one, two");
+    stage1.add("three!");
+    stage1.add("FOUR!!");
+    hm.contain.add(stage1);
+    stage1 = new ArrayList<String>(0);
+    hm.contain.add(stage1);
+
+    ArrayList<Bonk> stage2 = new ArrayList<Bonk>();
+    hm.bonks = new HashMap<String, List<Bonk>>();
+    hm.bonks.put("nothing", stage2);
+    Bonk b = new Bonk();
+    b.type = 1;
+    b.message = "Wait.";
+    stage2.add(b);
+    b = new Bonk();
+    b.type = 2;
+    b.message = "What?";
+    stage2.add(b);
+    stage2 = new ArrayList<Bonk>();
+    hm.bonks.put("something", stage2);
+    b = new Bonk();
+    b.type = 3;
+    b.message = "quoth";
+    b = new Bonk();
+    b.type = 4;
+    b.message = "the raven";
+    b = new Bonk();
+    b.type = 5;
+    b.message = "nevermore";
+    hm.bonks.put("poe", stage2);
+
+
+    byte[] binaryCopy = binarySerializer.serialize(hm);
+    HolyMoley hmCopy = new HolyMoley();
+    binaryDeserializer.deserialize(hmCopy, binaryCopy);
+    HolyMoley hmCopy2 = new HolyMoley(hm);
+
+    if (!hm.equals(hmCopy))
+      throw new RuntimeException("copy constructor modified the original object!");
+    if (!hmCopy.equals(hmCopy2))
+      throw new RuntimeException("copy constructor generated incorrect copy");
+
+    hmCopy2.bonks.get("nothing").get(1).message = "What else?";
+
+    if (hm.equals(hmCopy2))
+      throw new RuntimeException("A deep copy was not done!");
+
+    //System.out.println("DeepCopyTest passed!");
+  }
+}