[OpenTAM_DPIS] DPI Statistics Manager & dpis CLI
 - Fixed Thomas Vachuska's comment
   .Removed DpiStatisticsJson.java and related codes for avoiding confusion within package because this does not need anymore.
   .Simplified execute() code and fixed comments in DpiListCommand
   .Added javadocs for public methods and fixed typos
 - Rebased from master 1.8.0-SNAPSHOT, 2016.10.14

 - Upgraded the function and performance.
   .changed List<DpiStatistics> to SortedMap<DpiStatistics>
   .added DpiStatisticsService interfaces to get DpiStatistics by receivedTime
   .added more options of dpis CLI: displays indivisual category by topn
   .and some bug fix

 - Fix javadocs and ONOS style method name.
 - Fix javadocs error.

Change-Id: I07a058e04f63bd9c547a5d605975b214eec0ce1f
diff --git a/cli/src/main/java/org/onosproject/cli/net/DpisListCommand.java b/cli/src/main/java/org/onosproject/cli/net/DpisListCommand.java
new file mode 100644
index 0000000..85c2dda
--- /dev/null
+++ b/cli/src/main/java/org/onosproject/cli/net/DpisListCommand.java
@@ -0,0 +1,368 @@
+/*
+ * 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.cli.net;
+
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.apache.karaf.shell.commands.Option;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.incubator.net.dpi.DpiStatInfo;
+import org.onosproject.incubator.net.dpi.DpiStatistics;
+import org.onosproject.incubator.net.dpi.DpiStatisticsManagerService;
+import org.onosproject.incubator.net.dpi.FlowStatInfo;
+import org.onosproject.incubator.net.dpi.ProtocolStatInfo;
+import org.onosproject.incubator.net.dpi.TrafficStatInfo;
+
+import java.util.List;
+
+import static java.lang.Thread.sleep;
+
+/**
+ * Fetches DPI statistics list.
+ */
+@Command(scope = "onos", name = "dpis",
+        description = "Fetches the DPI result entries that is received from DPI engine server")
+public class DpisListCommand extends AbstractShellCommand {
+    @Argument(index = 0, name = "receivedTime", description = "received time: format 'yyyy-MM-dd HH:mm:ss', "
+            + "ex:'2016-08-30 10:31:20', default = null(latest time)",
+            required = false, multiValued = false)
+    String receivedTime = null; // default is latest time
+
+    @Option(name = "-l", aliases = "--latest",
+            description = "Show the latest dpi stats result entry",
+            required = false, multiValued = false)
+    boolean latest = true; // default
+
+    @Option(name = "-d", aliases = "--detectedProtocols",
+            description = "Show the detected protocols only for each statistic entry",
+            required = false, multiValued = false)
+    boolean dProtocols = false; // default
+
+    @Option(name = "-k", aliases = "--knownFlows",
+            description = "Show the known flows only for each statistic entry",
+            required = false, multiValued = false)
+    boolean kFlows = false; // default
+
+    @Option(name = "-u", aliases = "--unknownFlows",
+            description = "Show the unknown flows only for each statistic entry",
+            required = false, multiValued = false)
+    boolean uFlows = false; // default
+
+    @Option(name = "-a", aliases = "--all",
+            description = "Show the all statistics information in detail for each statistic entry",
+            required = false, multiValued = false)
+    boolean all = false; // default is traffic statistics only display
+
+    @Option(name = "-p", aliases = "--permanent",
+            description = "Show the latest dpi stats result entry permanently at 5 second, use Ctrl+C for quitting",
+            required = false, multiValued = false)
+    boolean permanent = false;
+
+    @Option(name = "-n", aliases = "--lastn",
+            description = "Show the last N Dpi stats result entries, MAX_REQUEST_ENTRY = 100",
+            required = false, multiValued = false)
+    String lastn = null;
+
+    @Option(name = "-P", aliases = "--topnProtocols",
+            description = "Show the topn detected Protocol result entries, MAX_PROTOCOLS_TOPN = 100",
+            required = false, multiValued = false)
+    String topnProtocols = null;
+
+    @Option(name = "-F", aliases = "--topnFlows",
+            description = "Show the topn known and unknown Flows result entries, MAX_FLOWS_TOPN = 100",
+            required = false, multiValued = false)
+    String topnFlows = null;
+
+    private static final int DEFAULT_LASTN = 100;
+    private static final int DEFAULT_TOPNP = -1;
+    private static final int DEFAULT_TOPNF = -1;
+    private static final String NO_DPI_ENTRY_ERROR_MSG = "No DPI statistic entry,"
+                                                        + " please check remote DPI engine is running";
+    private static final String RECEIVED_TIME_ERROR_MSG = NO_DPI_ENTRY_ERROR_MSG + "\n"
+                    + " or correct receivedTime format: 'yyyy-MM-dd HH:mm:ss', ex:'2016-08-30 10:31:20'";
+
+    @Override
+    protected void execute() {
+        DpiStatisticsManagerService dsms = get(DpiStatisticsManagerService.class);
+
+        DpiStatistics ds;
+
+        int topnP = DEFAULT_TOPNP;
+        int topnF = DEFAULT_TOPNF;
+
+        if (topnProtocols != null) {
+            topnP = parseIntWithDefault(topnProtocols, DEFAULT_TOPNP);
+            if (topnP <= 0) {
+                print("Invalid detected protocol topn number: 0 < valid number <= 100");
+                return;
+            }
+        }
+
+        if (topnFlows != null) {
+            topnF = parseIntWithDefault(topnFlows, DEFAULT_TOPNF);
+            if (topnF <= 0) {
+                print("Invalid known or unknown flows topn number: 0 < valid number <= 100");
+                return;
+            }
+        }
+
+        boolean isTopn = (topnP > 0 || topnF > 0);
+
+        if (all) {
+            dProtocols = true;
+            kFlows = true;
+            uFlows = true;
+        }
+
+        if (receivedTime != null) {
+            if (isTopn) {
+                ds = dsms.getDpiStatistics(receivedTime, topnP, topnF);
+            } else {
+                ds = dsms.getDpiStatistics(receivedTime);
+            }
+            if (ds == null) {
+                print(RECEIVED_TIME_ERROR_MSG);
+                return;
+            }
+
+            printDpiStatistics(0, ds);
+        } else if (lastn != null) {
+            int lastN = parseIntWithDefault(lastn, DEFAULT_LASTN);
+
+            List<DpiStatistics> dsList;
+            if (isTopn) {
+                dsList = dsms.getDpiStatistics(lastN, topnP, topnF);
+
+            } else {
+                dsList = dsms.getDpiStatistics(lastN);
+            }
+
+            printDpiStatisticsList(dsList);
+        } else if (permanent) {
+            int i = 0;
+            while (true) {
+                try {
+                    if (isTopn) {
+                        ds = dsms.getDpiStatisticsLatest(topnP, topnF);
+                    } else {
+                        ds = dsms.getDpiStatisticsLatest();
+                    }
+                    if (ds == null) {
+                        print(NO_DPI_ENTRY_ERROR_MSG);
+                        return;
+                    }
+
+                    printDpiStatistics(i++, ds);
+                    sleep(5000);
+                } catch (Exception e) {
+                    return;
+                }
+            }
+        } else { // latest == true
+            if (isTopn) {
+                ds = dsms.getDpiStatisticsLatest(topnP, topnF);
+            } else {
+                ds = dsms.getDpiStatisticsLatest();
+            }
+            if (ds == null) {
+                print(NO_DPI_ENTRY_ERROR_MSG);
+                return;
+            }
+
+            printDpiStatistics(0, ds);
+        }
+    }
+
+
+    /**
+     * Parse unsigned integer from input lastn string.
+     *
+     * @param lastN string lastn number
+     * @param defaultN integer default lastn number = 100
+     * @return integer lastN number, defaultN if input format is not a number
+     */
+    private int parseIntWithDefault(String lastN, int defaultN) {
+        try {
+            lastN = lastN.trim();
+            return Integer.parseUnsignedInt(lastN);
+        } catch (NumberFormatException e) {
+            return defaultN;
+        }
+    }
+
+    private void printDpiStatistics(int number, DpiStatistics ds) {
+        if (outputJson()) {
+            printDpiStatisticsJson(number, ds);
+        } else {
+            printDpiStatisticsClass(number, ds);
+        }
+    }
+
+    private void printDpiStatisticsJson(int number, DpiStatistics ds) {
+        String index = number < 0 ? String.format("  -  ") : String.format("%5d", number);
+        if (ds.receivedTime().equals("")) {
+            print("ReceivedTime is null, No valid DPI Statistics!");
+            return;
+        }
+
+        print("<--- (%s) DPI Statistics Time [%s] --->", index, ds.receivedTime());
+        print("      %s", ds.toString());
+        print("<--------------------------------------------------------->");
+    }
+
+    private void printDpiStatisticsClass(int number, DpiStatistics ds) {
+        String printLine = "";
+        String index = number < 0 ? String.format("  -  ") : String.format("%5d", number);
+
+        DpiStatInfo dsi = ds.dpiStatInfo();
+        if (dsi == null) {
+            return;
+        }
+
+        if (ds.receivedTime().equals("")) {
+            print("ReceivedTime is null, No valid DPI Statistics!");
+            return;
+        }
+
+        print("<--- (%s) DPI Statistics Time [%s] --->", index, ds.receivedTime());
+
+        print("Traffic Statistics:");
+        TrafficStatInfo tsi = dsi.trafficStatistics();
+
+        printLine = String.format("        %-30s %-30s", "ethernet.bytes:" + ":", tsi.ethernetBytes());
+        print("%s", printLine);
+        printLine = String.format("        %-30s %-30s", "discarded.bytes" + ":", tsi.discardedBytes());
+        print("%s", printLine);
+        printLine = String.format("        %-30s %-30s", "ip.packets" + ":", tsi.ipPackets());
+        print("%s", printLine);
+        printLine = String.format("        %-30s %-30s", "total.packets" + ":", tsi.totalPackets());
+        print("%s", printLine);
+        printLine = String.format("        %-30s %-30s", "ip.bytes" + ":", tsi.ipBytes());
+        print("%s", printLine);
+        printLine = String.format("        %-30s %-30s", "avg.pkt.size" + ":", tsi.avgPktSize());
+        print("%s", printLine);
+        printLine = String.format("        %-30s %-30s", "unique.flows" + ":", tsi.uniqueFlows());
+        print("%s", printLine);
+        printLine = String.format("        %-30s %-30s", "tcp.packets" + ":", tsi.tcpPackets());
+        print("%s", printLine);
+        printLine = String.format("        %-30s %-30s", "udp.packets" + ":", tsi.tcpPackets());
+        print("%s", printLine);
+        printLine = String.format("        %-30s %-30s", "dpi.throughput.pps" + ":",
+                                  tsi.dpiThroughputPps() + " pps");
+        print("%s", printLine);
+        printLine = String.format("        %-30s %-30s", "dpi.throughput.bps" + ":",
+                                  tsi.dpiThroughputBps() + " bps");
+        print("%s", printLine);
+        printLine = String.format("        %-30s %-30s", "traffic.throughput.pps" + ":",
+                                  tsi.trafficThroughputPps() + " pps");
+        print("%s", printLine);
+        printLine = String.format("        %-30s %-30s", "traffic.throughput.bps" + ":",
+                                  tsi.trafficThroughputBps() + " bps");
+        print("%s", printLine);
+        printLine = String.format("        %-30s %-30s", "traffic.duration.sec" + ":",
+                                  tsi.trafficDurationSec() + " sec");
+        print("%s", printLine);
+        printLine = String.format("        %-30s %-30s", "guessed.flow.protos" + ":", tsi.guessedFlowProtos());
+        print("%s", printLine);
+
+        if (dProtocols || topnProtocols != null) {
+            print("");
+            print("Detected Protocols:");
+            List<ProtocolStatInfo> psiList = dsi.detectedProtos();
+            if (psiList != null) {
+                psiList.forEach(psi -> print(makeProtocolString(psi)));
+            }
+        }
+
+        List<FlowStatInfo> fsiList;
+        if (kFlows || topnFlows != null) {
+            print("");
+            print("Known Flows:");
+            fsiList = dsi.knownFlows();
+            if (fsiList != null) {
+                for (int i = 0; i < fsiList.size(); i++) {
+                    print(makeFlowString(fsiList.get(i), i));
+                }
+            }
+        }
+
+        if (uFlows || topnFlows != null) {
+            print("");
+            print("Unknown Flows:");
+            fsiList = dsi.unknownFlows();
+            if (fsiList != null) {
+                for (int i = 0; i < fsiList.size(); i++) {
+                    print(makeFlowString(fsiList.get(i), i));
+                }
+            }
+        }
+
+        print("<--------------------------------------------------------->");
+    }
+
+    private void printDpiStatisticsList(List<DpiStatistics> dsList) {
+        if (outputJson()) {
+            printDpiStatisticsListJson(dsList);
+        } else {
+            printDpiStatisticsListClass(dsList);
+        }
+    }
+
+    private void printDpiStatisticsListJson(List<DpiStatistics> dsList) {
+        for (int i = 0; i < dsList.size(); i++) {
+            printDpiStatisticsJson(i, dsList.get(i));
+        }
+    }
+
+    private void printDpiStatisticsListClass(List<DpiStatistics> dsList) {
+        for (int i = 0; i < dsList.size(); i++) {
+            printDpiStatisticsClass(i, dsList.get(i));
+        }
+    }
+
+    private String makeProtocolString(ProtocolStatInfo psi) {
+        StringBuffer sb = new StringBuffer("        ");
+
+        sb.append(String.format("%-20s", psi.name()));
+        sb.append(String.format(" %s: %-20s", "packets", psi.packets()));
+        sb.append(String.format(" %s: %-20s", "bytes", psi.bytes()));
+        sb.append(String.format(" %s: %-20s", "flows", psi.flows()));
+
+        return sb.toString();
+    }
+
+    private String makeFlowString(FlowStatInfo fsi, int index) {
+        StringBuffer sb = new StringBuffer("        ");
+
+        sb.append(String.format("%-8d ", index));
+        sb.append(String.format("%s ", fsi.protocol()));
+        sb.append(String.format("%s", fsi.hostAName()));
+        sb.append(String.format(":%s", fsi.hostAPort()));
+        sb.append(String.format(" <-> %s", fsi.hostBName()));
+        sb.append(String.format(":%s", fsi.hostBPort()));
+        sb.append(String.format(" [proto: %d", fsi.detectedProtocol()));
+        sb.append(String.format("/%s]", fsi.detectedProtocolName()));
+        sb.append(String.format(" [%s pkts/", fsi.packets()));
+        sb.append(String.format("%s bytes]", fsi.bytes()));
+        String serverHostName = fsi.hostServerName();
+        if (serverHostName != null && !serverHostName.equals("")) {
+            sb.append(String.format("[Host: %s]", serverHostName));
+        }
+
+        return sb.toString();
+    }
+}
diff --git a/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml b/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml
index 69c52fe..f3c660f 100644
--- a/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml
+++ b/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml
@@ -714,6 +714,9 @@
         <command>
             <action class="org.onosproject.cli.net.vnet.VirtualNetworkIntentRemoveCommand"/>
         </command>
+        <command>
+            <action class="org.onosproject.cli.net.DpisListCommand"/>
+        </command>
     </command-bundle>
 
     <bean id="reviewAppNameCompleter" class="org.onosproject.cli.security.ReviewApplicationNameCompleter"/>