resources command enchancement

- Aggregate output of some Discrete resources
   Currently supports: VlanID, MPLS label, Tributary slots
- Add support for Continuous resource

Change-Id: I5d002ba7f43f8b8d06228507b7463c29296aec90
diff --git a/cli/src/main/java/org/onosproject/cli/net/ResourcesCommand.java b/cli/src/main/java/org/onosproject/cli/net/ResourcesCommand.java
index 6c74848..1cc0463 100644
--- a/cli/src/main/java/org/onosproject/cli/net/ResourcesCommand.java
+++ b/cli/src/main/java/org/onosproject/cli/net/ResourcesCommand.java
@@ -19,6 +19,8 @@
 
 import java.util.Set;
 import java.util.HashSet;
+import java.util.List;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
@@ -26,13 +28,23 @@
 import org.apache.karaf.shell.commands.Argument;
 import org.apache.karaf.shell.commands.Command;
 import org.apache.karaf.shell.commands.Option;
+import org.onlab.packet.MplsLabel;
+import org.onlab.packet.VlanId;
 import org.onosproject.cli.AbstractShellCommand;
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.PortNumber;
+import org.onosproject.net.TributarySlot;
 import org.onosproject.net.newresource.Resource;
 import org.onosproject.net.newresource.ResourceService;
 
 import com.google.common.base.Strings;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.DiscreteDomain;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Range;
+import com.google.common.collect.RangeSet;
+import com.google.common.collect.TreeRangeSet;
 
 /**
  * Lists available resources.
@@ -87,7 +99,8 @@
     }
 
     private void printResource(Resource resource, int level) {
-        Collection<Resource> children = resourceService.getAvailableResources(resource);
+        // TODO add an option to show only available resource
+        Collection<Resource> children = resourceService.getRegisteredResources(resource);
 
         if (resource.equals(Resource.ROOT)) {
             print("ROOT");
@@ -99,24 +112,88 @@
                 return;
             }
 
-            String toString = String.valueOf(resource.last());
-            if (toString.startsWith(resourceName)) {
-                print("%s%s", Strings.repeat(" ", level),
-                              toString);
+            if (resource instanceof Resource.Continuous) {
+                print("%s%s: %f", Strings.repeat(" ", level),
+                                  resource.last(),
+                                  ((Resource.Continuous) resource).value());
+                // Continuous resource is terminal node, stop here
+                return;
             } else {
-                print("%s%s: %s", Strings.repeat(" ", level),
-                                 resourceName,
-                                 toString);
+
+                String toString = String.valueOf(resource.last());
+                if (toString.startsWith(resourceName)) {
+                    print("%s%s", Strings.repeat(" ", level),
+                          toString);
+                } else {
+                    print("%s%s: %s", Strings.repeat(" ", level),
+                          resourceName,
+                          toString);
+                }
             }
         }
 
+
+        // Classify children into aggregatable terminal resources and everything else
+
+        Set<Class<?>> aggregatableTypes = ImmutableSet.<Class<?>>builder()
+                .add(VlanId.class)
+                .add(MplsLabel.class)
+                .build();
+        // (last() resource name) -> { Resource }
+        Multimap<String, Resource> aggregatables = ArrayListMultimap.create();
+        List<Resource> nonAggregatable = new ArrayList<>();
+
+        for (Resource r : children) {
+            if (r instanceof Resource.Continuous) {
+                // non-aggregatable terminal node
+                nonAggregatable.add(r);
+            } else if (aggregatableTypes.contains(r.last().getClass())) {
+                // aggregatable & terminal node
+                aggregatables.put(r.last().getClass().getSimpleName(), r);
+            } else {
+                nonAggregatable.add(r);
+            }
+        }
+
+        // print aggregated (terminal)
+        aggregatables.asMap().entrySet()
+            .forEach(e -> {
+                // for each type...
+                String resourceName = e.getKey();
+
+                RangeSet<Long> rangeSet = TreeRangeSet.create();
+
+                // aggregate into RangeSet
+                e.getValue().stream()
+                    .map(Resource::last)
+                    .map(res -> {
+                            if (res instanceof VlanId) {
+                                return (long) ((VlanId) res).toShort();
+                            } else if (res instanceof MplsLabel) {
+                                return (long) ((MplsLabel) res).toInt();
+                            } else if (res instanceof TributarySlot) {
+                                return ((TributarySlot) res).index();
+                            }
+                            // TODO support Lambda (OchSignal types)
+                            return 0L;
+                        })
+                    .map(Range::singleton)
+                    .map(range -> range.canonical(DiscreteDomain.longs()))
+                    .forEach(rangeSet::add);
+
+                print("%s%s: %s", Strings.repeat(" ", level + 1),
+                      resourceName,
+                      rangeSet);
+            });
+
+
+        // print non-aggregatables (recurse)
         if (sort) {
-            children.stream()
+            nonAggregatable.stream()
                     .sorted((o1, o2) -> String.valueOf(o1.id()).compareTo(String.valueOf(o2.id())))
                     .forEach(r -> printResource(r, level + 1));
         } else {
-            // TODO: Should consider better output for leaf nodes
-            children.forEach(r -> printResource(r, level + 1));
+            nonAggregatable.forEach(r -> printResource(r, level + 1));
         }
     }
 }