ONOS-6259: Topo2 - Implement server-side highlighting model (WIP)
- Enhanced BiLink to allow a UiLinkId as its identifier.
- Corrected aggregation of traffic data for synthetic links in regions.
- Removed TrafficLink copy constructor (not needed).

Change-Id: Ia7d84f28ec69737e7474bfc100262b837effef73
diff --git a/core/api/src/main/java/org/onosproject/ui/topo/BiLink.java b/core/api/src/main/java/org/onosproject/ui/topo/BiLink.java
index b50e34d..be7fa90 100644
--- a/core/api/src/main/java/org/onosproject/ui/topo/BiLink.java
+++ b/core/api/src/main/java/org/onosproject/ui/topo/BiLink.java
@@ -18,6 +18,7 @@
 
 import org.onosproject.net.Link;
 import org.onosproject.net.LinkKey;
+import org.onosproject.ui.model.topo.UiLinkId;
 
 import static com.google.common.base.Preconditions.checkNotNull;
 
@@ -26,9 +27,14 @@
  * <p>
  * Subclasses will decide how to generate the link highlighting (coloring
  * and labeling) for the topology view.
+ * <p>
+ * As an alternative, a bi-link can be initialized with a {@link UiLinkId}
+ * (ignoring the LinkKey and links one and two), which will be reported as
+ * its identifier instead.
  */
 public abstract class BiLink {
 
+    private final UiLinkId uiLinkId;
     private final LinkKey key;
     private final Link one;
     private Link two;
@@ -44,6 +50,19 @@
     public BiLink(LinkKey key, Link link) {
         this.key = checkNotNull(key);
         this.one = checkNotNull(link);
+        this.uiLinkId = null;
+    }
+
+    /**
+     * Constructs a bi-link for the given UI link identifier; sets remaining
+     * fields to null.
+     *
+     * @param uilinkId canonical ID for this bi-link
+     */
+    public BiLink(UiLinkId uilinkId) {
+        this.uiLinkId = checkNotNull(uilinkId);
+        this.key = null;
+        this.one = null;
     }
 
     /**
@@ -62,7 +81,16 @@
      * @return link identifier
      */
     public String linkId() {
-        return key.asId();
+        return uiLinkId != null ? uiLinkId.toString() : key.asId();
+    }
+
+    /**
+     * Returns the UI link identifier for this bi-link (if set).
+     *
+     * @return the UI link ID
+     */
+    public UiLinkId uiLinkId() {
+        return uiLinkId;
     }
 
     /**
@@ -94,7 +122,7 @@
 
     @Override
     public String toString() {
-        return key.asId();
+        return linkId();
     }
 
     /**
diff --git a/core/api/src/test/java/org/onosproject/ui/topo/BiLinkTest.java b/core/api/src/test/java/org/onosproject/ui/topo/BiLinkTest.java
index df6d3f7..937014f 100644
--- a/core/api/src/test/java/org/onosproject/ui/topo/BiLinkTest.java
+++ b/core/api/src/test/java/org/onosproject/ui/topo/BiLinkTest.java
@@ -17,6 +17,8 @@
 package org.onosproject.ui.topo;
 
 import org.junit.Test;
+import org.onosproject.net.region.RegionId;
+import org.onosproject.ui.model.topo.UiLinkId;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
@@ -28,6 +30,10 @@
 
     private static final String EXP_ID_AB = "device-a/1-device-b/2";
 
+    private static final RegionId RA = RegionId.regionId("rA");
+    private static final RegionId RB = RegionId.regionId("rB");
+    private static final String EXP_RA_RB = "rA~rB";
+
     private BiLink blink;
 
     @Test
@@ -77,5 +83,17 @@
         print(blink);
         assertEquals("non-canon BA", expected, blink.linkId());
     }
+
+    @Test
+    public void uiLinkId() {
+        blink = new ConcreteLink(UiLinkId.uiLinkId(RA, RB));
+        print(blink);
+        assertEquals("non-canon AB", EXP_RA_RB, blink.linkId());
+
+        assertNull("key not null", blink.key());
+        assertNull("one not null", blink.one());
+        assertNull("two not null", blink.two());
+    }
+
 }
 
diff --git a/core/api/src/test/java/org/onosproject/ui/topo/BiLinkTestBase.java b/core/api/src/test/java/org/onosproject/ui/topo/BiLinkTestBase.java
index 77b6858..f713b81 100644
--- a/core/api/src/test/java/org/onosproject/ui/topo/BiLinkTestBase.java
+++ b/core/api/src/test/java/org/onosproject/ui/topo/BiLinkTestBase.java
@@ -25,6 +25,7 @@
 import org.onosproject.net.driver.Behaviour;
 import org.onosproject.net.provider.ProviderId;
 import org.onosproject.ui.AbstractUiTest;
+import org.onosproject.ui.model.topo.UiLinkId;
 
 /**
  * Base class for unit tests of {@link BiLink} and {@link BiLinkMap}.
@@ -110,6 +111,10 @@
             super(key, link);
         }
 
+        public ConcreteLink(UiLinkId uiLinkId) {
+            super(uiLinkId);
+        }
+
         @Override
         public LinkHighlight highlight(Enum<?> type) {
             return null;
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/TrafficMonitorBase.java b/web/gui/src/main/java/org/onosproject/ui/impl/TrafficMonitorBase.java
index c59f2c5..4ab9796 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/TrafficMonitorBase.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/TrafficMonitorBase.java
@@ -270,7 +270,6 @@
         addEdgeLinks(linkMap);
 
         Set<TrafficLink> linksWithTraffic = new HashSet<>();
-        // TODO: consider whether a map would be better...
 
         for (TrafficLink tlink : linkMap.biLinks()) {
             if (type == TrafficLink.StatsType.FLOW_STATS) {
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/topo/Traffic2Monitor.java b/web/gui/src/main/java/org/onosproject/ui/impl/topo/Traffic2Monitor.java
index 60e7e83..bccc63c 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/topo/Traffic2Monitor.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/topo/Traffic2Monitor.java
@@ -31,6 +31,8 @@
 import java.util.Map;
 import java.util.Set;
 
+import static org.onosproject.ui.model.topo.UiLinkId.uiLinkId;
+
 /**
  * Encapsulates the behavior of monitoring specific traffic patterns in the
  * Topology-2 view.
@@ -112,17 +114,13 @@
         Map<UiLinkId, TrafficLink> mappedByUiLinkId = new HashMap<>();
 
         for (TrafficLink tl : linksWithTraffic) {
-            UiLinkId tlid = UiLinkId.uiLinkId(tl.key());
+            UiLinkId tlid = uiLinkId(tl.key());
             UiSynthLink sl = synthLinkMap.get(tlid);
             if (sl != null) {
                 UiLinkId aggrid = sl.link().id();
-                TrafficLink aggregated = mappedByUiLinkId.get(aggrid);
-                if (aggregated == null) {
-                    aggregated = new TrafficLink(tl);
-                    mappedByUiLinkId.put(aggrid, aggregated);
-                } else {
-                    aggregated.mergeStats(tl);
-                }
+                TrafficLink aggregated =
+                        mappedByUiLinkId.computeIfAbsent(aggrid, TrafficLink::new);
+                aggregated.mergeStats(tl);
             }
         }
 
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/topo/util/TrafficLink.java b/web/gui/src/main/java/org/onosproject/ui/impl/topo/util/TrafficLink.java
index f2643c6..02a8083 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/topo/util/TrafficLink.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/topo/util/TrafficLink.java
@@ -19,6 +19,7 @@
 import org.onosproject.net.Link;
 import org.onosproject.net.LinkKey;
 import org.onosproject.net.statistic.Load;
+import org.onosproject.ui.model.topo.UiLinkId;
 import org.onosproject.ui.topo.BiLink;
 import org.onosproject.ui.topo.LinkHighlight;
 import org.onosproject.ui.topo.LinkHighlight.Flavor;
@@ -70,22 +71,17 @@
         super(key, link);
     }
 
+
     /**
-     * Copy constructor.
+     * Returns an "empty" traffic link (one with no underlying links or stats)
+     * with the given identifier. This is useful when we want to aggregate
+     * stats from other links into a single entity (such as a region-region
+     * link reporting the stats for the links that compose it).
      *
-     * @param copy the instance to copy
+     * @param id the link identifier
      */
-    public TrafficLink(TrafficLink copy) {
-        super(copy.key(), copy.one());
-        setOther(copy.two());
-        bytes = copy.bytes;
-        rate = copy.rate;
-        flows = copy.flows;
-        taggedFlavor = copy.taggedFlavor;
-        hasTraffic = copy.hasTraffic;
-        isOptical = copy.isOptical;
-        antMarch = copy.antMarch;
-        mods.addAll(copy.mods);
+    public TrafficLink(UiLinkId id) {
+        super(id);
     }
 
     @Override
@@ -121,7 +117,7 @@
     @Override
     public String toString() {
         return toStringHelper(this)
-                .add("key", key())
+                .add("linkId", linkId())
                 .add("bytes", bytes)
                 .add("rate", rate)
                 .add("flows", flows)
diff --git a/web/gui/src/test/java/org/onosproject/ui/impl/topo/util/TrafficLinkTest.java b/web/gui/src/test/java/org/onosproject/ui/impl/topo/util/TrafficLinkTest.java
index 2afa8a0..a1f3d71 100644
--- a/web/gui/src/test/java/org/onosproject/ui/impl/topo/util/TrafficLinkTest.java
+++ b/web/gui/src/test/java/org/onosproject/ui/impl/topo/util/TrafficLinkTest.java
@@ -24,9 +24,11 @@
 import org.onosproject.net.Link;
 import org.onosproject.net.LinkKey;
 import org.onosproject.net.PortNumber;
+import org.onosproject.net.region.RegionId;
 import org.onosproject.net.statistic.DefaultLoad;
 import org.onosproject.net.statistic.Load;
 import org.onosproject.ui.impl.AbstractUiImplTest;
+import org.onosproject.ui.model.topo.UiLinkId;
 import org.onosproject.ui.topo.TopoUtils;
 
 import static com.google.common.base.Preconditions.checkNotNull;
@@ -45,10 +47,11 @@
     private static final PortNumber P2 = portNumber(2);
 
     private static final ConnectPoint SRC1 = new ConnectPoint(D1, P1);
-    private static final ConnectPoint DST1 = new ConnectPoint(D2, P1);
     private static final ConnectPoint DST2 = new ConnectPoint(D2, P2);
 
-    private static final LinkKey X = LinkKey.linkKey(SRC1, DST2);
+    private static final RegionId RA = RegionId.regionId("rA");
+    private static final RegionId RB = RegionId.regionId("rB");
+    private static final String EXP_RA_RB = "rA~rB";
 
 
     private TrafficLink createALink() {
@@ -76,20 +79,11 @@
     }
 
     @Test
-    public void copyConstructor() {
-        title("copy-constructor");
-        TrafficLink tlOrig = createALink();
-        TrafficLink tlCopy = new TrafficLink(tlOrig);
-        assertEquals("not copied correctly (1)", tlOrig, tlCopy);
-
-        tlOrig.addLoad(new DefaultLoad(2000, 0));
-        tlCopy = new TrafficLink(tlOrig);
-        assertEquals("not copied correctly (2)", tlOrig, tlCopy);
-
-        tlOrig = createALink();
-        tlOrig.addFlows(345);
-        tlCopy = new TrafficLink(tlOrig);
-        assertEquals("not copied correctly (3)", tlOrig, tlCopy);
+    public void emptyStats() {
+        title("emptyStats");
+        UiLinkId uiLinkId = UiLinkId.uiLinkId(RA, RB);
+        TrafficLink tl = new TrafficLink(uiLinkId);
+        assertEquals("wrong id", EXP_RA_RB, tl.linkId());
     }
 
     @Test