Adds loose filtering capability (-f) to cli commands (intents, flows)
- Multi-valued filtering
- Two search strategies (and/or) [defaults to add]

Change-Id: Ia9ad9233b65209b20550ba699c238b88ffb43f8d
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 659dd96..9de96f9 100644
--- a/cli/src/main/java/org/onosproject/cli/net/FlowsListCommand.java
+++ b/cli/src/main/java/org/onosproject/cli/net/FlowsListCommand.java
@@ -22,6 +22,7 @@
 import org.apache.karaf.shell.commands.Argument;
 import org.apache.karaf.shell.commands.Command;
 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;
@@ -34,6 +35,7 @@
 import org.onosproject.net.flow.FlowRuleService;
 import org.onosproject.net.flow.TrafficTreatment;
 
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
@@ -90,13 +92,21 @@
             required = false, multiValued = false)
     private boolean countOnly = false;
 
+    @Option(name = "-f", aliases = "--filter",
+            description = "Filter flows by specific key",
+            required = false, multiValued = true)
+    private List<String> filter = new ArrayList<>();
+
     private Predicate<FlowEntry> predicate = TRUE_PREDICATE;
 
+    private StringFilter contentFilter;
+
     @Override
     protected void execute() {
         CoreService coreService = get(CoreService.class);
         DeviceService deviceService = get(DeviceService.class);
         FlowRuleService service = get(FlowRuleService.class);
+        contentFilter = new StringFilter(filter, StringFilter.Strategy.AND);
 
         compilePredicate();
 
@@ -211,13 +221,15 @@
      */
     protected void printFlows(Device d, List<FlowEntry> flows,
                               CoreService coreService) {
-        boolean empty = flows == null || flows.isEmpty();
-        print("deviceId=%s, flowRuleCount=%d", d.id(), empty ? 0 : flows.size());
+        List<FlowEntry> filteredFlows = flows.stream().
+                filter(f -> contentFilter.filter(f)).collect(Collectors.toList());
+        boolean empty = filteredFlows == null || filteredFlows.isEmpty();
+        print("deviceId=%s, flowRuleCount=%d", d.id(), empty ? 0 : filteredFlows.size());
         if (empty || countOnly) {
             return;
         }
 
-        for (FlowEntry f : flows) {
+        for (FlowEntry f : filteredFlows) {
             if (shortOutput) {
                 print(SHORT_FORMAT, f.state(), f.bytes(), f.packets(),
                         f.tableId(), f.priority(), f.selector().criteria(),
diff --git a/cli/src/main/java/org/onosproject/cli/net/IntentsListCommand.java b/cli/src/main/java/org/onosproject/cli/net/IntentsListCommand.java
index 609c5f9..eb80fad 100644
--- a/cli/src/main/java/org/onosproject/cli/net/IntentsListCommand.java
+++ b/cli/src/main/java/org/onosproject/cli/net/IntentsListCommand.java
@@ -15,10 +15,13 @@
  */
 package org.onosproject.cli.net;
 
+import java.util.ArrayList;
 import java.util.List;
+import java.util.stream.StreamSupport;
 
 import org.apache.karaf.shell.commands.Command;
 import org.apache.karaf.shell.commands.Option;
+import org.onlab.util.StringFilter;
 import org.onosproject.cli.AbstractShellCommand;
 import org.onosproject.net.intent.ConnectivityIntent;
 import org.onosproject.net.intent.HostToHostIntent;
@@ -57,13 +60,23 @@
     private boolean intentsSummary = false;
 
     @Option(name = "-p", aliases = "--pending",
-            description = "Show inforamtion about pending intents",
+            description = "Show information about pending intents",
             required = false, multiValued = false)
     private boolean pending = false;
 
+    @Option(name = "-f", aliases = "--filter",
+            description = "Filter intents by specific key",
+            required = false, multiValued = true)
+    private List<String> filter = new ArrayList<>();
+
+    private StringFilter contentFilter;
+
+    private String sep = System.lineSeparator();
+
     @Override
     protected void execute() {
         IntentService service = get(IntentService.class);
+        contentFilter = new StringFilter(filter, StringFilter.Strategy.AND);
 
         if (intentsSummary) {
             IntentSummaries intentSummaries = new IntentSummaries();
@@ -79,12 +92,9 @@
             if (outputJson()) {
                 print("%s", json(service, service.getPending()));
             } else {
-                service.getPending().forEach(intent ->
-                                print("id=%s, key=%s, type=%s, appId=%s",
-                                        intent.id(), intent.key(),
-                                        intent.getClass().getSimpleName(),
-                                        intent.appId().name())
-                );
+                StreamSupport.stream(service.getPending().spliterator(), false)
+                        .filter(intent -> contentFilter.filter(intent))
+                        .forEach(intent -> print(fullFormat(intent)));
             }
             return;
         }
@@ -92,16 +102,7 @@
         if (outputJson()) {
             print("%s", json(service, service.getIntents()));
         } else {
-            for (Intent intent : service.getIntents()) {
-                IntentState state = service.getIntentState(intent.key());
-                if (state != null) {
-                    print("id=%s, state=%s, key=%s, type=%s, appId=%s",
-                          intent.id(), state, intent.key(),
-                          intent.getClass().getSimpleName(),
-                          intent.appId().name());
-                    printDetails(service, intent);
-                }
-            }
+            printIntents(service);
         }
     }
 
@@ -158,6 +159,9 @@
                 if (intentState == null) {
                     continue;
                 }
+                if (!contentFilter.filter(intent)) {
+                    break;
+                }
 
                 // Update the summary for all Intents
                 summaryAll.update(intentState);
@@ -368,56 +372,89 @@
         }
     }
 
-    private void printDetails(IntentService service, Intent intent) {
+    private String detailsFormat(IntentService service, Intent intent) {
+        StringBuilder builder = new StringBuilder();
         if (!intent.resources().isEmpty()) {
-            print("    resources=%s", intent.resources());
+            builder.append(String.format("    resources=%s%s", intent.resources(), sep));
         }
         if (intent instanceof ConnectivityIntent) {
             ConnectivityIntent ci = (ConnectivityIntent) intent;
             if (!ci.selector().criteria().isEmpty()) {
-                print("    selector=%s", ci.selector().criteria());
+                builder.append(String.format("    selector=%s%s", ci.selector().criteria(), sep));
             }
             if (!ci.treatment().allInstructions().isEmpty()) {
-                print("    treatment=%s", ci.treatment().allInstructions());
+                builder.append(String.format("    treatment=%s%s", ci.treatment().allInstructions(), sep));
             }
             if (ci.constraints() != null && !ci.constraints().isEmpty()) {
-                print("    constraints=%s", ci.constraints());
+                builder.append(String.format("    constraints=%s%s", ci.constraints(), sep));
             }
         }
 
         if (intent instanceof HostToHostIntent) {
             HostToHostIntent pi = (HostToHostIntent) intent;
-            print("    host1=%s, host2=%s", pi.one(), pi.two());
+            builder.append(String.format("    host1=%s, host2=%s", pi.one(), pi.two()));
         } else if (intent instanceof PointToPointIntent) {
             PointToPointIntent pi = (PointToPointIntent) intent;
-            print("    ingress=%s, egress=%s", pi.ingressPoint(), pi.egressPoint());
+            builder.append(String.format("    ingress=%s, egress=%s", pi.ingressPoint(), pi.egressPoint()));
         } else if (intent instanceof MultiPointToSinglePointIntent) {
             MultiPointToSinglePointIntent pi = (MultiPointToSinglePointIntent) intent;
-            print("    ingress=%s, egress=%s", pi.ingressPoints(), pi.egressPoint());
+            builder.append(String.format("    ingress=%s, egress=%s", pi.ingressPoints(), pi.egressPoint()));
         } else if (intent instanceof SinglePointToMultiPointIntent) {
             SinglePointToMultiPointIntent pi = (SinglePointToMultiPointIntent) intent;
-            print("    ingress=%s, egress=%s", pi.ingressPoint(), pi.egressPoints());
+            builder.append(String.format("    ingress=%s, egress=%s", pi.ingressPoint(), pi.egressPoints()));
         } else if (intent instanceof PathIntent) {
             PathIntent pi = (PathIntent) intent;
-            print("    path=%s, cost=%d", pi.path().links(), pi.path().cost());
+            builder.append(String.format("    path=%s, cost=%d", pi.path().links(), pi.path().cost()));
         } else if (intent instanceof LinkCollectionIntent) {
             LinkCollectionIntent li = (LinkCollectionIntent) intent;
-            print("    links=%s", li.links());
-            print("    egress=%s", li.egressPoints());
+            builder.append(String.format("    links=%s", li.links()));
+            builder.append(String.format("    egress=%s", li.egressPoints()));
         } else if (intent instanceof OpticalCircuitIntent) {
             OpticalCircuitIntent ci = (OpticalCircuitIntent) intent;
-            print("    src=%s, dst=%s", ci.getSrc(), ci.getDst());
+            builder.append(String.format("    src=%s, dst=%s", ci.getSrc(), ci.getDst()));
         } else if (intent instanceof OpticalConnectivityIntent) {
             OpticalConnectivityIntent ci = (OpticalConnectivityIntent) intent;
-            print("    src=%s, dst=%s", ci.getSrc(), ci.getDst());
+            builder.append(String.format("    src=%s, dst=%s", ci.getSrc(), ci.getDst()));
         } else if (intent instanceof OpticalOduIntent) {
             OpticalOduIntent ci = (OpticalOduIntent) intent;
-            print("    src=%s, dst=%s", ci.getSrc(), ci.getDst());
+            builder.append(String.format("    src=%s, dst=%s", ci.getSrc(), ci.getDst()));
         }
 
         List<Intent> installable = service.getInstallableIntents(intent.key());
+        installable.stream().filter(i -> contentFilter.filter(i));
         if (showInstallable && installable != null && !installable.isEmpty()) {
-            print("    installable=%s", installable);
+            builder.append(String.format("%s    installable=%s", sep, installable));
+        }
+        return builder.toString();
+    }
+
+    private String fullFormat(Intent intent) {
+        return fullFormat(intent, null);
+    }
+
+    private String fullFormat(Intent intent, String state) {
+        StringBuilder builder = new StringBuilder();
+        builder.append(String.format("id=%s, ", intent.id()));
+        if (state != null) {
+            builder.append(String.format("state=%s, ", state));
+        }
+        builder.append(String.format("key=%s, type=%s, appId=%s",
+                                     intent.key(),
+                                     intent.getClass().getSimpleName(),
+                                     intent.appId().name()));
+        return builder.toString();
+    }
+
+    private void printIntents(IntentService service) {
+        for (Intent intent : service.getIntents()) {
+            IntentState state = service.getIntentState(intent.key());
+            String intentFormat = fullFormat(intent, state.toString());
+            String detailsIntentFormat = detailsFormat(service, intent);
+            if (state != null && (contentFilter.filter(
+                    intentFormat + detailsIntentFormat))) {
+                print(intentFormat);
+                print(detailsIntentFormat);
+            }
         }
     }
 
@@ -425,8 +462,9 @@
     private JsonNode json(IntentService service, Iterable<Intent> intents) {
         ObjectMapper mapper = new ObjectMapper();
         ArrayNode result = mapper.createArrayNode();
-
-        intents.forEach(intent -> result.add(jsonForEntity(intent, Intent.class)));
+        StreamSupport.stream(intents.spliterator(), false)
+                .filter(intent -> contentFilter.filter(jsonForEntity(intent, Intent.class).toString()))
+                .forEach(intent -> result.add(jsonForEntity(intent, Intent.class)));
         return result;
     }
 
diff --git a/utils/misc/src/main/java/org/onlab/util/StringFilter.java b/utils/misc/src/main/java/org/onlab/util/StringFilter.java
new file mode 100644
index 0000000..46037a2
--- /dev/null
+++ b/utils/misc/src/main/java/org/onlab/util/StringFilter.java
@@ -0,0 +1,102 @@
+/*
+ * 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.onlab.util;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+/**
+ * Filters content on a given object with String representation.
+ * This is carried out through restrictive (AND) or loose (OR) searches.
+ * If not provided, strategy defaults to ADD.
+ */
+public class StringFilter {
+
+    /**
+     * Defines the filtering strategy.
+     */
+    public enum Strategy {
+        AND,
+        OR
+    }
+
+    private Strategy strategy = Strategy.AND;
+    private List<String> filter = new ArrayList<>();
+
+    /**
+     * Creates a new filter to apply on some data.
+     *
+     * @param filter list with filters to apply
+     */
+    public StringFilter(List<String> filter) {
+        this.filter = filter;
+    }
+
+    /**
+     * Creates a new filter to apply on some data,
+     * given a specific strategy (AND, OR).
+     *
+     * @param filter   list with filters to apply
+     * @param strategy type of strategy (AND, OR)
+     */
+    public StringFilter(List<String> filter, Strategy strategy) {
+        this(filter);
+        checkArgument(strategy == Strategy.AND || strategy == Strategy.OR,
+                      "Chosen strategy is not allowed (should be one of {AND, OR})");
+        this.strategy = strategy;
+    }
+
+    /**
+     * Filters data according to a set of restrictions and the AND strategy.
+     *
+     * @param data Object with data to filter
+     * @return true if data honours the filter, false otherwise
+     */
+    private boolean filterAnd(Object data) {
+        return filter.isEmpty() ||
+                filter.stream().filter(data.toString()::contains)
+                        .count() == filter.size();
+    }
+
+    /**
+     * Filters data according to a set of restrictions and the OR strategy.
+     *
+     * @param data Object with data to filter
+     * @return true if data honours the filter, false otherwise
+     */
+    private boolean filterOr(Object data) {
+        return filter.isEmpty() ||
+                filter.stream().filter(data.toString()::contains)
+                        .count() > 0;
+    }
+
+    /**
+     * Filters data according to a set of restrictions and a specific strategy.
+     *
+     * @param data Object with data to filter
+     * @return true if data honours the filters, false otherwise
+     */
+    public boolean filter(Object data) {
+        if (strategy == Strategy.OR) {
+            return filterOr(data);
+        } else {
+            return filterAnd(data);
+        }
+    }
+}