Implemented P4Info browser

Change-Id: Iebff15ac60cf1121b29afce7f67b4c31c5a60f0e
diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4InfoBrowser.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4InfoBrowser.java
new file mode 100644
index 0000000..d8edaff
--- /dev/null
+++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4InfoBrowser.java
@@ -0,0 +1,332 @@
+/*
+ * Copyright 2017-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.p4runtime.ctl;
+
+
+import com.google.common.collect.Maps;
+import com.google.protobuf.Message;
+import p4.config.P4InfoOuterClass.Action;
+import p4.config.P4InfoOuterClass.ActionProfile;
+import p4.config.P4InfoOuterClass.ControllerPacketMetadata;
+import p4.config.P4InfoOuterClass.Counter;
+import p4.config.P4InfoOuterClass.DirectCounter;
+import p4.config.P4InfoOuterClass.DirectMeter;
+import p4.config.P4InfoOuterClass.MatchField;
+import p4.config.P4InfoOuterClass.Meter;
+import p4.config.P4InfoOuterClass.P4Info;
+import p4.config.P4InfoOuterClass.Preamble;
+import p4.config.P4InfoOuterClass.Table;
+
+import java.util.Map;
+
+import static java.lang.String.format;
+
+/**
+ * Utility class to easily retrieve information from a P4Info protobuf message.
+ */
+final class P4InfoBrowser {
+
+    private final EntityBrowser<Table> tables = new EntityBrowser<>("table");
+    private final EntityBrowser<Action> actions = new EntityBrowser<>("action");
+    private final EntityBrowser<ActionProfile> actionProfiles = new EntityBrowser<>("action profile");
+    private final EntityBrowser<Counter> counters = new EntityBrowser<>("counter");
+    private final EntityBrowser<DirectCounter> directCounters = new EntityBrowser<>("direct counter");
+    private final EntityBrowser<Meter> meters = new EntityBrowser<>("meter");
+    private final EntityBrowser<DirectMeter> directMeters = new EntityBrowser<>("direct meter");
+    private final EntityBrowser<ControllerPacketMetadata> ctrlPktMetadatas =
+            new EntityBrowser<>("controller packet metadata");
+    private final Map<Integer, EntityBrowser<Action.Param>> actionParams = Maps.newHashMap();
+    private final Map<Integer, EntityBrowser<MatchField>> matchFields = Maps.newHashMap();
+
+    /**
+     * Creates a new browser for the given P4Info.
+     *
+     * @param p4info P4Info protobuf message
+     */
+    P4InfoBrowser(P4Info p4info) {
+        parseP4Info(p4info);
+    }
+
+    private void parseP4Info(P4Info p4info) {
+        p4info.getTablesList().forEach(
+                entity -> {
+                    tables.addWithPreamble(entity.getPreamble(), entity);
+                    // Index match fields.
+                    int tableId = entity.getPreamble().getId();
+                    String tableName = entity.getPreamble().getName();
+                    EntityBrowser<MatchField> matchFieldBrowser = new EntityBrowser<>(format(
+                            "match field for table '%s'", tableName));
+                    entity.getMatchFieldsList().forEach(m -> matchFieldBrowser.add(m.getName(), m.getId(), m));
+                    matchFields.put(tableId, matchFieldBrowser);
+                });
+
+        p4info.getActionsList().forEach(
+                entity -> {
+                    actions.addWithPreamble(entity.getPreamble(), entity);
+                    // Index action params.
+                    int actionId = entity.getPreamble().getId();
+                    String actionName = entity.getPreamble().getName();
+                    EntityBrowser<Action.Param> paramBrowser = new EntityBrowser<>(format(
+                            "param for action '%s'", actionName));
+                    entity.getParamsList().forEach(p -> paramBrowser.add(p.getName(), p.getId(), p));
+                    actionParams.put(actionId, paramBrowser);
+                });
+
+        p4info.getActionProfilesList().forEach(
+                entity -> actionProfiles.addWithPreamble(entity.getPreamble(), entity));
+
+        p4info.getCountersList().forEach(
+                entity -> counters.addWithPreamble(entity.getPreamble(), entity));
+
+        p4info.getDirectCountersList().forEach(
+                entity -> directCounters.addWithPreamble(entity.getPreamble(), entity));
+
+        p4info.getMetersList().forEach(
+                entity -> meters.addWithPreamble(entity.getPreamble(), entity));
+
+        p4info.getDirectMetersList().forEach(
+                entity -> directMeters.addWithPreamble(entity.getPreamble(), entity));
+
+        p4info.getControllerPacketMetadataList().forEach(
+                entity -> ctrlPktMetadatas.addWithPreamble(entity.getPreamble(), entity));
+    }
+
+    /**
+     * Returns a browser for tables.
+     *
+     * @return table browser
+     */
+    EntityBrowser<Table> tables() {
+        return tables;
+    }
+
+    /**
+     * Returns a browser for actions.
+     *
+     * @return action browser
+     */
+    EntityBrowser<Action> actions() {
+        return actions;
+    }
+
+    /**
+     * Returns a browser for action profiles.
+     *
+     * @return action profile browser
+     */
+    EntityBrowser<ActionProfile> actionProfiles() {
+        return actionProfiles;
+    }
+
+    /**
+     * Returns a browser for counters.
+     *
+     * @return counter browser
+     */
+    EntityBrowser<Counter> counters() {
+        return counters;
+    }
+
+    /**
+     * Returns a browser for direct counters.
+     *
+     * @return direct counter browser
+     */
+    EntityBrowser<DirectCounter> directCounters() {
+        return directCounters;
+    }
+
+    /**
+     * Returns a browser for meters.
+     *
+     * @return meter browser
+     */
+    EntityBrowser<Meter> meters() {
+        return meters;
+    }
+
+    /**
+     * Returns a browser for direct meters.
+     *
+     * @return table browser
+     */
+    EntityBrowser<DirectMeter> directMeters() {
+        return directMeters;
+    }
+
+    /**
+     * Returns a browser for controller packet metadata.
+     *
+     * @return controller packet metadata browser
+     */
+    EntityBrowser<ControllerPacketMetadata> controllerPacketMetadatas() {
+        return ctrlPktMetadatas;
+    }
+
+    /**
+     * Returns a browser for params of the given action.
+     *
+     * @param actionId action identifier
+     * @return action params browser
+     * @throws NotFoundException if the action cannot be found
+     */
+    EntityBrowser<Action.Param> actionParams(int actionId) throws NotFoundException {
+        // Throws exception if action id is not found.
+        actions.getById(actionId);
+        return actionParams.get(actionId);
+    }
+
+    /**
+     * Returns a browser for match fields of the given table.
+     *
+     * @param tableId table identifier
+     * @return controller packet metadata browser
+     * @throws NotFoundException if the table cannot be found
+     */
+    EntityBrowser<MatchField> matchFields(int tableId) throws NotFoundException {
+        // Throws exception if action id is not found.
+        tables.getById(tableId);
+        return matchFields.get(tableId);
+    }
+
+    /**
+     * Browser of P4Info entities.
+     *
+     * @param <T> protobuf message type
+     */
+    static final class EntityBrowser<T extends Message> {
+
+        private String entityName;
+        private final Map<String, T> names = Maps.newHashMap();
+        private final Map<String, T> aliases = Maps.newHashMap();
+        private final Map<Integer, T> ids = Maps.newHashMap();
+
+        private EntityBrowser(String entityName) {
+            this.entityName = entityName;
+        }
+
+        /**
+         * Adds the given entity identified by the given name and id.
+         *
+         * @param name   entity name
+         * @param id     entity id
+         * @param entity entity message
+         */
+        void add(String name, int id, T entity) {
+            names.put(name, entity);
+            ids.put(id, entity);
+        }
+
+        /**
+         * Adds the given entity identified by the given P4Info preamble.
+         *
+         * @param preamble P4Info preamble protobuf message
+         * @param entity   entity message
+         */
+        void addWithPreamble(Preamble preamble, T entity) {
+            names.put(preamble.getName(), entity);
+            aliases.put(preamble.getName(), entity);
+            ids.put(preamble.getId(), entity);
+        }
+
+        /**
+         * Returns true if the P4Info defines an entity with such name, false otherwise.
+         *
+         * @param name entity name
+         * @return boolean
+         */
+        boolean hasName(String name) {
+            return names.containsKey(name);
+        }
+
+        /**
+         * Returns the entity identified by the given name, if present, otherwise, throws an exception.
+         *
+         * @param name entity name
+         * @return entity message
+         * @throws NotFoundException if the entity cannot be found
+         */
+        T getByName(String name) throws NotFoundException {
+            if (!hasName(name)) {
+                throw new NotFoundException(entityName, name);
+            }
+            return names.get(name);
+        }
+
+        /**
+         * Returns true if the P4Info defines an entity with such alias, false otherwise.
+         *
+         * @param alias entity alias
+         * @return boolean
+         */
+        boolean hasAlias(String alias) {
+            return aliases.containsKey(alias);
+        }
+
+        /**
+         * Returns the entity identified by the given alias, if present, otherwise, throws an exception.
+         *
+         * @param alias entity alias
+         * @return entity message
+         * @throws NotFoundException if the entity cannot be found
+         */
+        T getByAlias(String alias) throws NotFoundException {
+            if (!hasName(alias)) {
+                throw new NotFoundException(entityName, alias);
+            }
+            return aliases.get(alias);
+        }
+
+        /**
+         * Returns true if the P4Info defines an entity with such id, false otherwise.
+         *
+         * @param id entity id
+         * @return boolean
+         */
+        boolean hasId(int id) {
+            return ids.containsKey(id);
+        }
+
+        /**
+         * Returns the entity identified by the given id, if present, otherwise, throws an exception.
+         *
+         * @param id entity id
+         * @return entity message
+         * @throws NotFoundException if the entity cannot be found
+         */
+        T getById(int id) throws NotFoundException {
+            if (!hasId(id)) {
+                throw new NotFoundException(entityName, id);
+            }
+            return ids.get(id);
+        }
+    }
+
+    /**
+     * Signals tha an entity cannot be found in the P4Info.
+     */
+    public static class NotFoundException extends Exception {
+
+        NotFoundException(String entityName, String key) {
+            super(format("No such %s in P4Info with name/alias '%s'", entityName, key));
+        }
+
+        NotFoundException(String entityName, int id) {
+            super(format("No such %s in P4Info with id '%d'", entityName, id));
+        }
+    }
+}
diff --git a/protocols/p4runtime/ctl/src/test/java/org/onosproject/p4runtime/ctl/TestP4Info.java b/protocols/p4runtime/ctl/src/test/java/org/onosproject/p4runtime/ctl/TestP4Info.java
new file mode 100644
index 0000000..17263e4
--- /dev/null
+++ b/protocols/p4runtime/ctl/src/test/java/org/onosproject/p4runtime/ctl/TestP4Info.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2017-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.p4runtime.ctl;
+
+import com.google.protobuf.ExtensionRegistry;
+import com.google.protobuf.TextFormat;
+import org.junit.Test;
+import p4.config.P4InfoOuterClass;
+
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+public class TestP4Info {
+
+    private InputStream p4InfoStream = this.getClass().getResourceAsStream("/default.p4info");
+
+    @Test
+    public void testP4InfoBrowser() throws Exception {
+
+        InputStreamReader input = new InputStreamReader(p4InfoStream);
+        ExtensionRegistry extensionRegistry = ExtensionRegistry.getEmptyRegistry();
+        P4InfoOuterClass.P4Info.Builder builder = P4InfoOuterClass.P4Info.newBuilder();
+
+        builder.clear();
+        TextFormat.getParser().merge(input, extensionRegistry, builder);
+        P4InfoOuterClass.P4Info p4Info = builder.build();
+
+        P4InfoBrowser browser = new P4InfoBrowser(p4Info);
+
+        assertThat(browser.tables().hasName("table0"), is(true));
+        assertThat(browser.actions().hasName("set_egress_port"), is(true));
+
+        int tableId = browser.tables().getByName("table0").getPreamble().getId();
+        int actionId = browser.actions().getByName("set_egress_port").getPreamble().getId();
+
+        assertThat(browser.matchFields(tableId).hasName("standard_metadata.ingress_port"), is(true));
+        assertThat(browser.actionParams(actionId).hasName("port"), is(true));
+
+        // TODO: improve, assert browsing of other entities (counters, meters, etc.)
+    }
+}
diff --git a/protocols/p4runtime/ctl/src/test/resources/default.p4info b/protocols/p4runtime/ctl/src/test/resources/default.p4info
new file mode 100644
index 0000000..bd3649d
--- /dev/null
+++ b/protocols/p4runtime/ctl/src/test/resources/default.p4info
@@ -0,0 +1,113 @@
+tables {
+  preamble {
+    id: 33617813
+    name: "table0"
+    alias: "table0"
+  }
+  match_fields {
+    id: 1
+    name: "standard_metadata.ingress_port"
+    bitwidth: 9
+    match_type: TERNARY
+  }
+  match_fields {
+    id: 2
+    name: "hdr.ethernet.dstAddr"
+    bitwidth: 48
+    match_type: TERNARY
+  }
+  match_fields {
+    id: 3
+    name: "hdr.ethernet.srcAddr"
+    bitwidth: 48
+    match_type: TERNARY
+  }
+  match_fields {
+    id: 4
+    name: "hdr.ethernet.etherType"
+    bitwidth: 16
+    match_type: TERNARY
+  }
+  action_refs {
+    id: 16794308
+  }
+  action_refs {
+    id: 16829080
+  }
+  action_refs {
+    id: 16793508
+  }
+  action_refs {
+    id: 16800567
+    annotations: "@defaultonly()"
+  }
+  direct_resource_ids: 301990488
+  size: 1024
+  with_entry_timeout: true
+}
+actions {
+  preamble {
+    id: 16794308
+    name: "set_egress_port"
+    alias: "set_egress_port"
+  }
+  params {
+    id: 1
+    name: "port"
+    bitwidth: 9
+  }
+}
+actions {
+  preamble {
+    id: 16829080
+    name: "send_to_cpu"
+    alias: "send_to_cpu"
+  }
+}
+actions {
+  preamble {
+    id: 16793508
+    name: "drop"
+    alias: "drop"
+  }
+}
+actions {
+  preamble {
+    id: 16800567
+    name: "NoAction"
+    alias: "NoAction"
+  }
+}
+counters {
+  preamble {
+    id: 302025528
+    name: "port_counters_control.egress_port_counter"
+    alias: "egress_port_counter"
+  }
+  spec {
+    unit: PACKETS
+  }
+  size: 254
+}
+counters {
+  preamble {
+    id: 301999025
+    name: "port_counters_control.ingress_port_counter"
+    alias: "ingress_port_counter"
+  }
+  spec {
+    unit: PACKETS
+  }
+  size: 254
+}
+direct_counters {
+  preamble {
+    id: 301990488
+    name: "table0_counter"
+    alias: "table0_counter"
+  }
+  spec {
+    unit: PACKETS
+  }
+  direct_table_id: 33617813
+}