THRIFT-551. java: Enumeration doesn't generate real enum in Java
This patch makes the compiler generate actual Enum classes.

git-svn-id: https://svn.apache.org/repos/asf/incubator/thrift/trunk@882211 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 8381f8e..24c7ef5 100644
--- a/compiler/cpp/src/generate/t_java_generator.cc
+++ b/compiler/cpp/src/generate/t_java_generator.cc
@@ -229,7 +229,8 @@
       ttype->is_container() ||
       ttype->is_struct() ||
       ttype->is_xception() ||
-      ttype->is_string();
+      ttype->is_string() ||
+      ttype->is_enum();
   }
 
   std::string constant_name(std::string name);
@@ -361,22 +362,21 @@
   f_enum <<
     autogen_comment() <<
     java_package() << endl;
-  
+
   // Add java imports
   f_enum << string() +
-    "import java.util.Set;\n" +
-    "import java.util.HashSet;\n" +
-    "import java.util.Collections;\n" +
-    "import org.apache.thrift.IntRangeSet;\n" +
     "import java.util.Map;\n" + 
-    "import java.util.HashMap;\n" << endl;
+    "import java.util.HashMap;\n" +
+    "import org.apache.thrift.TEnum;" << endl;
 
-  f_enum <<
-    "public class " << tenum->get_name() << " ";
+  generate_java_doc(f_enum, tenum);
+  indent(f_enum) <<
+    "public enum " << tenum->get_name() << " implements TEnum";
   scope_up(f_enum);
 
   vector<t_enum_value*> constants = tenum->get_constants();
   vector<t_enum_value*>::iterator c_iter;
+  bool first = true;
   int value = -1;
   for (c_iter = constants.begin(); c_iter != constants.end(); ++c_iter) {
     if ((*c_iter)->has_value()) {
@@ -385,39 +385,50 @@
       ++value;
     }
 
+    if (first) {
+      first = false;
+    } else {
+      f_enum << "," << endl;
+    }
+
     generate_java_doc(f_enum, *c_iter);
-    indent(f_enum) <<
-      "public static final int " << (*c_iter)->get_name() <<
-      " = " << value << ";" << endl;
+    indent(f_enum) << "  " << (*c_iter)->get_name() << "(" << value << ")";
   }
-  
-  // Create a static Set with all valid values for this enum
-  f_enum << endl;
-  indent(f_enum) << "public static final IntRangeSet VALID_VALUES = new IntRangeSet(";
-  indent_up();
-  bool first = true;
-  for (c_iter = constants.begin(); c_iter != constants.end(); ++c_iter) {
-    // populate set
-    f_enum << (first ? "" : ", ") << endl;
-    first = false;
-    indent(f_enum) << (*c_iter)->get_name();
-  }
-  f_enum << " );" << endl << endl;
-  indent_down();
+  f_enum << ";" << endl << endl;
 
-  indent(f_enum) << "public static final Map<Integer, String> VALUES_TO_NAMES = new HashMap<Integer, String>() {{" << endl;
-
-  indent_up();
-  for (c_iter = constants.begin(); c_iter != constants.end(); ++c_iter) {    
-    indent(f_enum) << "put(" << (*c_iter)->get_name() << ", \"" << (*c_iter)->get_name() <<"\");" << endl;
-  }
-  indent_down();
-
-  
+  indent(f_enum) << "private static final Map<Integer, "+ tenum->get_name() +
+    "> BY_VALUE = new HashMap<Integer,"+ tenum->get_name() +">() {{" << endl;
+  indent(f_enum) << "  for("+ tenum->get_name() +" val : "+ tenum->get_name() +".values()) {" << endl;
+  indent(f_enum) << "    put(val.getValue(), val);" << endl;
+  indent(f_enum) << "  }" << endl;
   indent(f_enum) << "}};" << endl;
 
+  f_enum << endl;
+
+  // Field for thriftCode
+  indent(f_enum) << "private final int value;" << endl << endl;
+
+  indent(f_enum) << "private " << tenum->get_name() << "(int value) {" << endl;
+  indent(f_enum) << "  this.value = value;" <<endl;
+  indent(f_enum) << "}" << endl << endl;
+ 
+  indent(f_enum) << "/**" << endl;
+  indent(f_enum) << " * Get the integer value of this enum value, as defined in the Thrift IDL." << endl;
+  indent(f_enum) << " */" << endl;
+  indent(f_enum) << "public int getValue() {" << endl;
+  indent(f_enum) << "  return value;" <<endl;
+  indent(f_enum) << "}" << endl << endl;
+
+  indent(f_enum) << "/**" << endl;
+  indent(f_enum) << " * Find a the enum type by its integer value, as defined in the Thrift IDL." << endl;
+  indent(f_enum) << " * @return null if the value is not found." << endl;
+  indent(f_enum) << " */" << endl;
+  indent(f_enum) << "public static "+ tenum->get_name() + " findByValue(int value) { " << endl;
+  indent(f_enum) << "  return BY_VALUE.get(value);" << endl;
+  indent(f_enum) << "}" << endl;
+
   scope_down(f_enum);
