Implemented convenient builders of BMv2 extension selectors and
treatments. 

Match and action parameters can now be built from primitive data types
(short, int, long or byte[]) which are then casted automatically
according to a given BMv2 configuration. Also, simplified demo
applications code / structure.

Change-Id: Ia5bebf62301c73c0b20cf6a4ddfb74165889106f
diff --git a/apps/bmv2-demo/ecmp/src/main/java/org/onosproject/bmv2/demo/app/ecmp/EcmpFabricApp.java b/apps/bmv2-demo/ecmp/src/main/java/org/onosproject/bmv2/demo/app/ecmp/EcmpFabricApp.java
index e028458..ab42d2f 100644
--- a/apps/bmv2-demo/ecmp/src/main/java/org/onosproject/bmv2/demo/app/ecmp/EcmpFabricApp.java
+++ b/apps/bmv2-demo/ecmp/src/main/java/org/onosproject/bmv2/demo/app/ecmp/EcmpFabricApp.java
@@ -19,11 +19,14 @@
 import com.eclipsesource.json.Json;
 import com.eclipsesource.json.JsonObject;
 import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.felix.scr.annotations.Component;
 import org.onosproject.bmv2.api.context.Bmv2Configuration;
 import org.onosproject.bmv2.api.context.Bmv2DefaultConfiguration;
 import org.onosproject.bmv2.api.context.Bmv2DeviceContext;
+import org.onosproject.bmv2.api.runtime.Bmv2ExtensionSelector;
+import org.onosproject.bmv2.api.runtime.Bmv2ExtensionTreatment;
 import org.onosproject.bmv2.demo.app.common.AbstractUpgradableFabricApp;
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.Host;
@@ -46,14 +49,13 @@
 import java.util.Collection;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 import java.util.stream.Collectors;
 
 import static java.util.stream.Collectors.toSet;
 import static org.onlab.packet.EthType.EtherType.IPV4;
