Merge into master from pull request #102:
Java gen: fix alignment for action set_field, add java_gen unit tests for actions (https://github.com/floodlight/loxigen/pull/102)
diff --git a/java_gen/java_model.py b/java_gen/java_model.py
index f2191c0..feb3c72 100644
--- a/java_gen/java_model.py
+++ b/java_gen/java_model.py
@@ -773,6 +773,10 @@
         return int(self.ir_class.params['align']) if 'align' in self.ir_class.params else 0
 
     @property
+    def length_includes_align(self):
+        return self.ir_class.params['length_includes_align'] == "True" if 'length_includes_align' in self.ir_class.params else False
+
+    @property
     @memoize
     def superclass(self):
         return find(lambda c: c.version == self.version and c.c_name == self.ir_class.superclass, model.all_classes)
@@ -1001,16 +1005,23 @@
         self.java_class = java_class
         first_data_file_name = "of{version}/{name}.data".format(version=java_class.version.of_version,
                                                      name=java_class.c_name[3:])
-        data_file_template = "of{version}/{name}.".format(version=java_class.version.of_version,
-                                                     name=java_class.c_name[3:]) + "{i}.data"
+        glob_file_name = "of{version}/{name}__*.data".format(version=java_class.version.of_version,
+                                                     name=java_class.c_name[3:])
         test_class_name = self.java_class.name + "Test"
         self.test_units = []
         if test_data.exists(first_data_file_name):
             self.test_units.append(JavaUnitTest(java_class, first_data_file_name, test_class_name))
+
         i = 1
-        while test_data.exists(data_file_template.format(i=i)):
-            self.test_units.append(JavaUnitTest(java_class, data_file_template.format(i=i), test_class_name + str(i)))
-            i = i + 1
+        for f in test_data.glob(glob_file_name):
+            m = re.match(".*__(.*).data", f)
+            if m:
+                suffix = java_type.name_c_to_caps_camel(m.group(1))
+            else:
+                suffix = str(i)
+                i += 1
+            test_class_name = self.java_class.name + suffix + "Test"
+            self.test_units.append(JavaUnitTest(java_class, f, test_class_name))
 
     @property
     def package(self):
diff --git a/java_gen/pre-written/src/main/java/org/projectfloodlight/openflow/types/OFGroup.java b/java_gen/pre-written/src/main/java/org/projectfloodlight/openflow/types/OFGroup.java
index d522202..b05d5fa 100644
--- a/java_gen/pre-written/src/main/java/org/projectfloodlight/openflow/types/OFGroup.java
+++ b/java_gen/pre-written/src/main/java/org/projectfloodlight/openflow/types/OFGroup.java
@@ -25,32 +25,25 @@
     private static final int ANY_VAL = 0xffffffff;
 
 
-    // ////////////// public constants - use to access well known OpenFlow ports
+    // ////////////// public constants - use to access well known OpenFlow group constants
 
-    /** Maximum number of physical and logical switch ports. */
+    /** Maximum number of physical and logical switch groups. */
     public final static OFGroup MAX = new NamedGroup(MAX_VAL, "max");
 
-    /**
-     * Send the packet out the input port. This reserved port must be explicitly
-     * used in order to send back out of the input port.
-     */
+    /** All groups */
     public final static OFGroup ALL = new NamedGroup(ALL_VAL, "all");
 
     /**
-     * Wildcard group used only for flow mod (delete) and flow stats requests.
-     * Selects all flows regardless of output port (including flows with no
-     * output port). NOTE: OpenFlow 1.0 calls this 'NONE'
-     */
+     * Wildcard group used only for flow mod (delete) and flow stats requests. */
     public final static OFGroup ANY = new NamedGroup(ANY_VAL, "any");
 
-    /** group 0 in case we need it
-     */
+    /** group 0 in case we need it */
     public static final OFGroup ZERO = OFGroup.of(ZERO_VAL);
 
     public static final OFGroup NO_MASK = ANY;
     public static final OFGroup FULL_MASK = ZERO;
 
-    /** raw openflow port number as a signed 32 bit integer */
+    /** raw openflow group number as a signed 32 bit integer */
     private final int groupNumber;
 
     /** private constructor. use of*-Factory methods instead */
@@ -59,11 +52,11 @@
     }
 
     /**
-     * get an OFPort object corresponding to a raw 32-bit integer port number.
-     * NOTE: The port object may either be newly allocated or cached. Do not
+     * get an OFGroup object corresponding to a raw 32-bit integer group number.
+     * NOTE: The group object may either be newly allocated or cached. Do not
      * rely on either behavior.
      *
-     * @param portNumber
+     * @param groupNumber the raw 32-bit group number
      * @return a corresponding OFPort
      */
     public static OFGroup of(final int groupNumber) {
@@ -86,7 +79,7 @@
         }
     }
 
-    /** return the port number as a int32 */
+    /** return the group number as a int32 */
     public int getGroupNumber() {
         return groupNumber;
     }
@@ -96,7 +89,7 @@
         return UnsignedInts.toString(groupNumber);
     }
 
