Merge branch 'master' of github.com:floodlight/loxigen
diff --git a/java_gen/java_model.py b/java_gen/java_model.py
index 896cc99..83c89b5 100644
--- a/java_gen/java_model.py
+++ b/java_gen/java_model.py
@@ -602,6 +602,7 @@
                     JavaVirtualMember(self, "mask", java_type.generic_t),
                     JavaVirtualMember(self, "matchField", java_type.make_match_field_jtype("T")),
                     JavaVirtualMember(self, "masked", java_type.boolean),
+                    JavaVirtualMember(self, "canonical", java_type.make_oxm_jtype("T"))
                    ]
         elif self.parent_interface and self.parent_interface.startswith("OFOxm"):
             field_type = java_type.make_match_field_jtype(model.oxm_map[self.name].type_name) \
@@ -611,6 +612,8 @@
             virtual_members += [
                     JavaVirtualMember(self, "matchField", field_type),
                     JavaVirtualMember(self, "masked", java_type.boolean),
+                    JavaVirtualMember(self, "canonical", java_type.make_oxm_jtype(model.oxm_map[self.name].type_name),
+                            custom_template=lambda builder: "OFOxm{}_getCanonical.java".format(".Builder" if builder else "")),
                    ]
             if not find(lambda x: x.name == "mask", self.ir_model_members):
                 virtual_members.append(JavaVirtualMember(self, "mask", find(lambda x: x.name == "value", self.ir_model_members).java_type))
@@ -745,18 +748,21 @@
                 virtual_members += [
                     JavaVirtualMember(self, "matchField", java_type.make_match_field_jtype(oxm_entry.type_name), "MatchField.%s" % oxm_entry.value),
                     JavaVirtualMember(self, "masked", java_type.boolean, "true" if oxm_entry.masked else "false"),
-                   ]
+                    ]
             else:
                 virtual_members += [
                     JavaVirtualMember(self, "matchField", java_type.make_match_field_jtype(), "null"),
                     JavaVirtualMember(self, "masked", java_type.boolean, "false"),
-                   ]
-
+                 ]
         if not find(lambda m: m.name == "version", self.ir_model_members):
             virtual_members.append(JavaVirtualMember(self, "version", java_type.of_version, "OFVersion.%s" % self.version.constant_version))
 
         return tuple(virtual_members)
 
+    @memoize
+    def member_by_name(self, name):
+        return find(lambda m: m.name == name, self.members)
+
     def all_versions(self):
         return [ JavaOFVersion(int_version)
                  for int_version in of_g.unified[self.c_name]
@@ -983,9 +989,10 @@
 
 class JavaVirtualMember(JavaMember):
     """ Models a virtual property (member) of an openflow class that is not backed by a loxi ir member """
-    def __init__(self, msg, name, java_type, value=None):
+    def __init__(self, msg, name, java_type, value=None, custom_template=None):
         JavaMember.__init__(self, msg, name, java_type, member=None)
         self._value = value
+        self.custom_template = custom_template
 
     @property
     def is_fixed_value(self):
diff --git a/java_gen/java_type.py b/java_gen/java_type.py
index 4c39389..333efd8 100644
--- a/java_gen/java_type.py
+++ b/java_gen/java_type.py
@@ -620,6 +620,8 @@
 def make_match_field_jtype(sub_type_name="?"):
     return JType("MatchField<{}>".format(sub_type_name))
 
+def make_oxm_jtype(sub_type_name="?"):
+    return JType("OFOxm<{}>".format(sub_type_name))
 
 def list_cname_to_java_name(c_type):
     m = re.match(r'list\(of_([a-zA-Z_]+)_t\)', c_type)
@@ -628,7 +630,6 @@
     base_name = m.group(1)
     return "OF" + name_c_to_caps_camel(base_name)
 
-
 #### main entry point for conversion of LOXI types (c_types) Java types.
 # FIXME: This badly needs a refactoring
 
diff --git a/java_gen/pre-written/src/main/java/org/projectfloodlight/openflow/protocol/OFOxmList.java b/java_gen/pre-written/src/main/java/org/projectfloodlight/openflow/protocol/OFOxmList.java
index c6ba116..7f66110 100644
--- a/java_gen/pre-written/src/main/java/org/projectfloodlight/openflow/protocol/OFOxmList.java
+++ b/java_gen/pre-written/src/main/java/org/projectfloodlight/openflow/protocol/OFOxmList.java
@@ -2,7 +2,6 @@
 
 import java.util.EnumMap;
 import java.util.Iterator;
-import java.util.List;
 import java.util.Map;
 
 import org.jboss.netty.buffer.ChannelBuffer;
@@ -13,11 +12,16 @@
 import org.projectfloodlight.openflow.types.OFValueType;
 import org.projectfloodlight.openflow.types.PrimitiveSinkable;
 import org.projectfloodlight.openflow.util.ChannelUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
+import com.google.common.base.Objects;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.hash.PrimitiveSink;
 
 public class OFOxmList implements Iterable<OFOxm<?>>, Writeable, PrimitiveSinkable {
+    private static final Logger logger = LoggerFactory.getLogger(OFOxmList.class);
+
     private final Map<MatchFields, OFOxm<?>> oxmMap;
 
     public final static OFOxmList EMPTY = new OFOxmList(ImmutableMap.<MatchFields, OFOxm<?>>of());
@@ -51,7 +55,7 @@
         }
 
         public OFOxmList build() {
-            return new OFOxmList(oxmMap);
+            return OFOxmList.ofList(oxmMap.values());
         }
     }
 