-  
+
   f_enum.close();
 }
 
@@ -985,7 +996,7 @@
   if (gen_hash_code_) {
     indent(out) << "@Override" << endl;
     indent(out) << "public int hashCode() {" << endl;
-    indent(out) << "  return new HashCodeBuilder().append(getSetField().getThriftFieldId()).append(getFieldValue()).toHashCode();" << endl;
+    indent(out) << "  return new HashCodeBuilder().append(getSetField().getThriftFieldId()).append((getFieldValue() instanceof TEnum) ? ((TEnum)getFieldValue()).getValue() : getFieldValue()).toHashCode();" << endl;
     indent(out) << "}";
   } else {
     indent(out) << "/**" << endl;
@@ -1305,12 +1316,14 @@
         present += " && (" + generate_isset_check(*m_iter) + ")";
       }
 
-      out <<
-        indent() << "boolean present_" << name << " = "
-                 << present << ";" << endl <<
-        indent() << "builder.append(present_" << name << ");" << endl <<
-        indent() << "if (present_" << name << ")" << endl <<
-        indent() << "  builder.append(" << name << ");" << endl;
+      indent(out) << "boolean present_" << name << " = " << present << ";" << endl;
+      indent(out) << "builder.append(present_" << name << ");" << endl;
+      indent(out) << "if (present_" << name << ")" << endl;
+      if (t->is_enum()) {
+        indent(out) << "  builder.append(" << name << ".getValue());" << endl;
+      } else {
+        indent(out) << "  builder.append(" << name << ");" << endl;
+      }
     }
 
     out << endl;
@@ -1401,9 +1414,9 @@
     indent(out) << "} else {" << endl;
     indent_up();
 
-    indent(out) << "switch (fieldId)" << endl;
+    indent(out) << "switch (fieldId) {" << endl;
 
-    scope_up(out);
+    indent_up();
 
     // Generate deserialization code for known cases
     for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) {
@@ -1425,7 +1438,8 @@
       indent_down();
     }
 
-    scope_down(out);
+    indent_down();
+    indent(out) << "}" << endl;
 
     // Read field end marker
     indent(out) <<
@@ -1467,11 +1481,11 @@
 void t_java_generator::generate_java_validator(ofstream& out,
                                                    t_struct* tstruct){
   indent(out) << "public void validate() throws TException {" << endl;
-  indent_up();  
-  
+  indent_up();
+
   const vector<t_field*>& fields = tstruct->get_members();
   vector<t_field*>::const_iterator f_iter;
-  
+
   out << indent() << "// check for required fields" << endl;
   for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) {
     if ((*f_iter)->get_req() == t_field::T_REQUIRED) {
@@ -1488,25 +1502,10 @@
         } else {
           indent(out) << "// alas, we cannot check '" << (*f_iter)->get_name() << "' because it's a primitive and you chose the non-beans generator." << endl;
         }
-      }      
+      }
     }
   }
-  
-  // check that fields of type enum have valid values
-  out << indent() << "// check that fields of type enum have valid values" << endl;
-  for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) {
-    t_field* field = (*f_iter);
-    t_type* type = field->get_type();
-    // if field is an enum, check that its value is valid
-    if (type->is_enum()){
-      indent(out) << "if (" << generate_isset_check(field) << " && !" << get_enum_class_name(type) << ".VALID_VALUES.contains(" << field->get_name() << ")){" << endl;
-      indent_up();
-      indent(out) << "throw new TProtocolException(\"The field '" << field->get_name() << "' has been assigned the invalid value \" + " << field->get_name() << ");" << endl;
-      indent_down();
-      indent(out) << "}" << endl;
-    } 
-  }  
-  
+
   indent_down();
   indent(out) << "}" << endl << endl;
 }
