CLI options: flows (remove), intents (remove/purge; details)

Change-Id: Id084c28451389a46826eced30f03dcce2c1afe86
diff --git a/cli/src/main/java/org/onosproject/cli/net/FlowsListCommand.java b/cli/src/main/java/org/onosproject/cli/net/FlowsListCommand.java
index bd7a246..7cbd586 100644
--- a/cli/src/main/java/org/onosproject/cli/net/FlowsListCommand.java
+++ b/cli/src/main/java/org/onosproject/cli/net/FlowsListCommand.java
@@ -24,7 +24,6 @@
 import org.apache.karaf.shell.commands.Option;
 import org.onlab.util.StringFilter;
 import org.onosproject.cli.AbstractShellCommand;
-import org.onosproject.utils.Comparators;
 import org.onosproject.core.ApplicationId;
 import org.onosproject.core.CoreService;
 import org.onosproject.net.Device;
@@ -34,7 +33,11 @@
 import org.onosproject.net.flow.FlowEntry.FlowEntryState;
 import org.onosproject.net.flow.FlowRuleService;
 import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.utils.Comparators;
 
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -93,10 +96,15 @@
     private boolean countOnly = false;
 
     @Option(name = "-f", aliases = "--filter",
-            description = "Filter flows by specific key",
+            description = "Filter flows by specific keyword",
             required = false, multiValued = true)
     private List<String> filter = new ArrayList<>();
 
+    @Option(name = "-r", aliases = "--remove",
+            description = "Remove flows by specific keyword",
+            required = false, multiValued = false)
+    private String remove = null;
+
     private Predicate<FlowEntry> predicate = TRUE_PREDICATE;
 
     private StringFilter contentFilter;
@@ -112,6 +120,22 @@
 
         SortedMap<Device, List<FlowEntry>> flows = getSortedFlows(deviceService, service, coreService);
 
+        // Remove flows
+        if (remove != null) {
+            flows.values().forEach(flowList -> {
+                if (!remove.isEmpty()) {
+                    filter.add(remove);
+                    contentFilter = new StringFilter(filter, StringFilter.Strategy.AND);
+                }
+                if (!filter.isEmpty() || (remove != null && !remove.isEmpty())) {
+                    flowList = filterFlows(flowList);
+                    this.removeFlowsInteractive(flowList, service, coreService);
+                }
+            });
+            return;
+        }
+
+        // Show flows
         if (outputJson()) {
             print("%s", json(flows.keySet(), flows));
         } else {
@@ -120,6 +144,36 @@
     }
 
     /**
+     * Removes the flows passed as argument after confirmation is provided
+     * for each of them.
+     * If no explicit confirmation is provided, the flow is not removed.
+     *
+     * @param flows       list of flows to remove
+     * @param flowService FlowRuleService object
+     * @param coreService CoreService object
+     */
+    public void removeFlowsInteractive(Iterable<FlowEntry> flows,
+                                       FlowRuleService flowService, CoreService coreService) {
+        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
+        flows.forEach(flow -> {
+            ApplicationId appId = coreService.getAppId(flow.appId());
+            System.out.print(String.format("Id=%s, AppId=%s. Remove? [y/N]: ",
+                                           flow.id(), appId != null ? appId.name() : "<none>"));
+            String response;
+            try {
+                response = br.readLine();
+                response = response.trim().replace("\n", "");
+                if (response.equals("y")) {
+                    flowService.removeFlowRules(flow);
+                }
+            } catch (IOException e) {
+                response = "";
+            }
+            print(response);
+        });
+    }
+
+    /**
      * Produces a JSON array of flows grouped by the each device.
      *
      * @param devices     collection of devices to group flow by
@@ -213,6 +267,17 @@
     }
 
     /**
+     * Filter a given list of flows based on the existing content filter.
+     *
+     * @param flows list of flows to filter
+     * @return further filtered list of flows
+     */
+    private List<FlowEntry> filterFlows(List<FlowEntry> flows) {
+        return flows.stream().
+                filter(f -> contentFilter.filter(f)).collect(Collectors.toList());
+    }
+
+    /**
      * Prints flows.
      *
      * @param d     the device
@@ -221,8 +286,7 @@
      */
     protected void printFlows(Device d, List<FlowEntry> flows,
                               CoreService coreService) {
-        List<FlowEntry> filteredFlows = flows.stream().
-                filter(f -> contentFilter.filter(f)).collect(Collectors.toList());
+        List<FlowEntry> filteredFlows = filterFlows(flows);
         boolean empty = filteredFlows == null || filteredFlows.isEmpty();
         print("deviceId=%s, flowRuleCount=%d", d.id(), empty ? 0 : filteredFlows.size());
         if (empty || countOnly) {
@@ -271,5 +335,4 @@
         builder.append("]");
         return builder.toString();
     }
-
 }