-import static org.onosproject.bmv2.demo.app.ecmp.EcmpGroupTreatmentBuilder.groupIdOf;
-import static org.onosproject.bmv2.demo.app.ecmp.EcmpInterpreter.ECMP_GROUP_TABLE;
-import static org.onosproject.bmv2.demo.app.ecmp.EcmpInterpreter.TABLE0;
+import static org.onosproject.bmv2.demo.app.ecmp.EcmpInterpreter.*;
 
 /**
  * Implementation of an upgradable fabric app for the ECMP configuration.
@@ -69,6 +71,8 @@
     private static final EcmpInterpreter ECMP_INTERPRETER = new EcmpInterpreter();
     protected static final Bmv2DeviceContext ECMP_CONTEXT = new Bmv2DeviceContext(ECMP_CONFIGURATION, ECMP_INTERPRETER);
 
+    private static final Map<DeviceId, Map<Set<PortNumber>, Short>> DEVICE_GROUP_ID_MAP = Maps.newHashMap();
+
     public EcmpFabricApp() {
         super(APP_NAME, MODEL_NAME, ECMP_CONTEXT);
     }
@@ -211,10 +215,7 @@
         Iterator<PortNumber> portIterator = fabricPorts.iterator();
         List<FlowRule> rules = Lists.newArrayList();
         for (short i = 0; i < groupSize; i++) {
-            ExtensionSelector extSelector = new EcmpGroupTableSelectorBuilder()
-                    .withGroupId(groupId)
-                    .withSelector(i)
-                    .build();
+            ExtensionSelector extSelector = buildEcmpSelector(groupId, i);
             FlowRule rule = flowRuleBuilder(deviceId, ECMP_GROUP_TABLE)
                     .withSelector(
                             DefaultTrafficSelector.builder()
@@ -228,14 +229,37 @@
             rules.add(rule);
         }
 
-        ExtensionTreatment extTreatment = new EcmpGroupTreatmentBuilder()
-                .withGroupId(groupId)
-                .withGroupSize(groupSize)
-                .build();
+        ExtensionTreatment extTreatment = buildEcmpTreatment(groupId, groupSize);
 
         return Pair.of(extTreatment, rules);
     }
 
+    private Bmv2ExtensionTreatment buildEcmpTreatment(int groupId, int groupSize) {
+        return Bmv2ExtensionTreatment.builder()
+                .forConfiguration(ECMP_CONTEXT.configuration())
+                .setActionName(ECMP_GROUP)
+                .addParameter(GROUP_ID, groupId)
+                .addParameter(GROUP_SIZE, groupSize)
+                .build();
+    }
+
+    private Bmv2ExtensionSelector buildEcmpSelector(int groupId, int selector) {
+        return Bmv2ExtensionSelector.builder()
+                .forConfiguration(ECMP_CONTEXT.configuration())
+                .matchExact(ECMP_METADATA, GROUP_ID, groupId)
+                .matchExact(ECMP_METADATA, SELECTOR, selector)
+                .build();
+    }
+
+
+    public int groupIdOf(DeviceId deviceId, Set<PortNumber> ports) {
+        DEVICE_GROUP_ID_MAP.putIfAbsent(deviceId, Maps.newHashMap());
+        // Counts the number of unique portNumber sets for each deviceId.
+        // Each distinct set of portNumbers will have a unique ID.
+        return DEVICE_GROUP_ID_MAP.get(deviceId).computeIfAbsent(ports, (pp) ->
+                (short) (DEVICE_GROUP_ID_MAP.get(deviceId).size() + 1));
+    }
+
     private static Bmv2Configuration loadConfiguration() {
         try {
             JsonObject json = Json.parse(new BufferedReader(new InputStreamReader(
diff --git a/apps/bmv2-demo/ecmp/src/main/java/org/onosproject/bmv2/demo/app/ecmp/EcmpGroupTableSelectorBuilder.java b/apps/bmv2-demo/ecmp/src/main/java/org/onosproject/bmv2/demo/app/ecmp/EcmpGroupTableSelectorBuilder.java
deleted file mode 100644
index e9ce0cf..0000000
--- a/apps/bmv2-demo/ecmp/src/main/java/org/onosproject/bmv2/demo/app/ecmp/EcmpGroupTableSelectorBuilder.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright 2016-present Open Networking Laboratory
- *
- * Licensed 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.onosproject.bmv2.demo.app.ecmp;
-
-import com.google.common.collect.ImmutableMap;
-import org.onlab.util.ImmutableByteSequence;
-import org.onosproject.bmv2.api.context.Bmv2HeaderTypeModel;
-import org.onosproject.bmv2.api.runtime.Bmv2ExactMatchParam;
-import org.onosproject.bmv2.api.runtime.Bmv2ExtensionSelector;
-import org.onosproject.bmv2.api.utils.Bmv2TranslatorUtils;
-import org.onosproject.net.flow.criteria.ExtensionSelector;
-
-import static org.onosproject.bmv2.api.utils.Bmv2TranslatorUtils.fitByteSequence;
-import static org.onosproject.bmv2.demo.app.ecmp.EcmpFabricApp.ECMP_CONTEXT;
-import static org.onosproject.bmv2.demo.app.ecmp.EcmpInterpreter.*;
-
-/**
- * Builder of ECMP group table extension selector.
- */
-public class EcmpGroupTableSelectorBuilder {
-
-    private int groupId;
-    private int selector;
-
-    /**
-     * Sets the ECMP group ID.
-     *
-     * @param groupId an integer value
-     * @return this
-     */
-    public EcmpGroupTableSelectorBuilder withGroupId(int groupId) {
-        this.groupId = groupId;
-        return this;
-    }
-
-    /**
-     * Sets the ECMP selector.
-     *
-     * @param selector an integer value
-     * @return this
-     */
-    public EcmpGroupTableSelectorBuilder withSelector(int selector) {
-        this.selector = selector;
-        return this;
-    }
-
-    /**
-     * Returns a new extension selector.
-     *
-     * @return an extension selector
-     */
-    public ExtensionSelector build() {
-        Bmv2HeaderTypeModel headerTypeModel = ECMP_CONTEXT.configuration().headerType(ECMP_METADATA_T);
-        int groupIdBitWidth = headerTypeModel.field(GROUP_ID).bitWidth();
-        int selectorBitWidth = headerTypeModel.field(SELECTOR).bitWidth();
-
-        try {
-            ImmutableByteSequence groupIdBs = fitByteSequence(ImmutableByteSequence.copyFrom(groupId),
-                                                              groupIdBitWidth);
-            ImmutableByteSequence selectorBs = fitByteSequence(ImmutableByteSequence.copyFrom(selector),
-                                                               selectorBitWidth);
-
-            Bmv2ExactMatchParam groupIdMatch = new Bmv2ExactMatchParam(groupIdBs);
-            Bmv2ExactMatchParam hashMatch = new Bmv2ExactMatchParam(selectorBs);
-
-            return new Bmv2ExtensionSelector(ImmutableMap.of(
-                    ECMP_METADATA + "." + GROUP_ID, groupIdMatch,
-                    ECMP_METADATA + "." + SELECTOR, hashMatch));
-
-        } catch (Bmv2TranslatorUtils.ByteSequenceFitException e) {
-            throw new RuntimeException(e);
-        }
-    }
-}
diff --git a/apps/bmv2-demo/ecmp/src/main/java/org/onosproject/bmv2/demo/app/ecmp/EcmpGroupTreatmentBuilder.java b/apps/bmv2-demo/ecmp/src/main/java/org/onosproject/bmv2/demo/app/ecmp/EcmpGroupTreatmentBuilder.java
deleted file mode 100644
index eee8c59..0000000
--- a/apps/bmv2-demo/ecmp/src/main/java/org/onosproject/bmv2/demo/app/ecmp/EcmpGroupTreatmentBuilder.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright 2016-present Open Networking Laboratory
- *
- * Licensed 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.onosproject.bmv2.demo.app.ecmp;
-
-import com.google.common.collect.Maps;
-import org.onlab.util.ImmutableByteSequence;
-import org.onosproject.bmv2.api.context.Bmv2ActionModel;
-import org.onosproject.bmv2.api.runtime.Bmv2Action;
-import org.onosproject.bmv2.api.runtime.Bmv2ExtensionTreatment;
-import org.onosproject.bmv2.api.utils.Bmv2TranslatorUtils;
-import org.onosproject.net.DeviceId;
-import org.onosproject.net.PortNumber;
-import org.onosproject.net.flow.instructions.ExtensionTreatment;
-
-import java.util.Map;
-import java.util.Set;
-
-import static org.onosproject.bmv2.api.utils.Bmv2TranslatorUtils.fitByteSequence;
-import static org.onosproject.bmv2.demo.app.ecmp.EcmpFabricApp.ECMP_CONTEXT;
-import static org.onosproject.bmv2.demo.app.ecmp.EcmpInterpreter.*;
-
-/**
- * Builder of ECMP extension treatments.
- */
-public class EcmpGroupTreatmentBuilder {
-
-    private static final Map<DeviceId, Map<Set<PortNumber>, Short>> DEVICE_GROUP_ID_MAP = Maps.newHashMap();
-    private int groupId;
-    private int groupSize;
-
-    /**
-     * Sets the group ID.
-     *
-     * @param groupId an integer value
-     * @return this
-     */
-    public EcmpGroupTreatmentBuilder withGroupId(int groupId) {
-        this.groupId = groupId;
-        return this;
-    }
-
-    /**
-     * Sets the group size.
-     *
-     * @param groupSize an integer value
-     * @return this
-     */
-    public EcmpGroupTreatmentBuilder withGroupSize(int groupSize) {
-        this.groupSize = groupSize;
-        return this;
-    }
-
-    /**
-     * Returns a new extension treatment.
-     *
-     * @return an extension treatment
-     */
-    public ExtensionTreatment build() {
-        Bmv2ActionModel actionModel = ECMP_CONTEXT.configuration().action(ECMP_GROUP);
-        int groupIdBitWidth = actionModel.runtimeData(GROUP_ID).bitWidth();
-        int groupSizeBitWidth = actionModel.runtimeData(GROUP_SIZE).bitWidth();
-
-        try {
-            ImmutableByteSequence groupIdBs = fitByteSequence(ImmutableByteSequence.copyFrom(groupId), groupIdBitWidth);
-            ImmutableByteSequence groupSizeBs = fitByteSequence(ImmutableByteSequence.copyFrom(groupSize),
-                                                                groupSizeBitWidth);
-
-            return new Bmv2ExtensionTreatment(Bmv2Action.builder()
-                                                      .withName(ECMP_GROUP)
-                                                      .addParameter(groupIdBs)
-                                                      .addParameter(groupSizeBs)
-                                                      .build());
-
-        } catch (Bmv2TranslatorUtils.ByteSequenceFitException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    /**
-     * Returns a group ID for the given device and set of ports.
-     *
-     * @param deviceId a device ID
-     * @param ports a set of ports
-     * @return an integer value
-     */
-    public static int groupIdOf(DeviceId deviceId, Set<PortNumber> ports) {
-        DEVICE_GROUP_ID_MAP.putIfAbsent(deviceId, Maps.newHashMap());
-        // Counts the number of unique portNumber sets for each deviceId.
-        // Each distinct set of portNumbers will have a unique ID.
-        return DEVICE_GROUP_ID_MAP.get(deviceId).computeIfAbsent(ports, (pp) ->
-                (short) (DEVICE_GROUP_ID_MAP.get(deviceId).size() + 1));
-    }
-}
diff --git a/apps/bmv2-demo/ecmp/src/main/java/org/onosproject/bmv2/demo/app/ecmp/EcmpInterpreter.java b/apps/bmv2-demo/ecmp/src/main/java/org/onosproject/bmv2/demo/app/ecmp/EcmpInterpreter.java
index 5585a64..c5ea2ae 100644
--- a/apps/bmv2-demo/ecmp/src/main/java/org/onosproject/bmv2/demo/app/ecmp/EcmpInterpreter.java
+++ b/apps/bmv2-demo/ecmp/src/main/java/org/onosproject/bmv2/demo/app/ecmp/EcmpInterpreter.java
@@ -37,7 +37,6 @@
  */
 public class EcmpInterpreter implements Bmv2Interpreter {
 
-    protected static final String ECMP_METADATA_T = "ecmp_metadata_t";
     protected static final String ECMP_METADATA = "ecmp_metadata";
     protected static final String SELECTOR = "selector";
     protected static final String GROUP_ID = "groupId";
diff --git a/apps/bmv2-demo/wcmp/src/main/java/org/onosproject/bmv2/demo/app/wcmp/WcmpFabricApp.java b/apps/bmv2-demo/wcmp/src/main/java/org/onosproject/bmv2/demo/app/wcmp/WcmpFabricApp.java
index db7567e..f86e5f9 100644
--- a/apps/bmv2-demo/wcmp/src/main/java/org/onosproject/bmv2/demo/app/wcmp/WcmpFabricApp.java
+++ b/apps/bmv2-demo/wcmp/src/main/java/org/onosproject/bmv2/demo/app/wcmp/WcmpFabricApp.java
@@ -18,6 +18,7 @@
 
 import com.eclipsesource.json.Json;
 import com.eclipsesource.json.JsonObject;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
@@ -30,6 +31,8 @@
 import org.onosproject.bmv2.api.context.Bmv2DeviceContext;
 import org.onosproject.bmv2.api.runtime.Bmv2Action;
 import org.onosproject.bmv2.api.runtime.Bmv2DeviceAgent;
+import org.onosproject.bmv2.api.runtime.Bmv2ExtensionSelector;
+import org.onosproject.bmv2.api.runtime.Bmv2ExtensionTreatment;
 import org.onosproject.bmv2.api.runtime.Bmv2RuntimeException;
 import org.onosproject.bmv2.api.service.Bmv2Controller;
 import org.onosproject.bmv2.demo.app.common.AbstractUpgradableFabricApp;
@@ -50,18 +53,19 @@
 import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.InputStreamReader;
+import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.stream.Collectors;
 
+import static java.util.stream.Collectors.toList;
 import static java.util.stream.Collectors.toSet;
 import static org.onlab.packet.EthType.EtherType.IPV4;
-import static org.onosproject.bmv2.demo.app.wcmp.WcmpGroupTreatmentBuilder.groupIdOf;
-import static org.onosproject.bmv2.demo.app.wcmp.WcmpGroupTreatmentBuilder.toPrefixLengths;
-import static org.onosproject.bmv2.demo.app.wcmp.WcmpInterpreter.TABLE0;
-import static org.onosproject.bmv2.demo.app.wcmp.WcmpInterpreter.WCMP_GROUP_TABLE;
+import static org.onosproject.bmv2.api.utils.Bmv2TranslatorUtils.roundToBytes;
+import static org.onosproject.bmv2.demo.app.wcmp.WcmpInterpreter.*;
 
 /**
  * Implementation of an upgradable fabric app for the WCMP configuration.
@@ -79,6 +83,8 @@
     private static final WcmpInterpreter WCMP_INTERPRETER = new WcmpInterpreter();
     protected static final Bmv2DeviceContext WCMP_CONTEXT = new Bmv2DeviceContext(WCMP_CONFIGURATION, WCMP_INTERPRETER);
 
+    private static final Map<DeviceId, Map<Map<PortNumber, Double>, Integer>> DEVICE_GROUP_ID_MAP = Maps.newHashMap();
+
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     private Bmv2Controller bmv2Controller;
 
@@ -252,19 +258,11 @@
             portNumbers.add(p);
             weights.add(w);
         });
-        List<Integer> prefixLengths;
-        try {
-            prefixLengths = toPrefixLengths(weights);
-        } catch (WcmpGroupTreatmentBuilder.WcmpGroupException e) {
-            throw new FlowRuleGeneratorException(e);
-        }
+        List<Integer> prefixLengths = toPrefixLengths(weights);
 
         List<FlowRule> rules = Lists.newArrayList();
         for (int i = 0; i < portNumbers.size(); i++) {
-            ExtensionSelector extSelector = new WcmpGroupTableSelectorBuilder()
-                    .withGroupId(groupId)
-                    .withPrefixLength(prefixLengths.get(i))
-                    .build();
+            ExtensionSelector extSelector = buildWcmpSelector(groupId, prefixLengths.get(i));
             FlowRule rule = flowRuleBuilder(deviceId, WCMP_GROUP_TABLE)
                     .withSelector(DefaultTrafficSelector.builder()
                                           .extension(extSelector, deviceId)
@@ -277,11 +275,78 @@
             rules.add(rule);
         }
 
-        ExtensionTreatment extTreatment = new WcmpGroupTreatmentBuilder().withGroupId(groupId).build();
+        ExtensionTreatment extTreatment = buildWcmpTreatment(groupId);
 
         return Pair.of(extTreatment, rules);
     }
 
+    private Bmv2ExtensionSelector buildWcmpSelector(int groupId, int prefixLength) {
+        byte[] ones = new byte[roundToBytes(prefixLength)];
+        Arrays.fill(ones, (byte) 0xFF);
+        return Bmv2ExtensionSelector.builder()
+                .forConfiguration(WCMP_CONTEXT.configuration())
+                .matchExact(WCMP_META, GROUP_ID, groupId)
+                .matchLpm(WCMP_META, SELECTOR, ones, prefixLength)
+                .build();
+    }
+
+    private Bmv2ExtensionTreatment buildWcmpTreatment(int groupId) {
+        return Bmv2ExtensionTreatment.builder()
+                .forConfiguration(WCMP_CONTEXT.configuration())
+                .setActionName(WCMP_GROUP)
+                .addParameter(GROUP_ID, groupId)
+                .build();
+    }
+
+    public int groupIdOf(DeviceId did, Map<PortNumber, Double> weightedPorts) {
+        DEVICE_GROUP_ID_MAP.putIfAbsent(did, Maps.newHashMap());
+        // Counts the number of unique portNumber sets for each device ID.
+        // Each distinct set of portNumbers will have a unique ID.
+        return DEVICE_GROUP_ID_MAP.get(did).computeIfAbsent(weightedPorts,
+                                                            (pp) -> DEVICE_GROUP_ID_MAP.get(did).size() + 1);
+    }
+
+    public List<Integer> toPrefixLengths(List<Double> weigths) {
+
+        final double weightSum = weigths.stream()
+                .mapToDouble(Double::doubleValue)
+                .map(this::roundDouble)
+                .sum();
+
+        if (Math.abs(weightSum - 1) > 0.0001) {
+            throw new RuntimeException("WCMP weights sum is expected to be 1, found was " + weightSum);
+        }
+
+        final int selectorBitWidth = WCMP_CONTEXT.configuration().headerType(WCMP_META_T).field(SELECTOR).bitWidth();
+        final int availableBits = selectorBitWidth - 1;
+
+        List<Long> prefixDiffs = weigths.stream().map(w -> Math.round(w * availableBits)).collect(toList());
+
+        final long bitSum = prefixDiffs.stream().mapToLong(Long::longValue).sum();
+        final long error = availableBits - bitSum;
+
+        if (error != 0) {
+            // Lazy intuition here is that the error can be absorbed by the longest prefixDiff with the minor impact.
+            Long maxDiff = Collections.max(prefixDiffs);
+            int idx = prefixDiffs.indexOf(maxDiff);
+            prefixDiffs.remove(idx);
+            prefixDiffs.add(idx, maxDiff + error);
+        }
+        List<Integer> prefixLengths = Lists.newArrayList();
+
+        int prefix = 1;
+        for (Long p : prefixDiffs) {
+            prefixLengths.add(prefix);
+            prefix += p;
+        }
+        return ImmutableList.copyOf(prefixLengths);
+    }
+
+    private double roundDouble(double n) {
+        // 5 digits precision.
+        return (double) Math.round(n * 100000d) / 100000d;
+    }
+
     private static Bmv2Configuration loadConfiguration() {
         try {
             JsonObject json = Json.parse(new BufferedReader(new InputStreamReader(
diff --git a/apps/bmv2-demo/wcmp/src/main/java/org/onosproject/bmv2/demo/app/wcmp/WcmpGroupTableSelectorBuilder.java b/apps/bmv2-demo/wcmp/src/main/java/org/onosproject/bmv2/demo/app/wcmp/WcmpGroupTableSelectorBuilder.java
deleted file mode 100644
index bac9d61..0000000
--- a/apps/bmv2-demo/wcmp/src/main/java/org/onosproject/bmv2/demo/app/wcmp/WcmpGroupTableSelectorBuilder.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright 2016-present Open Networking Laboratory
- *
- * Licensed 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.onosproject.bmv2.demo.app.wcmp;
-
-import com.google.common.collect.ImmutableMap;
-import org.onlab.util.ImmutableByteSequence;
-import org.onosproject.bmv2.api.runtime.Bmv2ExactMatchParam;
-import org.onosproject.bmv2.api.runtime.Bmv2ExtensionSelector;
-import org.onosproject.bmv2.api.runtime.Bmv2LpmMatchParam;
-import org.onosproject.bmv2.api.utils.Bmv2TranslatorUtils;
-import org.onosproject.net.flow.criteria.ExtensionSelector;
-
-import static com.google.common.base.Preconditions.checkArgument;
-import static org.onosproject.bmv2.api.utils.Bmv2TranslatorUtils.fitByteSequence;
-import static org.onosproject.bmv2.api.utils.Bmv2TranslatorUtils.roundToBytes;
-import static org.onosproject.bmv2.demo.app.wcmp.WcmpFabricApp.WCMP_CONTEXT;
-import static org.onosproject.bmv2.demo.app.wcmp.WcmpInterpreter.*;
-
-/**
- * Builder of WCMP group table extension selector.
- */
-public final class WcmpGroupTableSelectorBuilder {
-
-    private int groupId;
-    private int prefixLength;
-
-    /**
-     * Sets the WCMP group ID.
-     *
-     * @param groupId an integer value
-     * @return this
-     */
-    public WcmpGroupTableSelectorBuilder withGroupId(int groupId) {
-        this.groupId = groupId;
-        return this;
-    }
-
-    /**
-     * Sets the WCMP selector's prefix length.
-     *
-     * @param prefixLength an integer value
-     * @return this
-     */
-    public WcmpGroupTableSelectorBuilder withPrefixLength(int prefixLength) {
-        this.prefixLength = prefixLength;
-        return this;
-    }
-
-    /**
-     * Returns a new extension selector.
-     *
-     * @return an extension selector
-     */
-    public ExtensionSelector build() {
-
-        final int selectorBitWidth = WCMP_CONTEXT.configuration().headerType(WCMP_META_T).field(SELECTOR).bitWidth();
-        final int groupIdBitWidth = WCMP_CONTEXT.configuration().headerType(WCMP_META_T).field(GROUP_ID).bitWidth();
-        final ImmutableByteSequence ones = ImmutableByteSequence.ofOnes(roundToBytes(selectorBitWidth));
-
-        checkArgument(prefixLength >= 1 && prefixLength <= selectorBitWidth,
-                      "prefix length must be between 1 and " + selectorBitWidth);
-        try {
-            ImmutableByteSequence groupIdBs = fitByteSequence(ImmutableByteSequence.copyFrom(groupId), groupIdBitWidth);
-            Bmv2ExactMatchParam groupIdMatch = new Bmv2ExactMatchParam(groupIdBs);
-            Bmv2LpmMatchParam selectorMatch = new Bmv2LpmMatchParam(ones, prefixLength);
-
-            return new Bmv2ExtensionSelector(ImmutableMap.of(
-                    WCMP_META + "." + GROUP_ID, groupIdMatch,
-                    WCMP_META + "." + SELECTOR, selectorMatch));
-
-        } catch (Bmv2TranslatorUtils.ByteSequenceFitException e) {
-            throw new RuntimeException(e);
-        }
-    }
-}
diff --git a/apps/bmv2-demo/wcmp/src/main/java/org/onosproject/bmv2/demo/app/wcmp/WcmpGroupTreatmentBuilder.java b/apps/bmv2-demo/wcmp/src/main/java/org/onosproject/bmv2/demo/app/wcmp/WcmpGroupTreatmentBuilder.java
deleted file mode 100644
index 731b2ca..0000000
--- a/apps/bmv2-demo/wcmp/src/main/java/org/onosproject/bmv2/demo/app/wcmp/WcmpGroupTreatmentBuilder.java
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Copyright 2016-present Open Networking Laboratory
- *
- * Licensed 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.onosproject.bmv2.demo.app.wcmp;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import org.onlab.util.ImmutableByteSequence;
-import org.onosproject.bmv2.api.runtime.Bmv2Action;
-import org.onosproject.bmv2.api.runtime.Bmv2ExtensionTreatment;
-import org.onosproject.bmv2.api.utils.Bmv2TranslatorUtils;
-import org.onosproject.net.DeviceId;
-import org.onosproject.net.PortNumber;
-import org.onosproject.net.flow.instructions.ExtensionTreatment;
-
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-
-import static com.google.common.base.Preconditions.checkArgument;
-import static java.util.stream.Collectors.toList;
-import static org.onosproject.bmv2.api.utils.Bmv2TranslatorUtils.fitByteSequence;
-import static org.onosproject.bmv2.demo.app.wcmp.WcmpFabricApp.WCMP_CONTEXT;
-import static org.onosproject.bmv2.demo.app.wcmp.WcmpInterpreter.*;
-
-/**
- * Builder of WCMP extension treatment.
- */
-public final class WcmpGroupTreatmentBuilder {
-
-    private static final double MAX_ERROR = 0.0001;
-
-    private static final Map<DeviceId, Map<Map<PortNumber, Double>, Integer>> DEVICE_GROUP_ID_MAP = Maps.newHashMap();
-
-    private int groupId;
-
-    /**
-     * Sets the WCMP group ID.
-     *
-     * @param groupId an integer value
-     * @return this
-     */
-    public WcmpGroupTreatmentBuilder withGroupId(int groupId) {
-        this.groupId = groupId;
-        return this;
-    }
-
-    /**
-     * Returns a new extension treatment.
-     *
-     * @return an extension treatment
-     */
-    public ExtensionTreatment build() {
-        checkArgument(groupId >= 0, "group id must be a non-zero positive integer");
-        ImmutableByteSequence groupIdBs = ImmutableByteSequence.copyFrom(groupId);
-        final int groupIdBitWidth = WCMP_CONTEXT.configuration().headerType(WCMP_META_T).field(GROUP_ID).bitWidth();
-        try {
-            groupIdBs = fitByteSequence(groupIdBs, groupIdBitWidth);
-            return new Bmv2ExtensionTreatment(
-                    Bmv2Action.builder()
-                            .withName(WCMP_GROUP)
-                            .addParameter(groupIdBs)
-                            .build());
-        } catch (Bmv2TranslatorUtils.ByteSequenceFitException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    public static int groupIdOf(DeviceId did, Map<PortNumber, Double> weightedPorts) {
-        DEVICE_GROUP_ID_MAP.putIfAbsent(did, Maps.newHashMap());
-        // Counts the number of unique portNumber sets for each device ID.
-        // Each distinct set of portNumbers will have a unique ID.
-        return DEVICE_GROUP_ID_MAP.get(did).computeIfAbsent(weightedPorts,
-                                                            (pp) -> DEVICE_GROUP_ID_MAP.get(did).size() + 1);
-    }
-
-    public static List<Integer> toPrefixLengths(List<Double> weigths) throws WcmpGroupException {
-
-        double weightSum = weigths.stream()
-                .mapToDouble(Double::doubleValue)
-                .map(WcmpGroupTreatmentBuilder::roundDouble)
-                .sum();
-
-        if (Math.abs(weightSum - 1) > MAX_ERROR) {
-            throw new WcmpGroupException("weights sum is expected to be 1, found was " + weightSum);
-        }
-
-        final int selectorBitWidth = WCMP_CONTEXT.configuration().headerType(WCMP_META_T).field(SELECTOR).bitWidth();
-        final int availableBits = selectorBitWidth - 1;
-
-        List<Long> prefixDiffs = weigths.stream().map(w -> Math.round(w * availableBits)).collect(toList());
-
-        final long bitSum = prefixDiffs.stream().mapToLong(Long::longValue).sum();
-        final long error = availableBits - bitSum;
-
-        if (error != 0) {
-            // Lazy intuition here is that the error can be absorbed by the longest prefixDiff with the minor impact.
-            Long maxDiff = Collections.max(prefixDiffs);
-            int idx = prefixDiffs.indexOf(maxDiff);
-            prefixDiffs.remove(idx);
-            prefixDiffs.add(idx, maxDiff + error);
-        }
-        List<Integer> prefixLengths = Lists.newArrayList();
-
-        int prefix = 1;
-        for (Long p : prefixDiffs) {
-            prefixLengths.add(prefix);
-            prefix += p;
-        }
-        return ImmutableList.copyOf(prefixLengths);
-    }
-
-    private static double roundDouble(double n) {
-        // 5 digits precision.
-        return (double) Math.round(n * 100000d) / 100000d;
-    }
-
-    public static class WcmpGroupException extends Exception {
-        public WcmpGroupException(String s) {
-        }
-    }
-}
diff --git a/drivers/bmv2/src/main/java/org/onosproject/drivers/bmv2/Bmv2ExtensionSelectorResolver.java b/drivers/bmv2/src/main/java/org/onosproject/drivers/bmv2/Bmv2ExtensionSelectorResolver.java
index 28e93a7..78986a6 100644
--- a/drivers/bmv2/src/main/java/org/onosproject/drivers/bmv2/Bmv2ExtensionSelectorResolver.java
+++ b/drivers/bmv2/src/main/java/org/onosproject/drivers/bmv2/Bmv2ExtensionSelectorResolver.java
@@ -22,8 +22,6 @@
 import org.onosproject.net.flow.criteria.ExtensionSelector;
 import org.onosproject.net.flow.criteria.ExtensionSelectorType;
 
-import java.util.Collections;
-
 import static org.onosproject.net.flow.criteria.ExtensionSelectorType.ExtensionSelectorTypes.BMV2_MATCH_PARAMS;
 
 /**
@@ -34,7 +32,7 @@
     @Override
     public ExtensionSelector getExtensionSelector(ExtensionSelectorType type) {
         if (type.equals(BMV2_MATCH_PARAMS.type())) {
-            return new Bmv2ExtensionSelector(Collections.emptyMap());
+            return Bmv2ExtensionSelector.empty();
         }
 
         return null;
diff --git a/drivers/bmv2/src/main/java/org/onosproject/drivers/bmv2/Bmv2ExtensionTreatmentResolver.java b/drivers/bmv2/src/main/java/org/onosproject/drivers/bmv2/Bmv2ExtensionTreatmentResolver.java
index 00a22bb..6ef46d1 100644
--- a/drivers/bmv2/src/main/java/org/onosproject/drivers/bmv2/Bmv2ExtensionTreatmentResolver.java
+++ b/drivers/bmv2/src/main/java/org/onosproject/drivers/bmv2/Bmv2ExtensionTreatmentResolver.java
@@ -32,7 +32,7 @@
     @Override
     public ExtensionTreatment getExtensionInstruction(ExtensionTreatmentType type) {
         if (type.equals(BMV2_ACTION.type())) {
-            return new Bmv2ExtensionTreatment(null);
+            return Bmv2ExtensionTreatment.empty();
         }
         return null;
     }
diff --git a/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2Action.java b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2Action.java
index e05803c..a9d3ed8 100644
--- a/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2Action.java
+++ b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2Action.java
@@ -37,7 +37,7 @@
     private final String name;
     private final List<ImmutableByteSequence> parameters;
 
-    private Bmv2Action(String name, List<ImmutableByteSequence> parameters) {
+    protected Bmv2Action(String name, List<ImmutableByteSequence> parameters) {
         // hide constructor
         this.name = name;
         this.parameters = parameters;
diff --git a/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2ExtensionSelector.java b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2ExtensionSelector.java
index cd8ec77..d9d9d0e 100644
--- a/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2ExtensionSelector.java
+++ b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2ExtensionSelector.java
@@ -19,18 +19,29 @@
 import com.google.common.annotations.Beta;
 import com.google.common.base.MoreObjects;
 import com.google.common.base.Objects;
+import com.google.common.collect.Maps;
+import org.apache.commons.lang3.tuple.Pair;
 import org.onlab.util.KryoNamespace;
+import org.onosproject.bmv2.api.context.Bmv2Configuration;
+import org.onosproject.bmv2.api.context.Bmv2FieldTypeModel;
+import org.onosproject.bmv2.api.context.Bmv2HeaderModel;
+import org.onosproject.bmv2.api.utils.Bmv2TranslatorUtils;
 import org.onosproject.net.flow.AbstractExtension;
 import org.onosproject.net.flow.criteria.ExtensionSelector;
 import org.onosproject.net.flow.criteria.ExtensionSelectorType;
 
+import java.nio.ByteBuffer;
 import java.util.HashMap;
 import java.util.Map;
 
-import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.*;
+import static org.onlab.util.ImmutableByteSequence.copyFrom;
+import static org.onosproject.bmv2.api.utils.Bmv2TranslatorUtils.fitByteSequence;
 
 /**
- * Extension selector for BMv2 used as a wrapper for multiple BMv2 match parameters.
+ * Extension selector for BMv2 used as a wrapper for multiple BMv2 match parameters. Match parameters are
+ * encoded using a map where the keys are expected to be field names formatted as {@code headerName.fieldName}
+ * (e.g. {@code ethernet.dstAddr}).
  */
 @Beta
 public final class Bmv2ExtensionSelector extends AbstractExtension implements ExtensionSelector {
@@ -47,13 +58,12 @@
     private Map<String, Bmv2MatchParam> parameterMap;
 
     /**
-     * Creates a new BMv2 extension selector for the given match parameters map, where the keys are expected to be field
-     * names formatted as headerName.fieldMemberName (e.g. ethernet.dstAddr).
+     * Creates a new BMv2 extension selector for the given match parameters map.
      *
      * @param paramMap a map
      */
-    public Bmv2ExtensionSelector(Map<String, Bmv2MatchParam> paramMap) {
-        this.parameterMap = checkNotNull(paramMap, "param map cannot be null");
+    private Bmv2ExtensionSelector(Map<String, Bmv2MatchParam> paramMap) {
+        this.parameterMap = paramMap;
     }
 
     /**
@@ -104,4 +114,340 @@
                 .add("parameterMap", parameterMap)
                 .toString();
     }
+
+    /**
+     * Returns a new, empty BMv2 extension selector.
+     *
+     * @return a BMv2 extension treatment
+     */
+    public static Bmv2ExtensionSelector empty() {
+        return new Bmv2ExtensionSelector(null);
+    }
+
+    /**
+     * Returns a new builder of BMv2 extension selectors.
+     *
+     * @return a builder
+     */
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    /**
+     * Builder of BMv2 extension selectors.
+     * <p>
+     * Match parameters are built from primitive data types ({@code short}, {@code int}, {@code long} or
+     * {@code byte[]}) and automatically casted to fixed-length byte sequences according to the given BMv2
+     * configuration.
+     */
+    public static final class Builder {
+
+        private final Map<Pair<String, String>, Bmv2MatchParam> parameterMap = Maps.newHashMap();
+        private Bmv2Configuration configuration;
+
+        private Builder() {
+            // ban constructor.
+        }
+
+        /**
+         * Sets the BMv2 configuration to format the match parameters of the selector.
+         *
+         * @param config a BMv2 configuration
+         * @return this
+         */
+        public Builder forConfiguration(Bmv2Configuration config) {
+            this.configuration = config;
+            return this;
+        }
+
+        /**
+         * Adds an exact match parameter for the given header field and value.
+         *
+         * @param headerName a string value
+         * @param fieldName  a string value
+         * @param value      a short value
+         * @return this
+         */
+        public Builder matchExact(String headerName, String fieldName, short value) {
+            parameterMap.put(Pair.of(checkNotNull(headerName, "header name cannot be null"),
+                                     checkNotNull(fieldName, "field name cannot be null")),
+                             exact(value));
+            return this;
+        }
+
+        /**
+         * Adds an exact match parameter for the given header field and value.
+         *
+         * @param headerName a string value
+         * @param fieldName  a string value
+         * @param value      an integer value
+         * @return this
+         */
+        public Builder matchExact(String headerName, String fieldName, int value) {
+            parameterMap.put(Pair.of(checkNotNull(headerName, "header name cannot be null"),
+                                     checkNotNull(fieldName, "field name cannot be null")),
+                             exact(value));
+            return this;
+        }
+
+        /**
+         * Adds an exact match parameter for the given header field and value.
+         *
+         * @param headerName a string value
+         * @param fieldName  a string value
+         * @param value      a long value
+         * @return this
+         */
+        public Builder matchExact(String headerName, String fieldName, long value) {
+            parameterMap.put(Pair.of(checkNotNull(headerName, "header name cannot be null"),
+                                     checkNotNull(fieldName, "field name cannot be null")),
+                             exact(value));
+            return this;
+        }
+
+        /**
+         * Adds an exact match parameter for the given header field and value.
+         *
+         * @param headerName a string value
+         * @param fieldName  a string value
+         * @param value      a byte array
+         * @return this
+         */
+        public Builder matchExact(String headerName, String fieldName, byte[] value) {
+            parameterMap.put(Pair.of(checkNotNull(headerName, "header name cannot be null"),
+                                     checkNotNull(fieldName, "field name cannot be null")),
+                             exact(value));
+            return this;
+        }
+
+        /**
+         * Adds a ternary match parameter for the given header field, value and mask.
+         *
+         * @param headerName a string value
+         * @param fieldName  a string value
+         * @param value      a short value
+         * @param mask       a short value
+         * @return this
+         */
+        public Builder matchTernary(String headerName, String fieldName, short value, short mask) {
+            parameterMap.put(Pair.of(checkNotNull(headerName, "header name cannot be null"),
+                                     checkNotNull(fieldName, "field name cannot be null")),
+                             ternary(value, mask));
+            return this;
+        }
+        /**
+         * Adds a ternary match parameter for the given header field, value and mask.
+         *
+         * @param headerName a string value
+         * @param fieldName  a string value
+         * @param value      an integer value
+         * @param mask       an integer value
+         * @return this
+         */
+        public Builder matchTernary(String headerName, String fieldName, int value, int mask) {
+            parameterMap.put(Pair.of(checkNotNull(headerName, "header name cannot be null"),
+                                     checkNotNull(fieldName, "field name cannot be null")),
+                             ternary(value, mask));
+            return this;
+        }
+        /**
+         * Adds a ternary match parameter for the given header field, value and mask.
+         *
+         * @param headerName a string value
+         * @param fieldName  a string value
+         * @param value      a long value
+         * @param mask       a long value
+         * @return this
+         */
+        public Builder matchTernary(String headerName, String fieldName, long value, long mask) {
+            parameterMap.put(Pair.of(checkNotNull(headerName, "header name cannot be null"),
+                                     checkNotNull(fieldName, "field name cannot be null")),
+                             ternary(value, mask));
+            return this;
+        }
+        /**
+         * Adds a ternary match parameter for the given header field, value and mask.
+         *
+         * @param headerName a string value
+         * @param fieldName  a string value
+         * @param value      a byte array
+         * @param mask       a byte array
+         * @return this
+         */
+        public Builder matchTernary(String headerName, String fieldName, byte[] value, byte[] mask) {
+            parameterMap.put(Pair.of(checkNotNull(headerName, "header name cannot be null"),
+                                     checkNotNull(fieldName, "field name cannot be null")),
+                             ternary(value, mask));
+            return this;
+        }
+
+        /**
+         * Adds a longest-prefix match (LPM) parameter for the given header field, value and prefix length.
+         *
+         * @param headerName   a string value
+         * @param fieldName    a string value
+         * @param value        a short value
+         * @param prefixLength an integer value
+         * @return this
+         */
+        public Builder matchLpm(String headerName, String fieldName, short value, int prefixLength) {
+            parameterMap.put(Pair.of(checkNotNull(headerName, "header name cannot be null"),
+                                     checkNotNull(fieldName, "field name cannot be null")),
+                             lpm(value, prefixLength));
+            return this;
+        }
+        /**
+         * Adds a longest-prefix match (LPM) parameter for the given header field, value and prefix length.
+         *
+         * @param headerName   a string value
+         * @param fieldName    a string value
+         * @param value        an integer value
+         * @param prefixLength an integer value
+         * @return this
+         */
+        public Builder matchLpm(String headerName, String fieldName, int value, int prefixLength) {
+            parameterMap.put(Pair.of(checkNotNull(headerName, "header name cannot be null"),
+                                     checkNotNull(fieldName, "field name cannot be null")),
+                             lpm(value, prefixLength));
+            return this;
+        }
+        /**
+         * Adds a longest-prefix match (LPM) parameter for the given header field, value and prefix length.
+         *
+         * @param headerName   a string value
+         * @param fieldName    a string value
+         * @param value        a long value
+         * @param prefixLength an integer value
+         * @return this
+         */
+        public Builder matchLpm(String headerName, String fieldName, long value, int prefixLength) {
+            parameterMap.put(Pair.of(checkNotNull(headerName, "header name cannot be null"),
+                                     checkNotNull(fieldName, "field name cannot be null")),
+                             lpm(value, prefixLength));
+            return this;
+        }
+        /**
+         * Adds a longest-prefix match (LPM) parameter for the given header field, value and prefix length.
+         *
+         * @param headerName   a string value
+         * @param fieldName    a string value
+         * @param value        a byte array
+         * @param prefixLength an integer value
+         * @return this
+         */
+        public Builder matchLpm(String headerName, String fieldName, byte[] value, int prefixLength) {
+            parameterMap.put(Pair.of(checkNotNull(headerName, "header name cannot be null"),
+                                     checkNotNull(fieldName, "field name cannot be null")),
+                             lpm(value, prefixLength));
+            return this;
+        }
+
+        /**
+         * Adds a valid match parameter for the given header field.
+         *
+         * @param headerName a string value
+         * @param fieldName  a string value
+         * @param flag       a boolean value
+         * @return this
+         */
+        public Builder matchValid(String headerName, String fieldName, boolean flag) {
+            parameterMap.put(Pair.of(checkNotNull(headerName, "header name cannot be null"),
+                                     checkNotNull(fieldName, "field name cannot be null")),
+                             new Bmv2ValidMatchParam(flag));
+            return this;
+        }
+
+        /**
+         * Returns a new BMv2 extension selector.
+         *
+         * @return a BMv2 extension selector
+         * @throws NullPointerException     if a given header or field name is not defined in the given configuration
+         * @throws IllegalArgumentException if a given parameter cannot be casted for the given configuration, e.g.
+         *                                  when trying to fit an integer value into a smaller, fixed-length parameter
+         *                                  produces overflow.
+         */
+        public Bmv2ExtensionSelector build() {
+            checkNotNull(configuration, "configuration cannot be null");
+            checkState(parameterMap.size() > 0, "parameter map cannot be empty");
+
+            final Map<String, Bmv2MatchParam> newParameterMap = Maps.newHashMap();
+
+            for (Pair<String, String> key : parameterMap.keySet()) {
+
+                String headerName = key.getLeft();
+                String fieldName = key.getRight();
+
+                Bmv2HeaderModel headerModel = configuration.header(headerName);
+                checkNotNull(headerModel, "no such a header in configuration", headerName);
+
+                Bmv2FieldTypeModel fieldModel = headerModel.type().field(fieldName);
+                checkNotNull(fieldModel, "no such a field in configuration", key);
+
+                int bitWidth = fieldModel.bitWidth();
+
+                Bmv2MatchParam oldParam = parameterMap.get(key);
+                Bmv2MatchParam newParam = null;
+
+                try {
+                    switch (oldParam.type()) {
+                        case EXACT:
+                            Bmv2ExactMatchParam e = (Bmv2ExactMatchParam) oldParam;
+                            newParam = new Bmv2ExactMatchParam(fitByteSequence(e.value(), bitWidth));
+                            break;
+                        case TERNARY:
+                            Bmv2TernaryMatchParam t = (Bmv2TernaryMatchParam) oldParam;
+                            newParam = new Bmv2TernaryMatchParam(fitByteSequence(t.value(), bitWidth),
+                                                                 fitByteSequence(t.mask(), bitWidth));
+                            break;
+                        case LPM:
+                            Bmv2LpmMatchParam l = (Bmv2LpmMatchParam) oldParam;
+                            checkArgument(l.prefixLength() <= bitWidth, "LPM parameter has prefix length too long",
+                                          key);
+                            newParam = new Bmv2LpmMatchParam(fitByteSequence(l.value(), bitWidth),
+                                                             l.prefixLength());
+                            break;
+                        case VALID:
+                            newParam = oldParam;
+                            break;
+                        default:
+                            throw new RuntimeException("Match parameter type not supported: " + oldParam.type());
+                    }
+                } catch (Bmv2TranslatorUtils.ByteSequenceFitException e) {
+                    throw new IllegalArgumentException(e.getMessage() + " [" + key + "]");
+                }
+                // FIXME: should put the pair object instead of building a new string for the key.
+                newParameterMap.put(headerName + "." + fieldName, newParam);
+            }
+
+            return new Bmv2ExtensionSelector(newParameterMap);
+        }
+
+        private static Bmv2MatchParam exact(Object value) {
+            return new Bmv2ExactMatchParam(copyFrom(bb(value)));
+        }
+
+        private static Bmv2MatchParam ternary(Object value, Object mask) {
+            return new Bmv2TernaryMatchParam(copyFrom(bb(value)), copyFrom(bb(mask)));
+        }
+
+        private static Bmv2MatchParam lpm(Object value, int prefixLength) {
+            return new Bmv2LpmMatchParam(copyFrom(bb(value)), prefixLength);
+        }
+
+        private static ByteBuffer bb(Object value) {
+            if (value instanceof Short) {
+                return ByteBuffer.allocate(Short.BYTES).putShort((short) value);
+            } else if (value instanceof Integer) {
+                return ByteBuffer.allocate(Integer.BYTES).putInt((int) value);
+            } else if (value instanceof Long) {
+                return ByteBuffer.allocate(Long.BYTES).putLong((long) value);
+            } else if (value instanceof byte[]) {
+                byte[] bytes = (byte[]) value;
+                return ByteBuffer.allocate(bytes.length).put(bytes);
+            } else {
+                // Never here.
+                return null;
+            }
+        }
+    }
 }
diff --git a/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2ExtensionTreatment.java b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2ExtensionTreatment.java
index b2f65f3..b2a9ba3 100644
--- a/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2ExtensionTreatment.java
+++ b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2ExtensionTreatment.java
@@ -19,11 +19,26 @@
 import com.google.common.annotations.Beta;
 import com.google.common.base.MoreObjects;
 import com.google.common.base.Objects;
+import com.google.common.collect.Maps;
+import org.onlab.util.ImmutableByteSequence;
 import org.onlab.util.KryoNamespace;
+import org.onosproject.bmv2.api.context.Bmv2ActionModel;
+import org.onosproject.bmv2.api.context.Bmv2Configuration;
+import org.onosproject.bmv2.api.context.Bmv2RuntimeDataModel;
+import org.onosproject.bmv2.api.utils.Bmv2TranslatorUtils;
 import org.onosproject.net.flow.AbstractExtension;
 import org.onosproject.net.flow.instructions.ExtensionTreatment;
 import org.onosproject.net.flow.instructions.ExtensionTreatmentType;
 
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onlab.util.ImmutableByteSequence.copyFrom;
+import static org.onosproject.bmv2.api.utils.Bmv2TranslatorUtils.fitByteSequence;
 import static org.onosproject.net.flow.instructions.ExtensionTreatmentType.ExtensionTreatmentTypes.BMV2_ACTION;
 
 /**
@@ -40,7 +55,7 @@
      *
      * @param action an action
      */
-    public Bmv2ExtensionTreatment(Bmv2Action action) {
+    private Bmv2ExtensionTreatment(Bmv2Action action) {
         this.action = action;
     }
 
@@ -91,4 +106,167 @@
                 .add("action", action)
                 .toString();
     }
+
+    /**
+     * Returns a new, empty BMv2 extension treatment.
+     *
+     * @return a BMv2 extension treatment
+     */
+    public static Bmv2ExtensionTreatment empty() {
+        return new Bmv2ExtensionTreatment(null);
+    }
+
+    /**
+     * Returns a new BMv2 extension treatment builder.
+     *
+     * @return a builder
+     */
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    /**
+     * A builder of BMv2 extension treatments.
+     *
+     * BMv2 action parameters are built from primitive data types ({@code short}, {@code int}, {@code long} or
+     * {@code byte[]}) and automatically casted to fixed-length byte sequences according to the given BMv2
+     * configuration.
+     */
+    public static final class Builder {
+        private Bmv2Configuration configuration;
+        private String actionName;
+        private final Map<String, ImmutableByteSequence> parameters = Maps.newHashMap();
+
+        private Builder() {
+            // Ban constructor.
+        }
+
+        /**
+         * Sets the BMv2 configuration to format the action parameters.
+         *
+         * @param config a BMv2 configuration
+         * @return this
+         */
+        public Builder forConfiguration(Bmv2Configuration config) {
+            this.configuration = config;
+            return this;
+        }
+
+        /**
+         * Sets the action name.
+         *
+         * @param actionName a string value
+         * @return this
+         */
+        public Builder setActionName(String actionName) {
+            this.actionName = actionName;
+            return this;
+        }
+
+        /**
+         * Adds an action parameter.
+         *
+         * @param parameterName a string value
+         * @param value a short value
+         * @return this
+         */
+        public Builder addParameter(String parameterName, short value) {
+            this.parameters.put(parameterName, copyFrom(bb(value)));
+            return this;
+        }
+
+        /**
+         * Adds an action parameter.
+         *
+         * @param parameterName a string value
+         * @param value an integer value
+         * @return this
+         */
+        public Builder addParameter(String parameterName, int value) {
+            this.parameters.put(parameterName, copyFrom(bb(value)));
+            return this;
+        }
+
+        /**
+         * Adds an action parameter.
+         *
+         * @param parameterName a string value
+         * @param value a long value
+         * @return this
+         */
+        public Builder addParameter(String parameterName, long value) {
+            this.parameters.put(parameterName, copyFrom(bb(value)));
+            return this;
+        }
+
+        /**
+         * Adds an action parameter.
+         *
+         * @param parameterName a string value
+         * @param value a byte array
+         * @return this
+         */
+        public Builder addParameter(String parameterName, byte[] value) {
+            this.parameters.put(parameterName, copyFrom(bb(value)));
+            return this;
+        }
+
+        /**
+         * Returns a new BMv2 extension treatment.
+         *
+         * @return a BMv2 extension treatment
+         * @throws NullPointerException     if the given action or parameter names are not defined in the given
+         *                                  configuration
+         * @throws IllegalArgumentException if a given parameter cannot be casted for the given configuration, e.g.
+         *                                  when trying to fit an integer value into a smaller, fixed-length parameter
+         *                                  produces overflow.
+         */
+        public Bmv2ExtensionTreatment build() {
+            checkNotNull(configuration, "configuration cannot be null");
+            checkNotNull(actionName, "action name cannot be null");
+
+            Bmv2ActionModel actionModel = configuration.action(actionName);
+
+            checkNotNull(actionModel, "no such an action in configuration", actionName);
+            checkArgument(actionModel.runtimeDatas().size() == parameters.size(),
+                          "invalid number of parameters", actionName);
+
+            List<ImmutableByteSequence> newParameters = new ArrayList<>(parameters.size());
+
+            for (String parameterName : parameters.keySet()) {
+                Bmv2RuntimeDataModel runtimeData = actionModel.runtimeData(parameterName);
+                checkNotNull(runtimeData, "no such an action parameter in configuration",
+                             actionName + "->" + runtimeData.name());
+                int bitWidth = runtimeData.bitWidth();
+                try {
+                    ImmutableByteSequence newSequence = fitByteSequence(parameters.get(parameterName), bitWidth);
+                    int idx = actionModel.runtimeDatas().indexOf(runtimeData);
+                    newParameters.add(idx, newSequence);
+                } catch (Bmv2TranslatorUtils.ByteSequenceFitException e) {
+                    throw new IllegalArgumentException(e.getMessage() +
+                                                               " [" + actionName + "->" + runtimeData.name() + "]");
+                }
+            }
+
+            return new Bmv2ExtensionTreatment(new Bmv2Action(actionName, newParameters));
+        }
+
+
+
+        private static ByteBuffer bb(Object value) {
+            if (value instanceof Short) {
+                return ByteBuffer.allocate(Short.BYTES).putShort((short) value);
+            } else if (value instanceof Integer) {
+                return ByteBuffer.allocate(Integer.BYTES).putInt((int) value);
+            } else if (value instanceof Long) {
+                return ByteBuffer.allocate(Long.BYTES).putLong((long) value);
+            } else if (value instanceof byte[]) {
+                byte[] bytes = (byte[]) value;
+                return ByteBuffer.allocate(bytes.length).put(bytes);
+            } else {
+                // Never here.
+                return null;
+            }
+        }
+    }
 }
diff --git a/protocols/bmv2/api/src/test/java/org/onosproject/bmv2/api/runtime/Bmv2ExtensionBuilderTest.java b/protocols/bmv2/api/src/test/java/org/onosproject/bmv2/api/runtime/Bmv2ExtensionBuilderTest.java
new file mode 100644
index 0000000..2c9cc95
--- /dev/null
+++ b/protocols/bmv2/api/src/test/java/org/onosproject/bmv2/api/runtime/Bmv2ExtensionBuilderTest.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed 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.onosproject.bmv2.api.runtime;
+
+import com.eclipsesource.json.Json;
+import com.eclipsesource.json.JsonObject;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.MacAddress;
+import org.onosproject.bmv2.api.context.Bmv2Configuration;
+import org.onosproject.bmv2.api.context.Bmv2DefaultConfiguration;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+public class Bmv2ExtensionBuilderTest {
+
+    private Bmv2Configuration config;
+
+    @Before
+    public void setUp() throws Exception {
+        JsonObject json = Json.parse(new BufferedReader(new InputStreamReader(
+                this.getClass().getResourceAsStream("/simple.json")))).asObject();
+        config = Bmv2DefaultConfiguration.parse(json);
+    }
+
+    @Test
+    public void testExtensionSelector() throws Exception {
+
+        Bmv2ExtensionSelector extSelectorExact = Bmv2ExtensionSelector.builder()
+                .forConfiguration(config)
+                .matchExact("standard_metadata", "ingress_port", (short) 255)
+                .matchExact("ethernet", "etherType", 512)
+                .matchExact("ethernet", "dstAddr", 1024L)
+                .matchExact("ethernet", "srcAddr", MacAddress.BROADCAST.toBytes())
+                .build();
+
+        Bmv2ExtensionSelector extSelectorTernary = Bmv2ExtensionSelector.builder()
+                .forConfiguration(config)
+                .matchTernary("standard_metadata", "ingress_port", (short) 255, (short) 255)
+                .matchTernary("ethernet", "etherType", 512, 512)
+                .matchTernary("ethernet", "dstAddr", 1024L, 1024L)
+                .matchTernary("ethernet", "srcAddr", MacAddress.BROADCAST.toBytes(), MacAddress.NONE.toBytes())
+                .build();
+
+        Bmv2ExtensionSelector extSelectorLpm = Bmv2ExtensionSelector.builder()
+                .forConfiguration(config)
+                .matchLpm("standard_metadata", "ingress_port", (short) 255, 1)
+                .matchLpm("ethernet", "etherType", 512, 2)
+                .matchLpm("ethernet", "dstAddr", 1024L, 3)
+                .matchLpm("ethernet", "srcAddr", MacAddress.BROADCAST.toBytes(), 4)
+                .build();
+
+        Bmv2ExtensionSelector extSelectorValid = Bmv2ExtensionSelector.builder()
+                .forConfiguration(config)
+                .matchValid("standard_metadata", "ingress_port", true)
+                .matchValid("ethernet", "etherType", true)
+                .matchValid("ethernet", "dstAddr", false)
+                .matchValid("ethernet", "srcAddr", false)
+                .build();
+
+        assertThat(extSelectorExact.parameterMap().size(), is(4));
+        assertThat(extSelectorTernary.parameterMap().size(), is(4));
+        assertThat(extSelectorLpm.parameterMap().size(), is(4));
+        assertThat(extSelectorValid.parameterMap().size(), is(4));
+
+        // TODO add more tests, e.g. check for byte sequences content and size.
+    }
+
+    @Test
+    public void testExtensionTreatment() throws Exception {
+
+        Bmv2ExtensionTreatment treatment = Bmv2ExtensionTreatment.builder()
+                .forConfiguration(config)
+                .setActionName("set_egress_port")
+                .addParameter("port", 1)
+                .build();
+
+        assertThat(treatment.action().parameters().size(), is(1));
+
+        // TODO add more tests, e.g. check for byte sequences content and size.
+    }
+}
\ No newline at end of file