java_gen: first shot at unit tests
diff --git a/java_gen/codegen.py b/java_gen/codegen.py
index 0765c91..8b8afa1 100644
--- a/java_gen/codegen.py
+++ b/java_gen/codegen.py
@@ -36,6 +36,7 @@
 import of_g
 from loxi_ir import *
 import lang_java
+import test_data
 
 import loxi_utils.loxi_utils as loxi_utils
 
@@ -43,14 +44,13 @@
 
 def gen_all_java(out, name):
     basedir= '%s/openflowj' % of_g.options.install_dir
-    srcdir = "%s/src/main/java/" % basedir
     print "Outputting to %s" % basedir
     if os.path.exists(basedir):
         shutil.rmtree(basedir)
     os.makedirs(basedir)
     copy_prewrite_tree(basedir)
 
-    gen = JavaGenerator(srcdir)
+    gen = JavaGenerator(basedir)
     gen.create_of_interfaces()
     gen.create_of_classes()
     gen.create_of_const_enums()
@@ -67,11 +67,14 @@
         self.basedir = basedir
         self.java_model = java_model.model
 
-    def render_class(self, clazz, template, **context):
+    def render_class(self, clazz, template, src_dir=None, **context):
+        if not src_dir:
+            src_dir = "src/main/java/"
+
         context['class_name'] = clazz.name
         context['package'] = clazz.package
 
-        filename = os.path.join(self.basedir, "%s/%s.java" % (clazz.package.replace(".", "/"), clazz.name))
+        filename = os.path.join(self.basedir, src_dir, "%s/%s.java" % (clazz.package.replace(".", "/"), clazz.name))
         dirname = os.path.dirname(filename)
         if not os.path.exists(dirname):
             os.makedirs(dirname)
@@ -107,6 +110,23 @@
                         template='of_class.java', version=java_class.version, msg=java_class,
                         impl_class=java_class.name)
 
+                self.create_unit_test(java_class.unit_test)
+
+    def create_unit_test(self, unit_test):
+        if unit_test.has_test_data:
+            self.render_class(clazz=unit_test,
+                    template='unit_test.java', src_dir="src/test/java",
+                    version=unit_test.java_class.version,
+                    test=unit_test, msg=unit_test.java_class,
+                    test_data=unit_test.test_data)
+
+    def create_of_factories(self):
+        factory = self.java_model.of_factory
+        self.render_class(clazz=factory, template="of_factory_interface.java", factory=factory)
+        for factory_class in factory.factory_classes:
+            self.render_class(clazz=factory_class, template="of_factory_class.java", factory=factory_class, model=self.java_model)
+        self.render_class(clazz=java_model.OFGenericClass(package="org.openflow.protocol", name="OFFactories"), template="of_factories.java", versions=self.java_model.versions)
+
 def copy_prewrite_tree(basedir):
     """ Recursively copy the directory structure from ./java_gen/pre-write
        into $basedir"""
diff --git a/java_gen/java_model.py b/java_gen/java_model.py
index 35f334c..ccb118d 100644
--- a/java_gen/java_model.py
+++ b/java_gen/java_model.py
@@ -218,6 +218,12 @@
         self.version = version
         self.constant_name = self.c_name.upper().replace("OF_", "")
         self.package = "org.openflow.protocol.ver%s" % version.of_version
+        self.generated = False
+
+    @property
+    @memoize
+    def unit_test(self):
+        return JavaUnitTest(self)
 
     @property
     def name(self):
@@ -441,6 +447,34 @@
             return False
         return (self.name,) == (other.name,)
 
+
+#######################################################################
+### Unit Test
+#######################################################################
+
+class JavaUnitTest(object):
+    def __init__(self, java_class):
+        self.java_class = java_class
+        self.data_file_name = "of{version}/{name}.data".format(version=java_class.version.of_version,
+                                                     name=java_class.c_name[3:])
+    @property
+    def package(self):
+        return self.java_class.package
+
+    @property
+    def name(self):
+        return self.java_class.name + "Test"
+
+    @property
+    def has_test_data(self):
+        return test_data.exists(self.data_file_name)
+
+    @property
+    @memoize
+    def test_data(self):
+        return test_data.read(self.data_file_name)
+
+
 #######################################################################
 ### Enums
 #######################################################################
diff --git a/java_gen/java_type.py b/java_gen/java_type.py
index 8580f37..8d0d7bb 100644
--- a/java_gen/java_type.py
+++ b/java_gen/java_type.py
@@ -21,19 +21,19 @@
         return camel
 
 
-ANY = 0xFFFFFFFFFFFFFFFF
+java_primitive_types = set("byte char short int long".split(" "))
 
+ANY = 0xFFFFFFFFFFFFFFFF
 
 class VersionOp:
     def __init__(self, version=ANY, read=None, write=None):
         self.version = version
         self.read = read
         self.write = write
-        
+
     def __str__(self):
         return "[Version: %d, Read: '%s', Write: '%s']" % (self.version, self.read, self.write)
 
