ONOS-6259: Topo2 - Implement server-side highlighting model
- NOTE: Still WIP
- Implement doAggregation() in Traffic2Monitor.
- Plumb through call to get relevantSynthLinks().
- Create UiLinkId from LinkKey.
- Add reference to original UiLink in the UiSynthLink, (so we can use as a key later).
- TrafficLink enhancements:
-- Implement equals/hashCode
-- add a copy constructor
-- add mergeStats() method
-- add stats accessor methods

Change-Id: I693626971b3511b842e80cee7fcd2a252087597f
diff --git a/core/api/src/main/java/org/onosproject/ui/model/topo/UiLinkId.java b/core/api/src/main/java/org/onosproject/ui/model/topo/UiLinkId.java
index c8dae05..dd1b3b8 100644
--- a/core/api/src/main/java/org/onosproject/ui/model/topo/UiLinkId.java
+++ b/core/api/src/main/java/org/onosproject/ui/model/topo/UiLinkId.java
@@ -16,11 +16,13 @@
 
 package org.onosproject.ui.model.topo;
 
+import org.onlab.util.Identifier;
 import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.ElementId;
 import org.onosproject.net.HostId;
 import org.onosproject.net.Link;
+import org.onosproject.net.LinkKey;
 import org.onosproject.net.PortNumber;
 import org.onosproject.net.region.RegionId;
 
@@ -40,7 +42,7 @@
     private static final String E_IDENTICAL = "Region IDs cannot be same";
 
     private static final Comparator<RegionId> REGION_ID_COMPARATOR =
-            (o1, o2) -> o1.toString().compareTo(o2.toString());
+            Comparator.comparing(Identifier::toString);
 
     /**
      * Designates the directionality of an underlying (uni-directional) link.
@@ -257,16 +259,28 @@
      *
      * @param link link for which the identifier is required
      * @return link identifier
-     * @throws NullPointerException if any of the required fields are null
+     * @throws NullPointerException if src or dst connect point is null
      */
     public static UiLinkId uiLinkId(Link link) {
-        ConnectPoint src = link.src();
-        ConnectPoint dst = link.dst();
+        return canonicalizeIdentifier(link.src(), link.dst());
+    }
+
+    /**
+     * Creates the canonical link identifier from the given link key.
+     *
+     * @param lk link key
+     * @return equivalent link identifier
+     * @throws NullPointerException if src or dst connect point is null
+     */
+    public static UiLinkId uiLinkId(LinkKey lk) {
+        return canonicalizeIdentifier(lk.src(), lk.dst());
+    }
+
+    private static UiLinkId canonicalizeIdentifier(ConnectPoint src, ConnectPoint dst) {
         if (src == null || dst == null) {
             throw new NullPointerException(
-                    "null src or dst connect point: " + link);
+                    "null src or dst connect point (illegal for UiLinkId)");
         }
-
         ElementId srcId = src.elementId();
         ElementId dstId = dst.elementId();
 
diff --git a/core/api/src/main/java/org/onosproject/ui/model/topo/UiSynthLink.java b/core/api/src/main/java/org/onosproject/ui/model/topo/UiSynthLink.java
index 7e67430..af1dfa7 100644
--- a/core/api/src/main/java/org/onosproject/ui/model/topo/UiSynthLink.java
+++ b/core/api/src/main/java/org/onosproject/ui/model/topo/UiSynthLink.java
@@ -28,16 +28,20 @@
 
     private final RegionId regionId;
     private final UiLink link;
+    private final UiLink original;
 
     /**
      * Constructs a synthetic link with the given parameters.
      *
      * @param regionId the region to which the link belongs
      * @param link     the link instance
+     * @param original the original link (device or edge)
+     *                 from which this was derived
      */
-    public UiSynthLink(RegionId regionId, UiLink link) {
+    public UiSynthLink(RegionId regionId, UiLink link, UiLink original) {
         this.regionId = regionId;
         this.link = link;
+        this.original = original;
     }
 
     @Override
@@ -45,6 +49,7 @@
         return toStringHelper(this)
                 .add("region", regionId)
                 .add("link", link)
+                .add("original", original)
                 .toString();
     }
 
@@ -65,4 +70,13 @@
     public UiLink link() {
         return link;
     }
+
+    /**
+     * Returns the original link from which this was derived.
+     *
+     * @return the original link
+     */
+    public UiLink original() {
+        return original;
+    }
 }
diff --git a/core/api/src/main/java/org/onosproject/ui/model/topo/UiTopology.java b/core/api/src/main/java/org/onosproject/ui/model/topo/UiTopology.java
index 8c8740b..ff21176 100644
--- a/core/api/src/main/java/org/onosproject/ui/model/topo/UiTopology.java
+++ b/core/api/src/main/java/org/onosproject/ui/model/topo/UiTopology.java
@@ -56,7 +56,7 @@
     private static final Logger log = LoggerFactory.getLogger(UiTopology.class);
 
     private static final Comparator<UiClusterMember> CLUSTER_MEMBER_COMPARATOR =