@@ -1935,7 +1934,7 @@
       indent(out) << "  }" << endl;
       indent(out) << "  if (this." << field->get_name() << ".length > 128) sb.append(\" ...\");" << endl;
     } else if(field->get_type()->is_enum()) {
-      indent(out) << "String " << field->get_name() << "_name = " << get_enum_class_name(field->get_type()) << ".VALUES_TO_NAMES.get(this." << (*f_iter)->get_name() << ");"<< endl;
+      indent(out) << "String " << field->get_name() << "_name = " << field->get_name() << ".name();"<< endl;
       indent(out) << "if (" << field->get_name() << "_name != null) {" << endl;
       indent(out) << "  sb.append(" << field->get_name() << "_name);" << endl;
       indent(out) << "  sb.append(\" (\");" << endl;
@@ -2028,7 +2027,7 @@
   } else if (type->is_struct() || type->is_xception()) {
     return "TType.STRUCT";
   } else if (type->is_enum()) {
-    return "TType.I32";
+    return "TType.ENUM";
   } else if (type->is_typedef()) {
     return get_java_type_string(((t_typedef*)type)->get_type());
   } else if (type->is_base_type()) {
@@ -2071,6 +2070,8 @@
       out << ", ";
       generate_field_value_meta_data(out, val_type);
     }
+  } else if (type->is_enum()) {
+    indent(out) << "new EnumMetaData(TType.ENUM, " << type_name(type) << ".class";
   } else {
     indent(out) << "new FieldValueMetaData(" << get_java_type_string(type);
   }