-
 class JType(object):
     """ Wrapper class to hold C to Java type conversion information """
     def __init__(self, pub_type, priv_type=None, size=None, read_op=None, write_op=None):
@@ -98,6 +98,15 @@
         else:
             return _write_op.replace("$name", str(name)).replace("$version", version.of_version)
 
+    @property
+    def is_primitive(self):
+        return self.pub_type in java_primitive_types
+
+    @property
+    def is_array(self):
+        return self.pub_type.endswith("[]")
+
+
 hello_elem_list = JType("List<OFHelloElement>") \
         .op(read='ChannelUtils.readHelloElementList(bb)', write='ChannelUtils.writeHelloElementList(bb)')
 u8 =  JType('byte',  size=1) \
diff --git a/java_gen/templates/unit_test.java b/java_gen/templates/unit_test.java
new file mode 100644
index 0000000..1462465
--- /dev/null
+++ b/java_gen/templates/unit_test.java
@@ -0,0 +1,102 @@
+//:: # Copyright 2013, Big Switch Networks, Inc.
+//:: #
+//:: # LoxiGen is licensed under the Eclipse Public License, version 1.0 (EPL), with
+//:: # the following special exception:
+//:: #
+//:: # LOXI Exception
+//:: #
+//:: # As a special exception to the terms of the EPL, you may distribute libraries
+//:: # generated by LoxiGen (LoxiGen Libraries) under the terms of your choice, provided
+//:: # that copyright and licensing notices generated by LoxiGen are not altered or removed
+//:: # from the LoxiGen Libraries and the notice provided below is (i) included in
+//:: # the LoxiGen Libraries, if distributed in source code form and (ii) included in any
+//:: # documentation for the LoxiGen Libraries, if distributed in binary form.
+//:: #
+//:: # Notice: "Copyright 2013, Big Switch Networks, Inc. This library was generated by the LoxiGen Compiler."
+//:: #
+//:: # You may not use this file except in compliance with the EPL or LOXI Exception. You may obtain
+//:: # a copy of the EPL at:
+//:: #
+//:: # http::: #www.eclipse.org/legal/epl-v10.html
+//:: #
+//:: # 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
+//:: # EPL for the specific language governing permissions and limitations
+//:: # under the EPL.
+//::
+//:: from loxi_ir import *
+//:: import itertools
+//:: import of_g
+//:: include('_copyright.java')
+
+//:: include('_autogen.java')
+
+package ${test.package};
+
+//:: include("_imports.java", msg=msg)
+import org.junit.Before;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+public class ${test.name} {
+    //:: var_type = msg.interface.name
+    //:: var_name = msg.interface.variable_name
+    OFFactory factory;
+
+    final static byte[] ${msg.constant_name}_SERIALIZED =
+        new byte[] { ${", ".join("%s0x%x" % (("" if ord(c)<128 else "(byte) "),  ord(c)) for c in test_data["binary"] ) } };
+
+    @Before
+    public void setup() {
+        factory = OFFactories.getFactory(OFVersion.${version.constant_version});
+    }
+
+    //:: if "java" in test_data:
+    @Test
+    public void testWrite() {
+        ${var_type}.Builder builder = factory.create${var_type[2:]}Builder();
+        ${test_data["java"]};
+        ${var_type} ${var_name} = builder.getMessage();
+        ChannelBuffer bb = ChannelBuffers.dynamicBuffer();
+        ${var_name}.writeTo(bb);
+        byte[] written = new byte[bb.readableBytes()];
+        bb.readBytes(written);
+
+        assertArrayEquals(${msg.constant_name}_SERIALIZED, written);
+    }
+
+    @Test
+    public void testRead() throws Exception {
+        ${var_type}.Builder builder = factory.create${var_type[2:]}Builder();
+        ${test_data["java"]};
+        ${var_type} ${var_name}Built = builder.getMessage();
+
+        ChannelBuffer input = ChannelBuffers.copiedBuffer(${msg.constant_name}_SERIALIZED);
+
+        // FIXME should invoke the overall reader once implemented
+        ${var_type} ${var_name}Read = ${msg.name}.READER.readFrom(input);
+
+        assertEquals(${var_name}Read, ${var_name}Built);
+   }
+   //:: else:
+   // FIXME: No java stanza in test_data for this class. Add for more comprehensive unit testing
+   //:: #endif
+
+   @Test
+   public void testReadWrite() throws Exception {
+       ChannelBuffer input = ChannelBuffers.copiedBuffer(${msg.constant_name}_SERIALIZED);
+
+       // FIXME should invoke the overall reader once implemented
+       ${var_type} ${var_name} = ${msg.name}.READER.readFrom(input);
+
+       // write message again
+       ChannelBuffer bb = ChannelBuffers.dynamicBuffer();
+       ${var_name}.writeTo(bb);
+       byte[] written = new byte[bb.readableBytes()];
+       bb.readBytes(written);
+
+       assertArrayEquals(${msg.constant_name}_SERIALIZED, written);
+   }
+
+}