@@ -60,12 +64,19 @@
         return oxmMap.values().iterator();
     }
 
-    public static OFOxmList ofList(List<OFOxm<?>> oxmList) {
+    public static OFOxmList ofList(Iterable<OFOxm<?>> oxmList) {
         Map<MatchFields, OFOxm<?>> map = new EnumMap<MatchFields, OFOxm<?>>(
                 MatchFields.class);
         for (OFOxm<?> o : oxmList) {
-            // TODO: if fully masked, ignore oxm.
-            map.put(o.getMatchField().id, o);
+            OFOxm<?> canonical = o.getCanonical();
+
+            if(logger.isDebugEnabled() && !Objects.equal(o, canonical)) {
+                logger.debug("OFOxmList: normalized non-canonical OXM {} to {}", o, canonical);
+            }
+
+            if(canonical != null)
+                map.put(canonical.getMatchField().id, canonical);
+
         }
         return new OFOxmList(map);
     }
@@ -74,8 +85,14 @@
         Map<MatchFields, OFOxm<?>> map = new EnumMap<MatchFields, OFOxm<?>>(
                 MatchFields.class);
         for (OFOxm<?> o : oxms) {
-            // TODO: if fully masked, ignore oxm.
-            map.put(o.getMatchField().id, o);
+            OFOxm<?> canonical = o.getCanonical();
+
+            if(logger.isDebugEnabled() && !Objects.equal(o, canonical)) {
+                logger.debug("OFOxmList: normalized non-canonical OXM {} to {}", o, canonical);
+            }
+
+            if(canonical != null)
+                map.put(canonical.getMatchField().id, canonical);
         }
         return new OFOxmList(map);
     }
diff --git a/java_gen/pre-written/src/main/java/org/projectfloodlight/openflow/types/ClassId.java b/java_gen/pre-written/src/main/java/org/projectfloodlight/openflow/types/ClassId.java
index b082f8b..7d7c38e 100644
--- a/java_gen/pre-written/src/main/java/org/projectfloodlight/openflow/types/ClassId.java
+++ b/java_gen/pre-written/src/main/java/org/projectfloodlight/openflow/types/ClassId.java
@@ -14,6 +14,10 @@
     private final static int NONE_VAL = 0;
     public final static ClassId NONE = new ClassId(NONE_VAL);
 