@@ -2642,14 +2643,11 @@
                                 name);
   } else if (type->is_container()) {
     generate_deserialize_container(out, type, name);
-  } else if (type->is_base_type() || type->is_enum()) {
+  } else if (type->is_base_type()) {
+    indent(out) << name << " = iprot.";
 
-    indent(out) <<
-      name << " = iprot.";
-
-    if (type->is_base_type()) {
-      t_base_type::t_base tbase = ((t_base_type*)type)->get_base();
-      switch (tbase) {
+    t_base_type::t_base tbase = ((t_base_type*)type)->get_base();
+    switch (tbase) {
       case t_base_type::TYPE_VOID:
         throw "compiler error: cannot serialize void field in a struct: " +
           name;
@@ -2681,12 +2679,10 @@
         break;
       default:
         throw "compiler error: no Java name for base type " + t_base_type::t_base_name(tbase);
-      }
-    } else if (type->is_enum()) {
-      out << "readI32();";
     }
-    out <<
-      endl;
+    out << endl;
+  } else if (type->is_enum()) {
+    indent(out) << name << " = " << type_name(tfield->get_type(), true, false) + ".findByValue(iprot.readI32());" << endl;
   } else {
     printf("DO NOT KNOW HOW TO DESERIALIZE FIELD '%s' TYPE '%s'\n",
            tfield->get_name().c_str(), type_name(type).c_str());
@@ -2856,8 +2852,9 @@
     generate_serialize_container(out,
                                  type,
                                  prefix + tfield->get_name());
-  } else if (type->is_base_type() || type->is_enum()) {
-
+  } else if (type->is_enum()){
+    indent(out) << "oprot.writeI32(" << prefix + tfield->get_name() << ".getValue());" << endl;
+  } else if (type->is_base_type()) {
     string name = prefix + tfield->get_name();
     indent(out) <<
       "oprot.";
@@ -3045,8 +3042,6 @@
 
   if (ttype->is_base_type()) {
     return base_type_name((t_base_type*)ttype, in_container);
-  } else if (ttype->is_enum()) {
-    return (in_container ? "Integer" : "int");
   } else if (ttype->is_map()) {
     t_map* tmap = (t_map*) ttype;
     if (in_init) {
diff --git a/lib/java/src/org/apache/thrift/TEnum.java b/lib/java/src/org/apache/thrift/TEnum.java
new file mode 100644
index 0000000..325fdec
--- /dev/null
+++ b/lib/java/src/org/apache/thrift/TEnum.java
@@ -0,0 +1,24 @@
+/*
+ * 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.thrift;
+
+public interface TEnum {
+  public int getValue();
+}
diff --git a/lib/java/src/org/apache/thrift/meta_data/EnumMetaData.java b/lib/java/src/org/apache/thrift/meta_data/EnumMetaData.java
new file mode 100644
index 0000000..be49cb9
--- /dev/null
+++ b/lib/java/src/org/apache/thrift/meta_data/EnumMetaData.java
@@ -0,0 +1,31 @@
+/*
+ * 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.thrift.meta_data;
+
+import org.apache.thrift.TEnum;
+
+public class EnumMetaData extends FieldValueMetaData {
+  public final Class<? extends TEnum> enumClass;
+  
+  public EnumMetaData(byte type, Class<? extends TEnum> sClass){
+    super(type);
+    this.enumClass = sClass;
+  }    
+}
diff --git a/lib/java/src/org/apache/thrift/meta_data/FieldValueMetaData.java b/lib/java/src/org/apache/thrift/meta_data/FieldValueMetaData.java
index f72da0c..076a768 100644
--- a/lib/java/src/org/apache/thrift/meta_data/FieldValueMetaData.java
+++ b/lib/java/src/org/apache/thrift/meta_data/FieldValueMetaData.java
@@ -27,15 +27,15 @@
  */
 public class FieldValueMetaData implements java.io.Serializable {
   public final byte type;  
- 
+
   public FieldValueMetaData(byte type){
     this.type = type;
   }
-  
+
   public boolean isStruct() {
     return type == TType.STRUCT; 
   }
-  
+
   public boolean isContainer() {
     return type == TType.LIST || type == TType.MAP || type == TType.SET;
   }
diff --git a/lib/java/src/org/apache/thrift/protocol/TType.java b/lib/java/src/org/apache/thrift/protocol/TType.java
index dbdc3ca..c3c1a0a 100644
--- a/lib/java/src/org/apache/thrift/protocol/TType.java
+++ b/lib/java/src/org/apache/thrift/protocol/TType.java
@@ -21,7 +21,6 @@
 
 /**
  * Type constants in the Thrift protocol.
- *
  */
 public final class TType {
   public static final byte STOP   = 0;
@@ -37,4 +36,5 @@
   public static final byte MAP    = 13;
   public static final byte SET    = 14;
   public static final byte LIST   = 15;
+  public static final byte ENUM   = 16;
 }
diff --git a/lib/java/test/org/apache/thrift/test/TestClient.java b/lib/java/test/org/apache/thrift/test/TestClient.java
index 4a95f7a..aee3bbf 100644
--- a/lib/java/test/org/apache/thrift/test/TestClient.java
+++ b/lib/java/test/org/apache/thrift/test/TestClient.java
@@ -281,7 +281,7 @@
          * ENUM TEST
          */
         System.out.print("testEnum(ONE)");
-        int ret = testClient.testEnum(Numberz.ONE);
+        Numberz ret = testClient.testEnum(Numberz.ONE);
         System.out.print(" = " + ret + "\n");
 
         System.out.print("testEnum(TWO)");
@@ -328,7 +328,7 @@
          * INSANITY TEST
          */
         insane = new Insanity();
-        insane.userMap = new HashMap<Integer, Long>();
+        insane.userMap = new HashMap<Numberz, Long>();
         insane.userMap.put(Numberz.FIVE, (long)5000);
         Xtruct truck = new Xtruct();
         truck.string_thing = "Truck";
@@ -338,20 +338,20 @@
         insane.xtructs = new ArrayList<Xtruct>();
         insane.xtructs.add(truck);
         System.out.print("testInsanity()");
-        Map<Long,Map<Integer,Insanity>> whoa =
+        Map<Long,Map<Numberz,Insanity>> whoa =
           testClient.testInsanity(insane);
         System.out.print(" = {");
         for (long key : whoa.keySet()) {
-          Map<Integer,Insanity> val = whoa.get(key);
+          Map<Numberz,Insanity> val = whoa.get(key);
           System.out.print(key + " => {");
 
-          for (int k2 : val.keySet()) {
+          for (Numberz k2 : val.keySet()) {
             Insanity v2 = val.get(k2);
             System.out.print(k2 + " => {");
-            Map<Integer, Long> userMap = v2.userMap;
+            Map<Numberz, Long> userMap = v2.userMap;
             System.out.print("{");
             if (userMap != null) {
-              for (int k3 : userMap.keySet()) {
+              for (Numberz k3 : userMap.keySet()) {
                 System.out.print(k3 + " => " + userMap.get(k3) + ", ");
               }
             }
diff --git a/lib/java/test/org/apache/thrift/test/TestServer.java b/lib/java/test/org/apache/thrift/test/TestServer.java
index 986f889..304d8a3 100644
--- a/lib/java/test/org/apache/thrift/test/TestServer.java
+++ b/lib/java/test/org/apache/thrift/test/TestServer.java
@@ -140,7 +140,7 @@
       return thing;
     }
 
-    public int testEnum(int thing) {
+    public Numberz testEnum(Numberz thing) {
       System.out.print("testEnum(" + thing + ")\n");
       return thing;
     }
@@ -168,7 +168,7 @@
       return mapmap;
     }
 
-    public Map<Long, Map<Integer,Insanity>> testInsanity(Insanity argument) {
+    public Map<Long, Map<Numberz,Insanity>> testInsanity(Insanity argument) {
       System.out.print("testInsanity()\n");
 
       Xtruct hello = new Xtruct();
@@ -184,7 +184,7 @@
       goodbye.i64_thing = (long)4;
 
       Insanity crazy = new Insanity();
-      crazy.userMap = new HashMap<Integer, Long>();
+      crazy.userMap = new HashMap<Numberz, Long>();
       crazy.xtructs = new ArrayList<Xtruct>();
 
       crazy.userMap.put(Numberz.EIGHT, (long)8);
@@ -194,23 +194,23 @@
       crazy.userMap.put(Numberz.FIVE, (long)5);
       crazy.xtructs.add(hello);
 
-      HashMap<Integer,Insanity> first_map = new HashMap<Integer, Insanity>();
-      HashMap<Integer,Insanity> second_map = new HashMap<Integer, Insanity>();;
+      HashMap<Numberz,Insanity> first_map = new HashMap<Numberz, Insanity>();
+      HashMap<Numberz,Insanity> second_map = new HashMap<Numberz, Insanity>();;
 
       first_map.put(Numberz.TWO, crazy);
       first_map.put(Numberz.THREE, crazy);
 
       second_map.put(Numberz.SIX, looney);
 
-      Map<Long,Map<Integer,Insanity>> insane =
-        new HashMap<Long, Map<Integer,Insanity>>();
+      Map<Long,Map<Numberz,Insanity>> insane =
+        new HashMap<Long, Map<Numberz,Insanity>>();
       insane.put((long)1, first_map);
       insane.put((long)2, second_map);
 
       return insane;
     }
 
-    public Xtruct testMulti(byte arg0, int arg1, long arg2, Map<Short,String> arg3, int arg4, long arg5) {
+    public Xtruct testMulti(byte arg0, int arg1, long arg2, Map<Short,String> arg3, Numberz arg4, long arg5) {
       System.out.print("testMulti()\n");
 
       Xtruct hello = new Xtruct();;
diff --git a/lib/java/test/org/apache/thrift/test/UnionTest.java b/lib/java/test/org/apache/thrift/test/UnionTest.java
index cb69063..670a33f 100644
--- a/lib/java/test/org/apache/thrift/test/UnionTest.java
+++ b/lib/java/test/org/apache/thrift/test/UnionTest.java
@@ -7,6 +7,7 @@
 import thrift.test.Empty;
 import thrift.test.StructWithAUnion;
 import thrift.test.TestUnion;
+import thrift.test.SomeEnum;
 import thrift.test.ComparableUnion;
 
 public class UnionTest {
@@ -56,6 +57,7 @@
     if (union.getI32_field() != 1) {
       throw new RuntimeException("didn't get the right value for i32 field!");
     }
+    union.hashCode();
 
     try {
       union.getString_field();
@@ -69,6 +71,9 @@
     if (union.equals((TestUnion)null)) {
       throw new RuntimeException("uh oh, union.equals(null)!");
     }
+
+    union = TestUnion.enum_field(SomeEnum.ONE);
+    union.hashCode();
   }
 
 
diff --git a/test/DebugProtoTest.thrift b/test/DebugProtoTest.thrift
index 5b2f672..549462e 100644
--- a/test/DebugProtoTest.thrift
+++ b/test/DebugProtoTest.thrift
@@ -252,6 +252,11 @@
   void myMethod(4: string first, 3: i16 second, 2: i32 third, 1: i64 fourth);
 }
 
+enum SomeEnum {
+  ONE
+  TWO
+}
+
 union TestUnion {
   /**
    * A doc string
@@ -261,6 +266,7 @@
   3: OneOfEach struct_field;
   4: list<RandomStuff> struct_list;
   5: i32 other_i32_field;
+  6: SomeEnum enum_field;
 }
 
 union ComparableUnion {
diff --git a/test/ThriftTest.thrift b/test/ThriftTest.thrift
index 3517640..f16c02e 100644
--- a/test/ThriftTest.thrift
+++ b/test/ThriftTest.thrift
@@ -23,6 +23,9 @@
 namespace perl ThriftTest
 namespace csharp Thrift.Test
 
+/**
+ * Docstring!
+ */
 enum Numberz
 {
   ONE = 1,