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