-    /** Extension of OFPort for named groups */
+    /** Extension of OFGroup for named groups */
     static class NamedGroup extends OFGroup {
         private final String name;
 
diff --git a/java_gen/templates/of_class.java b/java_gen/templates/of_class.java
index ef927fd..7b3b762 100644
--- a/java_gen/templates/of_class.java
+++ b/java_gen/templates/of_class.java
@@ -229,9 +229,14 @@
 //:: #endif
 //:: #endfor
             //:: if msg.align:
-            // align message to ${msg.align} bytes
+            //:: if msg.length_includes_align:
+            // align message to ${msg.align} bytes (length contains aligned value)
+            bb.skipBytes(length - (bb.readerIndex() - start));
+            //:: else:
+            // align message to ${msg.align} bytes (length does not contain alignment)
             bb.skipBytes(((length + ${msg.align-1})/${msg.align} * ${msg.align} ) - length );
             //:: #endif
+            //:: #endif
 
             //:: if msg.data_members:
             //:: if os.path.exists("%s/custom/%s.Reader_normalize_stanza.java" % (template_dir, msg.name)):
@@ -327,10 +332,13 @@
 //:: if not msg.is_fixed_length:
             // update length field
             int length = bb.writerIndex() - startIndex;
-            bb.setShort(lengthIndex, length);
+            //:: if msg.align:
+            int alignedLength = ((length + ${msg.align-1})/${msg.align} * ${msg.align});
+            //:: #endif
+            bb.setShort(lengthIndex, ${"alignedLength" if msg.length_includes_align else "length"});
             //:: if msg.align:
             // align message to ${msg.align} bytes
-            bb.writeZero( ((length + ${msg.align-1})/${msg.align} * ${msg.align}) - length);
+            bb.writeZero(alignedLength - length);
             //:: #endif
 //:: #end
 
diff --git a/java_gen/templates/unit_test.java b/java_gen/templates/unit_test.java
index 5a525e4..cd85a74 100644
--- a/java_gen/templates/unit_test.java
+++ b/java_gen/templates/unit_test.java
@@ -80,6 +80,7 @@
 
         // FIXME should invoke the overall reader once implemented
         ${var_type} ${var_name}Read = ${msg.name}.READER.readFrom(input);
+        assertEquals(${msg.constant_name}_SERIALIZED.length, input.readerIndex());
 
         assertEquals(${var_name}Built, ${var_name}Read);
    }
@@ -93,6 +94,7 @@
 
        // FIXME should invoke the overall reader once implemented
        ${var_type} ${var_name} = ${msg.name}.READER.readFrom(input);
+       assertEquals(${msg.constant_name}_SERIALIZED.length, input.readerIndex());
 
        // write message again
        ChannelBuffer bb = ChannelBuffers.dynamicBuffer();
diff --git a/loxi_front_end/parser.py b/loxi_front_end/parser.py
index f2cf4b0..4a465f7 100644
--- a/loxi_front_end/parser.py
+++ b/loxi_front_end/parser.py
@@ -56,7 +56,7 @@
 type_member = P.Group(tag('type') + any_type + identifier + s('==') + integer)
 data_member = P.Group(tag('data') + any_type - identifier)
 
-struct_param_name = kw("align")
+struct_param_name = kw("align") | kw("length_includes_align")
 struct_param = P.Group(struct_param_name - s('=') - word)
 struct_param_list = P.Forward()
 struct_param_list << struct_param + P.Optional(s(',') - P.Optional(struct_param_list))
diff --git a/openflow_input/standard-1.2 b/openflow_input/standard-1.2
index a804643..cf146e0 100644
--- a/openflow_input/standard-1.2
+++ b/openflow_input/standard-1.2
@@ -603,7 +603,7 @@
     pad(4);
 };
 