+    private final static int NO_MASK_VAL = 0xFFFFFFFF;
+    public final static ClassId NO_MASK = new ClassId(NO_MASK_VAL);
+    public final static ClassId FULL_MASK = NONE;
+
     private final int rawValue;
 
     private ClassId(final int rawValue) {
@@ -23,7 +27,8 @@
     public static ClassId of(final int raw) {
         if(raw == NONE_VAL)
             return NONE;
-
+        else if(raw == NO_MASK_VAL)
+            return NO_MASK;
         return new ClassId(raw);
     }
 
diff --git a/java_gen/pre-written/src/main/java/org/projectfloodlight/openflow/types/IPv4AddressWithMask.java b/java_gen/pre-written/src/main/java/org/projectfloodlight/openflow/types/IPv4AddressWithMask.java
index 67bbce4..f30fcbb 100644
--- a/java_gen/pre-written/src/main/java/org/projectfloodlight/openflow/types/IPv4AddressWithMask.java
+++ b/java_gen/pre-written/src/main/java/org/projectfloodlight/openflow/types/IPv4AddressWithMask.java
@@ -46,7 +46,7 @@
     public static IPv4AddressWithMask of(final String string) {
         int slashPos;
         String ip = string;
-        int maskBits = 0;
+        int maskBits = 32;
         IPv4Address maskAddress = null;
 
         // Read mask suffix
@@ -77,9 +77,12 @@
         if (maskAddress != null) {
             // Full address mask
             return IPv4AddressWithMask.of(ipv4, maskAddress);
-        } else if (maskBits == 0) {
+        } else if (maskBits == 32) {
             // No mask
             return IPv4AddressWithMask.of(ipv4, IPv4Address.NO_MASK);
+        } else if (maskBits == 0) {
+            // No mask
+            return IPv4AddressWithMask.of(ipv4, IPv4Address.FULL_MASK);
         } else {
             // With mask
             int mask = (-1) << (32 - maskBits);
diff --git a/java_gen/pre-written/src/main/java/org/projectfloodlight/openflow/types/IPv6AddressWithMask.java b/java_gen/pre-written/src/main/java/org/projectfloodlight/openflow/types/IPv6AddressWithMask.java
index 727daf6..6faf0b8 100644
--- a/java_gen/pre-written/src/main/java/org/projectfloodlight/openflow/types/IPv6AddressWithMask.java
+++ b/java_gen/pre-written/src/main/java/org/projectfloodlight/openflow/types/IPv6AddressWithMask.java
@@ -40,7 +40,7 @@
     public static IPv6AddressWithMask of(final String string) {
         int slashPos;
         String ip = string;
-        int maskBits = 0;
+        int maskBits = 128;
         IPv6Address maskAddress = null;
 
         // Read mask suffix
@@ -71,10 +71,13 @@
         if (maskAddress != null) {
             // Full address mask
             return IPv6AddressWithMask.of(ipv6, maskAddress);
-        } else if (maskBits == 0) {
+        } else if (maskBits == 128) {
             // No mask
             return IPv6AddressWithMask.of(ipv6, IPv6Address.NO_MASK);
-        } else {
+        } else if (maskBits == 0) {
+            // Entirely masked out
+            return IPv6AddressWithMask.of(ipv6, IPv6Address.FULL_MASK);
+        }else {
             // With mask
             BigInteger mask = BigInteger.ONE.negate().shiftLeft(128 - maskBits);
             byte[] maskBytesTemp = mask.toByteArray();
diff --git a/java_gen/pre-written/src/main/java/org/projectfloodlight/openflow/types/LagId.java b/java_gen/pre-written/src/main/java/org/projectfloodlight/openflow/types/LagId.java
index 6d9421a..51364e1 100644
--- a/java_gen/pre-written/src/main/java/org/projectfloodlight/openflow/types/LagId.java
+++ b/java_gen/pre-written/src/main/java/org/projectfloodlight/openflow/types/LagId.java
@@ -15,11 +15,19 @@
     private final static int NONE_VAL = 0;
     public final static LagId NONE = new LagId(NONE_VAL);
 
+    private final static int NO_MASK_VAL = 0xFFFFFFFF;
+    public final static LagId NO_MASK = new LagId(NO_MASK_VAL);
+    public final static LagId FULL_MASK = NONE;
+
     private LagId(final int rawValue) {
         this.rawValue = rawValue;
     }
 
     public static LagId of(final int raw) {
+        if(raw == NONE_VAL)
+            return NONE;
+        else if (raw == NO_MASK_VAL)
+            return NO_MASK;
         return new LagId(raw);
     }
 
diff --git a/java_gen/pre-written/src/main/java/org/projectfloodlight/openflow/types/OFBitMask128.java b/java_gen/pre-written/src/main/java/org/projectfloodlight/openflow/types/OFBitMask128.java
index 5cec233..051d573 100644
--- a/java_gen/pre-written/src/main/java/org/projectfloodlight/openflow/types/OFBitMask128.java
+++ b/java_gen/pre-written/src/main/java/org/projectfloodlight/openflow/types/OFBitMask128.java
@@ -14,6 +14,9 @@
     public static final OFBitMask128 ALL = new OFBitMask128(-1, -1);
     public static final OFBitMask128 NONE = new OFBitMask128(0, 0);
 
+    public static final OFBitMask128 NO_MASK = ALL;
+    public static final OFBitMask128 FULL_MASK = NONE;
+
     private OFBitMask128(long raw1, long raw2) {
         this.raw1 = raw1;
         this.raw2 = raw2;
diff --git a/java_gen/pre-written/src/main/java/org/projectfloodlight/openflow/types/OFBooleanValue.java b/java_gen/pre-written/src/main/java/org/projectfloodlight/openflow/types/OFBooleanValue.java
index b386e54..e276092 100644
--- a/java_gen/pre-written/src/main/java/org/projectfloodlight/openflow/types/OFBooleanValue.java
+++ b/java_gen/pre-written/src/main/java/org/projectfloodlight/openflow/types/OFBooleanValue.java
@@ -28,6 +28,9 @@
     public final static OFBooleanValue TRUE = new OFBooleanValue(true);
     public final static OFBooleanValue FALSE = new OFBooleanValue(false);
 
+    public final static OFBooleanValue NO_MASK = TRUE;
+    public final static OFBooleanValue FULL_MASK = FALSE;
+
     private final boolean value;
 
     private OFBooleanValue(boolean value) {
diff --git a/java_gen/pre-written/src/main/java/org/projectfloodlight/openflow/types/U32.java b/java_gen/pre-written/src/main/java/org/projectfloodlight/openflow/types/U32.java
index 9fafc30..7f53374 100644
--- a/java_gen/pre-written/src/main/java/org/projectfloodlight/openflow/types/U32.java
+++ b/java_gen/pre-written/src/main/java/org/projectfloodlight/openflow/types/U32.java
@@ -29,6 +29,10 @@
     private final static int ZERO_VAL = 0;
     public final static U32 ZERO = new U32(ZERO_VAL);
 
+    private static final int NO_MASK_VAL = 0xFFffFFff;
+    public final static U32 NO_MASK = new U32(NO_MASK_VAL);
+    public static final U32 FULL_MASK = ZERO;
+
     private final int raw;
 
     private U32(int raw) {
@@ -42,6 +46,8 @@
     public static U32 ofRaw(int raw) {
         if(raw == ZERO_VAL)
             return ZERO;
+        if(raw == NO_MASK_VAL)
+            return NO_MASK;
         return new U32(raw);
     }
 
diff --git a/java_gen/pre-written/src/main/java/org/projectfloodlight/openflow/types/U8.java b/java_gen/pre-written/src/main/java/org/projectfloodlight/openflow/types/U8.java
index 078a846..c644599 100644
--- a/java_gen/pre-written/src/main/java/org/projectfloodlight/openflow/types/U8.java
+++ b/java_gen/pre-written/src/main/java/org/projectfloodlight/openflow/types/U8.java
@@ -29,6 +29,10 @@
     private final static byte ZERO_VAL = 0;
     public final static U8 ZERO = new U8(ZERO_VAL);
 
+    private static final byte NO_MASK_VAL = (byte) 0xFF;
+    public static final U8 NO_MASK = new U8(NO_MASK_VAL);
+    public static final U8 FULL_MASK = ZERO;
+
     private final byte raw;
 
     private U8(byte raw) {
@@ -38,6 +42,8 @@
     public static final U8 of(short value) {
         if(value == ZERO_VAL)
             return ZERO;
+        if(value == NO_MASK_VAL)
+            return NO_MASK;
 
         return new U8(t(value));
     }
diff --git a/java_gen/pre-written/src/test/java/org/projectfloodlight/openflow/types/IPv4AddressTest.java b/java_gen/pre-written/src/test/java/org/projectfloodlight/openflow/types/IPv4AddressTest.java
index 4f3f968..38d60b3 100644
--- a/java_gen/pre-written/src/test/java/org/projectfloodlight/openflow/types/IPv4AddressTest.java
+++ b/java_gen/pre-written/src/test/java/org/projectfloodlight/openflow/types/IPv4AddressTest.java
@@ -2,8 +2,10 @@
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
 import static org.junit.Assert.fail;
 
+import org.hamcrest.CoreMatchers;
 import org.jboss.netty.buffer.ChannelBuffers;
 import org.junit.Test;
 import org.projectfloodlight.openflow.exceptions.OFParseError;
@@ -47,20 +49,23 @@
                             "192.168.130.140/255.255.192.0",
                             "127.0.0.1/8",
                             "8.8.8.8",
+                            "0.0.0.0/0"
     };
 
     boolean[] hasMask = {
                          true,
                          true,
                          true,
-                         false
+                         false,
+                         true
     };
 
     byte[][][] ipsWithMaskValues = {
                              new byte[][] { new byte[] { (byte)0x01, (byte)0x02, (byte)0x03, (byte)0x04 }, new byte[] { (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0x00 } },
                              new byte[][] { new byte[] { (byte)0xC0, (byte)0xA8, (byte)0x82, (byte)0x8C }, new byte[] { (byte)0xFF, (byte)0xFF, (byte)0xC0, (byte)0x00 } },
                              new byte[][] { new byte[] { (byte)0x7F, (byte)0x00, (byte)0x00, (byte)0x01 }, new byte[] { (byte)0xFF, (byte)0x00, (byte)0x00, (byte)0x00 } },
-                             new byte[][] { new byte[] { (byte)0x08, (byte)0x08, (byte)0x08, (byte)0x08 }, null }
+                             new byte[][] { new byte[] { (byte)0x08, (byte)0x08, (byte)0x08, (byte)0x08 }, null },
+                             new byte[][] { new byte[] { (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00 }, new byte[] { (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00 } }
     };
 
 
@@ -123,7 +128,8 @@
                 }
 
                 assertArrayEquals(ipBytes, value.getValue().getBytes());
-                assertArrayEquals(ipsWithMaskValues[i][1], value.getMask().getBytes());
+                assertThat(String.format("Byte comparison for mask of %s (%s)", ipsWithMask[i], value),
+                        value.getMask().getBytes(), CoreMatchers.equalTo(ipsWithMaskValues[i][1]));
             }
         }
     }
diff --git a/java_gen/pre-written/src/test/java/org/projectfloodlight/openflow/types/IPv6AddressTest.java b/java_gen/pre-written/src/test/java/org/projectfloodlight/openflow/types/IPv6AddressTest.java
index 3fcfe23..672a0e1 100644
--- a/java_gen/pre-written/src/test/java/org/projectfloodlight/openflow/types/IPv6AddressTest.java
+++ b/java_gen/pre-written/src/test/java/org/projectfloodlight/openflow/types/IPv6AddressTest.java
@@ -2,16 +2,20 @@
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
 import static org.junit.Assert.fail;
 
 import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.net.UnknownHostException;
 
+import org.hamcrest.CoreMatchers;
 import org.jboss.netty.buffer.ChannelBuffers;
 import org.junit.Test;
 import org.projectfloodlight.openflow.exceptions.OFParseError;
 
+import com.google.common.io.BaseEncoding;
+
 public class IPv6AddressTest {
 
     String[] testStrings = {
@@ -21,61 +25,83 @@
             "1:2:3:4:5:6:7:8"
     };
 
-    String[] ipsWithMask = {
-                            "1::1/80",
-                            "1:2:3:4::/ffff:ffff:ffff:ff00::",
-                            "ffff:ffee:1::/ff00:ff00:ff00:ff00::",
-                            "8:8:8:8:8:8:8:8",
-    };
 
-    byte[][] masks = {
-                    new byte[] { (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff,
-                                 (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff,
-                                 (byte)0xff, (byte)0xff, (byte)0x00, (byte)0x00,
-                                 (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00 },
-                    new byte[] { (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff,
-                                 (byte)0xff, (byte)0xff, (byte)0xff, (byte)0x00,
-                                 (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
-                                 (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00 },
-                    new byte[] { (byte)0xff, (byte)0x00, (byte)0xff, (byte)0x00,
-                                 (byte)0xff, (byte)0x00, (byte)0xff, (byte)0x00,
-                                 (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
-                                 (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00 },
-                    new byte[] { (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
-                                 (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
-                                 (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
-                                 (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00 }
-    };
+    private final BaseEncoding hex = BaseEncoding.base16().omitPadding().lowerCase();
 
-    boolean[] hasMask = {
-                         true,
-                         true,
-                         true,
-                         false
+    private class WithMaskTaskCase {
+        final String input;
+        boolean hasMask;
+        byte[] expectedMask = hex.decode("ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff".replaceAll(" ", ""));
+
+        public WithMaskTaskCase(String input) {
+            super();
+            this.input = input;
+        }
+
+        public WithMaskTaskCase hasMask() {
+            this.hasMask = true;
+            return this;
+        }
+
+        public WithMaskTaskCase maskLength(int l) {
+            this.hasMask = l < 128;
+            for(int j=0; j < 8; j++) {
+                int pos = j * 8;
+                int index = Math.max(0, Math.min(7, pos - l));
+
+                if(index == 0) {
+                    expectedMask[j] = 0;
+                } else {
+                    expectedMask[j] = (byte) (-1 << index);
+                }
+            }
+            return this;
+        }
+
+        public WithMaskTaskCase maskHex(String string) {
+            string = string.replaceAll(" ", "");
+            this.hasMask = true;
+            expectedMask = hex.decode(string);
+            return this;
+        }
+
+    }
+
+    WithMaskTaskCase[] withMasks = new WithMaskTaskCase[] {
+            new WithMaskTaskCase("1::1/80")
+                .maskHex("ff ff ff ff ff ff ff ff ff ff 00 00 00 00 00 00"),
+
+            new WithMaskTaskCase("ffff:ffee:1::/ff00:ff00:ff00:ff00::")
+                .maskHex("ff 00 ff 00 ff 00 ff 00 00 00 00 00 00 00 00 00"),
+            new WithMaskTaskCase("8:8:8:8:8:8:8:8"),
+            new WithMaskTaskCase("8:8:8:8:8:8:8:8"),
+            new WithMaskTaskCase("1:2:3:4:5:6:7:8/128"),
+            new WithMaskTaskCase("::/0")
+                .maskHex("00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00")
     };
 
     @Test
     public void testMasked() throws UnknownHostException {
-        for(int i=0; i < ipsWithMask.length; i++ ) {
-            IPv6AddressWithMask value = IPv6AddressWithMask.of(ipsWithMask[i]);
-            if (!hasMask[i]) {
+        for(WithMaskTaskCase w: withMasks) {
+            IPv6AddressWithMask value = IPv6AddressWithMask.of(w.input);
+            if (!w.hasMask) {
                 IPv6Address ip = value.getValue();
-                InetAddress inetAddress = InetAddress.getByName(ipsWithMask[i]);
+                InetAddress inetAddress = InetAddress.getByName(w.input.split("/")[0]);
 
                 assertArrayEquals(ip.getBytes(), inetAddress.getAddress());
-                assertEquals(ipsWithMask[i], ip.toString());
-            } else if (value instanceof IPv6AddressWithMask && hasMask[i]) {
-                InetAddress inetAddress = InetAddress.getByName(ipsWithMask[i].substring(0, ipsWithMask[i].indexOf('/')));
+                assertEquals(w.input.split("/")[0], ip.toString());
+            } else {
+                InetAddress inetAddress = InetAddress.getByName(w.input.substring(0, w.input.indexOf('/')));
 
                 byte[] address = inetAddress.getAddress();
                 assertEquals(address.length, value.getValue().getBytes().length);
 
                 for (int j = 0; j < address.length; j++) {
-                    address[j] &= masks[i][j];
+                    address[j] &= w.expectedMask[j];
                 }
 
-                assertArrayEquals(value.getValue().getBytes(), address);
-                assertArrayEquals(masks[i], value.getMask().getBytes());
+                assertThat("Address bytes for input " + w.input + ", value=" + value, value.getValue().getBytes(), CoreMatchers.equalTo(address));
+                assertThat("mask check for input " + w.input + ", value=" + value, value.getMask().getBytes(), CoreMatchers.equalTo(w.expectedMask));
             }
         }
     }
diff --git a/java_gen/pre-written/src/test/java/org/projectfloodlight/protocol/OFOxmListTest.java b/java_gen/pre-written/src/test/java/org/projectfloodlight/protocol/OFOxmListTest.java
new file mode 100644
index 0000000..39e8c0c
--- /dev/null
+++ b/java_gen/pre-written/src/test/java/org/projectfloodlight/protocol/OFOxmListTest.java
@@ -0,0 +1,41 @@
+package org.projectfloodlight.protocol;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+
+import org.hamcrest.CoreMatchers;
+import org.junit.Before;
+import org.junit.Test;
+import org.projectfloodlight.openflow.protocol.OFFactories;
+import org.projectfloodlight.openflow.protocol.OFOxmList;
+import org.projectfloodlight.openflow.protocol.OFVersion;
+import org.projectfloodlight.openflow.protocol.match.MatchField;
+import org.projectfloodlight.openflow.protocol.oxm.OFOxmIpv6DstMasked;
+import org.projectfloodlight.openflow.protocol.oxm.OFOxmIpv6SrcMasked;
+import org.projectfloodlight.openflow.protocol.oxm.OFOxms;
+import org.projectfloodlight.openflow.types.IPv6AddressWithMask;
+
+public class OFOxmListTest {
+    private OFOxms oxms;
+
+    @Before
+    public void setup() {
+        oxms = OFFactories.getFactory(OFVersion.OF_13).oxms();
+    }
+
+    @Test
+    public void testCanonicalize() {
+        OFOxmList.Builder builder = new OFOxmList.Builder();
+        IPv6AddressWithMask fullMasked = IPv6AddressWithMask.of("::/0");
+        OFOxmIpv6DstMasked  fullMaskedOxm = oxms.ipv6DstMasked(fullMasked.getValue(), fullMasked.getMask());
+        builder.set(fullMaskedOxm);
+
+        IPv6AddressWithMask address= IPv6AddressWithMask.of("1:2:3:4:5:6::8");
+        OFOxmIpv6SrcMasked  addressSrcOxm = oxms.ipv6SrcMasked(address.getValue(), address.getMask());
+        builder.set(addressSrcOxm);
+
+        OFOxmList list = builder.build();
+        assertThat(list.get(MatchField.IPV6_DST), CoreMatchers.nullValue());
+        assertFalse(list.get(MatchField.IPV6_SRC).isMasked());
+    }
+}
diff --git a/java_gen/pre-written/src/test/java/org/projectfloodlight/protocol/OFOxmTest.java b/java_gen/pre-written/src/test/java/org/projectfloodlight/protocol/OFOxmTest.java
new file mode 100644
index 0000000..8482886
--- /dev/null
+++ b/java_gen/pre-written/src/test/java/org/projectfloodlight/protocol/OFOxmTest.java
@@ -0,0 +1,62 @@
+package org.projectfloodlight.protocol;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import org.hamcrest.CoreMatchers;
+import org.junit.Before;
+import org.junit.Test;
+import org.projectfloodlight.openflow.protocol.OFFactories;
+import org.projectfloodlight.openflow.protocol.OFVersion;
+import org.projectfloodlight.openflow.protocol.oxm.OFOxm;
+import org.projectfloodlight.openflow.protocol.oxm.OFOxmIpv4Src;
+import org.projectfloodlight.openflow.protocol.oxm.OFOxmIpv4SrcMasked;
+import org.projectfloodlight.openflow.protocol.oxm.OFOxms;
+import org.projectfloodlight.openflow.types.IPv4Address;
+import org.projectfloodlight.openflow.types.IPv4AddressWithMask;
+
+public class OFOxmTest {
+    private OFOxms oxms;
+
+    @Before
+    public void setup() {
+        oxms = OFFactories.getFactory(OFVersion.OF_13).oxms();
+    }
+
+    @Test
+    public void testGetCanonicalFullMask() {
+        IPv4AddressWithMask empty = IPv4AddressWithMask.of("0.0.0.0/0");
+        assertEquals(IPv4Address.FULL_MASK, empty.getMask());
+        OFOxmIpv4SrcMasked ipv4SrcMasked = oxms.ipv4SrcMasked(empty.getValue(), empty.getMask());
+        // canonicalize should remove /0
+        assertNull(ipv4SrcMasked.getCanonical());
+    }
+
+    @Test
+    public void testGetCanonicalNoMask() {
+        IPv4AddressWithMask fullIp = IPv4AddressWithMask.of("1.2.3.4/32");
+        assertEquals(IPv4Address.NO_MASK, fullIp.getMask());
+        OFOxmIpv4SrcMasked ipv4SrcMasked = oxms.ipv4SrcMasked(fullIp.getValue(), fullIp.getMask());
+        assertTrue(ipv4SrcMasked.isMasked());
+        assertEquals(IPv4Address.NO_MASK, ipv4SrcMasked.getMask());
+
+        // canonicalize should convert the masked oxm to the non-masked one
+        OFOxm<IPv4Address> canonical = ipv4SrcMasked.getCanonical();
+        assertThat(canonical, CoreMatchers.instanceOf(OFOxmIpv4Src.class));
+        assertFalse(canonical.isMasked());
+    }
+
+    @Test
+    public void testGetCanonicalNormalMask() {
+        IPv4AddressWithMask ip = IPv4AddressWithMask.of("1.2.3.0/24");
+        OFOxmIpv4SrcMasked ipv4SrcMasked = oxms.ipv4SrcMasked(ip.getValue(), ip.getMask());
+        assertTrue(ipv4SrcMasked.isMasked());
+
+        // canonicalize should convert the masked oxm to the non-masked one
+        OFOxm<IPv4Address> canonical = ipv4SrcMasked.getCanonical();
+        assertEquals(ipv4SrcMasked, canonical);
+    }
+}
diff --git a/java_gen/templates/_field_accessors.java b/java_gen/templates/_field_accessors.java
index c7aa3a0..030388c 100644
--- a/java_gen/templates/_field_accessors.java
+++ b/java_gen/templates/_field_accessors.java
@@ -1,10 +1,13 @@
 //:: import os
 //:: for prop in msg.interface.members:
-//:: getter_template_file_name = "%s/custom/%s_%s.java" % (template_dir, msg.name if not builder else msg.name + '.Builder', prop.getter_name)
-//:: if os.path.exists(getter_template_file_name):
-//:: include(getter_template_file_name, msg=msg, builder=builder, has_parent=has_parent)
-
-//:: else:
+//::    if hasattr(prop, "custom_template") and prop.custom_template != None:
+//::        getter_template_file_name = "%s/custom/%s" % (template_dir, prop.custom_template(builder=builder))
+//::    else:
+//::        getter_template_file_name = "%s/custom/%s_%s.java" % (template_dir, msg.name if not builder else msg.name + '.Builder', prop.getter_name)
+//::    #endif
+//::    if os.path.exists(getter_template_file_name):
+//::        include(getter_template_file_name, msg=msg, builder=builder, has_parent=has_parent, prop=prop)
+//::    else:
     @Override
     public ${prop.java_type.public_type} ${prop.getter_name}()${ "" if prop in msg.members else "throws UnsupportedOperationException"} {
 //:: if prop in msg.members:
diff --git a/java_gen/templates/custom/OFMatchV3Ver13.Builder.java b/java_gen/templates/custom/OFMatchV3Ver13.Builder.java
index 48f8b70..79cbdbc 100644
--- a/java_gen/templates/custom/OFMatchV3Ver13.Builder.java
+++ b/java_gen/templates/custom/OFMatchV3Ver13.Builder.java
@@ -1,13 +1,13 @@
 
     private OFOxmList.Builder oxmListBuilder;
 
-    private synchronized void initBuilder() {
+    private void initBuilder() {
         if (oxmListBuilder != null)
             return;
         oxmListBuilder = new OFOxmList.Builder();
     }
 
-    private synchronized void updateOxmList() {
+    private void updateOxmList() {
         this.oxmList = this.oxmListBuilder.build();
         this.oxmListSet = true;
     }
@@ -21,7 +21,7 @@
     }
 
     @Override
-    public synchronized <F extends OFValueType<F>> F get(MatchField<F> field)
+    public <F extends OFValueType<F>> F get(MatchField<F> field)
             throws UnsupportedOperationException {
         OFOxm<F> value = getOxm(field);
         if (value == null)
@@ -30,7 +30,7 @@
     }
 
     @Override
-    public synchronized <F extends OFValueType<F>> Masked<F> getMasked(MatchField<F> field)
+    public <F extends OFValueType<F>> Masked<F> getMasked(MatchField<F> field)
             throws UnsupportedOperationException {
         OFOxm<F> value = getOxm(field);
         if (value == null || !value.isMasked())
@@ -50,25 +50,25 @@
     }
 
     @Override
-    public synchronized boolean isExact(MatchField<?> field) {
+    public boolean isExact(MatchField<?> field) {
         OFOxm<?> value = getOxm(field);
         return (value != null && !value.isMasked());
     }
 
     @Override
-    public synchronized boolean isFullyWildcarded(MatchField<?> field) {
+    public boolean isFullyWildcarded(MatchField<?> field) {
         OFOxm<?> value = getOxm(field);
         return (value == null);
     }
 
     @Override
-    public synchronized boolean isPartiallyMasked(MatchField<?> field) {
+    public boolean isPartiallyMasked(MatchField<?> field) {
         OFOxm<?> value = getOxm(field);
         return (value != null && value.isMasked());
     }
 
     @Override
-    public synchronized <F extends OFValueType<F>> Match.Builder setExact(
+    public <F extends OFValueType<F>> Match.Builder setExact(
             MatchField<F> field, F value) {
         initBuilder();
         OFOxm<F> oxm = OFFactories.getFactory(OFVersion.OF_13).oxms().fromValue(value, field);
@@ -78,7 +78,7 @@
     }
 
     @Override
-    public synchronized <F extends OFValueType<F>> Match.Builder setMasked(
+    public <F extends OFValueType<F>> Match.Builder setMasked(
             MatchField<F> field, F value, F mask) {
         initBuilder();
         OFOxm<F> oxm = OFFactories.getFactory(OFVersion.OF_13).oxms().fromValueAndMask(value, mask, field);
@@ -88,7 +88,7 @@
     }
 
     @Override
-    public synchronized <F extends OFValueType<F>> Match.Builder setMasked(
+    public <F extends OFValueType<F>> Match.Builder setMasked(
             MatchField<F> field, Masked<F> valueWithMask) {
         initBuilder();
         OFOxm<F> oxm = OFFactories.getFactory(OFVersion.OF_13).oxms().fromMasked(valueWithMask, field);
@@ -98,7 +98,7 @@
     }
 
     @Override
-    public synchronized <F extends OFValueType<F>> Match.Builder wildcard(MatchField<F> field) {
+    public <F extends OFValueType<F>> Match.Builder wildcard(MatchField<F> field) {
         initBuilder();
         this.oxmListBuilder.unset(field);
         updateOxmList();
diff --git a/java_gen/templates/custom/OFOxm_getCanonical.java b/java_gen/templates/custom/OFOxm_getCanonical.java
new file mode 100644
index 0000000..6681870
--- /dev/null
+++ b/java_gen/templates/custom/OFOxm_getCanonical.java
@@ -0,0 +1,17 @@
+//:: import re
+    public ${prop.java_type.public_type} getCanonical() {
+        //:: if not msg.member_by_name("masked").value == "true":
+        // exact match OXM is always canonical
+        return this;
+        //:: else:
+        //:: mask_type = msg.member_by_name("mask").java_type.public_type
+        if (${mask_type}.NO_MASK.equals(mask)) {
+            //:: unmasked = re.sub(r'(.*)Masked(Ver.*)', r'\1\2', msg.name)
+            return new ${unmasked}(value);
+        } else if(${mask_type}.FULL_MASK.equals(mask)) {
+            return null;
+        } else {
+            return this;
+        }
+        //:: #endif
+    }