Topo2 - UiSynthLink aggregation in JSON structure.
- implemented aggregation of data

Change-Id: Ie54cd768c11a4e6345a541c3ba4b9992801cefdd
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/topo/Topo2Jsonifier.java b/web/gui/src/main/java/org/onosproject/ui/impl/topo/Topo2Jsonifier.java
index 8b5741a..85deaa1 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/topo/Topo2Jsonifier.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/topo/Topo2Jsonifier.java
@@ -51,6 +51,7 @@
 import org.onosproject.ui.model.topo.UiElement;
 import org.onosproject.ui.model.topo.UiHost;
 import org.onosproject.ui.model.topo.UiLink;
+import org.onosproject.ui.model.topo.UiLinkId;
 import org.onosproject.ui.model.topo.UiModelEvent;
 import org.onosproject.ui.model.topo.UiNode;
 import org.onosproject.ui.model.topo.UiRegion;
@@ -364,13 +365,7 @@
     }
 
     protected JsonNode jsonLinks(List<UiSynthLink> links) {
-        ArrayNode synthLinks = arrayNode();
-        links.forEach(l -> synthLinks.add(json(l)));
-
-        // TODO: implement the following........
-//        collateSynthLinks(synthLinks, links);
-
-        return synthLinks;
+        return collateSynthLinks(links);
     }
 
     private ArrayNode jsonStrings(List<String> strings) {
@@ -581,15 +576,42 @@
         return node;
     }
 
-    // TODO: add method to JSONify an aggregated collection of synth links
+    private ArrayNode collateSynthLinks(List<UiSynthLink> links) {
+        Map<UiLinkId, Set<UiSynthLink>> collation = new HashMap<>();
 
-    private void collateSynthLinks(ArrayNode array,
-                                   List<UiSynthLink> links) {
-        // TODO - combine via link id
+        // first, group together the synthlinks into sets per ID...
+        for (UiSynthLink sl : links) {
+            UiLinkId id = sl.link().id();
+            Set<UiSynthLink> rollup =
+                    collation.computeIfAbsent(id, k -> new HashSet<>());
+            rollup.add(sl);
+        }
+
+        // now add json nodes per set, and return the array of them
+        ArrayNode array = arrayNode();
+        for (UiLinkId id : collation.keySet()) {
+            array.add(json(collation.get(id)));
+        }
+        return array;
     }
 
-    private ObjectNode json(UiSynthLink sLink) {
-        return json(sLink.link());
+    private ObjectNode json(Set<UiSynthLink> memberSet) {
+        ArrayNode rollup = arrayNode();
+        ObjectNode node = null;
+
+        boolean first = true;
+        for (UiSynthLink member : memberSet) {
+            UiLink link = member.link();
+            if (first) {
+                node = json(link);
+                first = false;
+            }
+            rollup.add(json(member.original()));
+        }
+        if (node != null) {
+            node.set("rollup", rollup);
+        }
+        return node;
     }
 
     private ObjectNode json(UiLink link) {
diff --git a/web/gui/src/test/java/org/onosproject/ui/impl/topo/Topo2JsonifierTest.java b/web/gui/src/test/java/org/onosproject/ui/impl/topo/Topo2JsonifierTest.java
index c1ff3de..c4560b8 100644
--- a/web/gui/src/test/java/org/onosproject/ui/impl/topo/Topo2JsonifierTest.java
+++ b/web/gui/src/test/java/org/onosproject/ui/impl/topo/Topo2JsonifierTest.java
@@ -17,6 +17,8 @@
 package org.onosproject.ui.impl.topo;
 
 import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import org.junit.Test;
@@ -34,11 +36,13 @@
 import org.onosproject.ui.model.topo.UiSynthLink;
 
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
 import static org.onosproject.net.DeviceId.deviceId;
 import static org.onosproject.net.region.RegionId.regionId;
 import static org.onosproject.ui.model.topo.UiLinkId.uiLinkId;
@@ -287,9 +291,41 @@
     @Test
     public void encodeSynthLinks() {
         title("encodeSynthLinks()");
-        JsonNode node = t2.jsonLinks(createSynthLinks());
-        print(node);
-        // TODO: assert structure of JSON created so we know we got it right
+        ArrayNode array = (ArrayNode) t2.jsonLinks(createSynthLinks());
+        print(array);
 
+        assertEquals("wrong size", 2, array.size());
+        ObjectNode first = (ObjectNode) array.get(0);
+        ObjectNode second = (ObjectNode) array.get(1);
+
+        boolean firstIsAB = first.get("id").asText().equals("rA~rB");
+        if (firstIsAB) {
+            validateSynthLinks(first, second);
+        } else {
+            validateSynthLinks(second, first);
+        }
+    }
+
+    private void validateSynthLinks(ObjectNode ab, ObjectNode bc) {
+        validateLinkRollup(ab, RA_RB, D1_D3, D2_D4);
+        validateLinkRollup(bc, RB_RC, D3_D5, D4_D6);
+    }
+
+    private void validateLinkRollup(ObjectNode link, UiLinkId id,
+                                    UiLinkId... expInRollup) {
+        String actId = link.get("id").asText();
+        assertEquals("unexp id", id.toString(), actId);
+
+        Set<String> rollupIds = new HashSet<>();
+        ArrayNode rollupArray = (ArrayNode) link.get("rollup");
+
+        for (JsonNode n : rollupArray) {
+            ObjectNode o = (ObjectNode) n;
+            rollupIds.add(o.get("id").asText());
+        }
+
+        for (UiLinkId expId : expInRollup) {
+            assertTrue("missing exp id: " + expId, rollupIds.contains(expId.toString()));
+        }
     }
 }