-struct of_match_v3(align=8) {
+struct of_match_v3(align=8, length_includes_align=False) {
     uint16_t type == 1;
     uint16_t length;
     list(of_oxm_t) oxm_list;
@@ -694,13 +694,13 @@
     pad(4);
 };
 
-struct of_action_set_field : of_action {
+struct of_action_set_field(align=8, length_includes_align=True) : of_action {
     uint16_t type == 25;
     uint16_t len;
     of_oxm_t field;
 };
 
-struct of_action_experimenter : of_action {
+struct of_action_experimenter(align=8, length_includes_align=True) : of_action {
     uint16_t type == 65535;
     uint16_t len;
     uint32_t experimenter == ?;
diff --git a/openflow_input/standard-1.3 b/openflow_input/standard-1.3
index 29e6c76..0d4a8c5 100644
--- a/openflow_input/standard-1.3
+++ b/openflow_input/standard-1.3
@@ -730,7 +730,7 @@
 };
 
 // FIXME Does this need to be v4?
-struct of_match_v3(align=8) {
+struct of_match_v3(align=8, length_includes_align=False) {
     uint16_t type == 1;
     uint16_t length;
     list(of_oxm_t) oxm_list;
@@ -829,13 +829,13 @@
     pad(4);
 };
 
-struct of_action_set_field : of_action {
+struct of_action_set_field(align=8, length_includes_align=True) : of_action {
     uint16_t type == 25;
     uint16_t len;
     of_oxm_t field;
 };
 
-struct of_action_experimenter : of_action {
+struct of_action_experimenter(align=8, length_includes_align=True): of_action {
     uint16_t type == 65535;
     uint16_t len;
     uint32_t experimenter == ?;
diff --git a/test_data/__init__.py b/test_data/__init__.py
index 7a55c11..dc063fa 100644
--- a/test_data/__init__.py
+++ b/test_data/__init__.py
@@ -26,6 +26,7 @@
 # EPL for the specific language governing permissions and limitations
 # under the EPL.
 
+import fnmatch
 import os
 
 _test_data_dir = os.path.dirname(os.path.realpath(__file__))
@@ -45,6 +46,11 @@
                 result.append(dirname + '/' + filename)
     return sorted(result)
 
+def glob(pattern):
+    for f in list_files():
+        if fnmatch.fnmatch(f, pattern):
+            yield f
+
 def exists(name):
     return os.path.exists(os.path.join(_test_data_dir, name))
 
diff --git a/test_data/of13/action_output.data b/test_data/of13/action_output.data
index 7cd52ce..7653a30 100644
--- a/test_data/of13/action_output.data
+++ b/test_data/of13/action_output.data
@@ -6,3 +6,5 @@
 00 00 00 00 00 00 # pad
 -- python
 ofp.action.output(port=50, max_len=65535)
+-- java
+builder.setPort(OFPort.of(50)).setMaxLen(65535)
diff --git a/test_data/of13/action_set_field__eth_dst.data b/test_data/of13/action_set_field__eth_dst.data
new file mode 100644
index 0000000..833bb36
--- /dev/null
+++ b/test_data/of13/action_set_field__eth_dst.data
@@ -0,0 +1,11 @@
+-- binary
+00 19 # type
+00 10 # length
+80 00 06 06 # OXM header
+00 01 02 03 04 05 # OXM value
+00 00 # pad
+-- python
+ofp.action.set_field(field=ofp.oxm.eth_dst([0, 1, 2, 3, 4, 5]))
+-- java
+OFOxms oxms = OFFactories.getFactory(OFVersion.OF_13).oxms();
+builder.setField(oxms.ethDst(MacAddress.of("00:01:02:03:04:05")))
diff --git a/test_data/of13/action_set_field_ipv6_src.data b/test_data/of13/action_set_field__ipv6_src.data
similarity index 60%
rename from test_data/of13/action_set_field_ipv6_src.data
rename to test_data/of13/action_set_field__ipv6_src.data
index f440dee..cfa2738 100644
--- a/test_data/of13/action_set_field_ipv6_src.data
+++ b/test_data/of13/action_set_field__ipv6_src.data
@@ -5,3 +5,6 @@
 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f # OXM value
 -- python
 ofp.action.set_field(field=ofp.oxm.ipv6_src("\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"))
+-- java
+OFOxms oxms = OFFactories.getFactory(OFVersion.OF_13).oxms();
+builder.setField(oxms.ipv6Src(IPv6Address.of("0001:0203:0405:0607:0809:0a0b:0c0d:0e0f")))
diff --git a/test_data/of13/action_set_field__tcp_src.data b/test_data/of13/action_set_field__tcp_src.data
new file mode 100644
index 0000000..41c4780
--- /dev/null
+++ b/test_data/of13/action_set_field__tcp_src.data
@@ -0,0 +1,11 @@
+-- binary
+00 19 # type
+00 10 # length
+80 00 1a 02 # OXM header
+00 32 # OXM value
+00 00 00 00 00 00 # pad
+-- python
+ofp.action.set_field(field=ofp.oxm.tcp_src(50))
+-- java
+OFOxms oxms = OFFactories.getFactory(OFVersion.OF_13).oxms();
+builder.setField(oxms.tcpSrc(TransportPort.of(50)))
diff --git a/test_data/of13/action_set_field_eth_dst.data b/test_data/of13/action_set_field_eth_dst.data
deleted file mode 100644
index 1e4a971..0000000
--- a/test_data/of13/action_set_field_eth_dst.data
+++ /dev/null
@@ -1,8 +0,0 @@
--- binary
-00 19 # type
-00 10 # length
-80 00 06 06 # OXM header
-00 01 02 03 04 05 # OXM value
-00 00 # pad
--- python
-ofp.action.set_field(field=ofp.oxm.eth_dst([0, 1, 2, 3, 4, 5]))
diff --git a/test_data/of13/action_set_field_tcp_src.data b/test_data/of13/action_set_field_tcp_src.data
deleted file mode 100644
index a69c7c0..0000000
--- a/test_data/of13/action_set_field_tcp_src.data
+++ /dev/null
@@ -1,8 +0,0 @@
--- binary
-00 19 # type
-00 10 # length
-80 00 1a 02 # OXM header
-00 32 # OXM value
-00 00 00 00 00 00 # pad
--- python
-ofp.action.set_field(field=ofp.oxm.tcp_src(50))