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/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;
+            }
+        }
+    }
 }