-            (o1, o2) -> o1.idAsString().compareTo(o2.idAsString());
+            Comparator.comparing(UiClusterMember::idAsString);
 
 
     // top level mappings of topology elements by ID
@@ -67,8 +67,8 @@
     private final Map<UiLinkId, UiDeviceLink> devLinkLookup = new HashMap<>();
     private final Map<UiLinkId, UiEdgeLink> edgeLinkLookup = new HashMap<>();
 
-    // a cache of the computed synthetic links
-    private final List<UiSynthLink> synthLinks = new ArrayList<>();
+    // a cache of the computed synthetic links, keyed by ID of original UiLink
+    private final Map<UiLinkId, UiSynthLink> synthMap = new HashMap<>();
 
     // a container for devices, hosts, etc. belonging to no region
     private final UiRegion nullRegion = new UiRegion(this, null);
@@ -93,7 +93,7 @@
                 .add("#hosts", hostLookup.size())
                 .add("#dev-links", devLinkLookup.size())
                 .add("#edge-links", edgeLinkLookup.size())
-                .add("#synth-links", synthLinks.size())
+                .add("#synth-links", synthMap.size())
                 .toString();
     }
 
@@ -115,7 +115,7 @@
         devLinkLookup.clear();
         edgeLinkLookup.clear();
 
-        synthLinks.clear();
+        synthMap.clear();
 
         nullRegion.destroy();
     }
@@ -507,8 +507,10 @@
             slinks.addAll(wrapHostLinks(r));
         }
 
-        synthLinks.clear();
-        synthLinks.addAll(slinks);
+        synthMap.clear();
+        for (UiSynthLink sl : slinks) {
+            synthMap.put(sl.original().id(), sl);
+        }
     }
 
     private Set<UiSynthLink> wrapHostLinks(UiRegion region) {
@@ -519,7 +521,7 @@
 
     private UiSynthLink wrapHostLink(RegionId regionId, UiHost host) {
         UiEdgeLink elink = new UiEdgeLink(this, host.edgeLinkId());
-        return new UiSynthLink(regionId, elink);
+        return new UiSynthLink(regionId, elink, elink);
     }
 
     private UiSynthLink inferSyntheticLink(UiDeviceLink link) {
@@ -637,7 +639,7 @@
                 link = orig;
             }
         }
-        return new UiSynthLink(commonRegion, link);
+        return new UiSynthLink(commonRegion, link, orig);
     }
 
     private List<RegionId> ancestors(DeviceId id) {
@@ -667,7 +669,7 @@
      * @return synthetic links for this region
      */
     public List<UiSynthLink> findSynthLinks(RegionId regionId) {
-        return synthLinks.stream()
+        return synthMap.values().stream()
                 .filter(s -> Objects.equals(regionId, s.regionId()))
                 .collect(Collectors.toList());
     }
@@ -679,7 +681,7 @@
      * @return the synthetic link count
      */
     public int synthLinkCount() {
-        return synthLinks.size();
+        return synthMap.size();
     }
 
     /**
@@ -722,7 +724,7 @@
         }
 
         sb.append(INDENT_1).append("Synth Links").append(EOL);
-        for (UiSynthLink link : synthLinks) {
+        for (UiSynthLink link : synthMap.values()) {
             sb.append(INDENT_2).append(link).append(EOL);
         }
         sb.append("------").append(EOL);
diff --git a/core/api/src/test/java/org/onosproject/ui/model/topo/UiLinkIdTest.java b/core/api/src/test/java/org/onosproject/ui/model/topo/UiLinkIdTest.java
index 80666d6..39d4cb4 100644
--- a/core/api/src/test/java/org/onosproject/ui/model/topo/UiLinkIdTest.java
+++ b/core/api/src/test/java/org/onosproject/ui/model/topo/UiLinkIdTest.java
@@ -23,6 +23,7 @@
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.HostId;
 import org.onosproject.net.Link;
+import org.onosproject.net.LinkKey;
 import org.onosproject.net.PortNumber;
 import org.onosproject.net.provider.ProviderId;
 import org.onosproject.net.region.RegionId;
@@ -164,4 +165,20 @@
         assertEquals("port", P1, id.portB());
     }
 
+    @Test
+    public void fromLinkKey() {
+        title("fromLinkKey");
+
+        LinkKey lk1 = LinkKey.linkKey(CP_X1, CP_Y2);
+        print("link-key-1: %s", lk1);
+        LinkKey lk2 = LinkKey.linkKey(CP_Y2, CP_X1);
+        print("link-key-2: %s", lk2);
+
+        UiLinkId id1 = UiLinkId.uiLinkId(lk1);
+        print("identifier-1: %s", id1);
+        UiLinkId id2 = UiLinkId.uiLinkId(lk2);
+        print("identifier-2: %s", id2);
+
+        assertEquals("unequal canon-ids", id1, id2);
+    }
 }