ONOS-2186 - GUI Topo Overlay - (WIP)
- split BiLink into abstract superclass and concrete subclasses.
- created BiLinkMap to collate bilinks (and derivative subclasses).
- added missing Javadocs, and other general cleanup.

Change-Id: Icfa85bc44a223c6cf245a4005170583dad1cc801
diff --git a/core/api/src/main/java/org/onosproject/ui/topo/AbstractHighlight.java b/core/api/src/main/java/org/onosproject/ui/topo/AbstractHighlight.java
index 23cd7d8..5e7ef4b 100644
--- a/core/api/src/main/java/org/onosproject/ui/topo/AbstractHighlight.java
+++ b/core/api/src/main/java/org/onosproject/ui/topo/AbstractHighlight.java
@@ -18,22 +18,38 @@
 package org.onosproject.ui.topo;
 
 /**
- * Partial implementation of the types of highlight to apply to topology
- * elements.
+ * Partial implementation of the highlighting to apply to topology
+ * view elements.
  */
 public abstract class AbstractHighlight {
     private final TopoElementType type;
     private final String elementId;
 
+    /**
+     * Constructs the highlight.
+     *
+     * @param type highlight element type
+     * @param elementId element identifier
+     */
     public AbstractHighlight(TopoElementType type, String elementId) {
         this.type = type;
         this.elementId = elementId;
     }
 
+    /**
+     * Returns the element type.
+     *
+     * @return element type
+     */
     public TopoElementType type() {
         return type;
     }
 
+    /**
+     * Returns the element identifier.
+     *
+     * @return element identifier
+     */
     public String elementId() {
         return elementId;
     }
diff --git a/core/api/src/main/java/org/onosproject/ui/topo/DeviceHighlight.java b/core/api/src/main/java/org/onosproject/ui/topo/DeviceHighlight.java
index 1b721b8..fe1ecb2 100644
--- a/core/api/src/main/java/org/onosproject/ui/topo/DeviceHighlight.java
+++ b/core/api/src/main/java/org/onosproject/ui/topo/DeviceHighlight.java
@@ -18,7 +18,7 @@
 package org.onosproject.ui.topo;
 
 /**
- * Denotes the types of highlight to apply to a link.
+ * Denotes the highlighting to apply to a device.
  */
 public class DeviceHighlight extends AbstractHighlight {
 
@@ -26,5 +26,8 @@
         super(TopoElementType.DEVICE, deviceId);
     }
 
+    // TODO: implement device highlighting:
+    //   - visual highlight
+    //   - badging
 
 }
diff --git a/core/api/src/main/java/org/onosproject/ui/topo/HostHighlight.java b/core/api/src/main/java/org/onosproject/ui/topo/HostHighlight.java
index ff8b3be..cb64e07 100644
--- a/core/api/src/main/java/org/onosproject/ui/topo/HostHighlight.java
+++ b/core/api/src/main/java/org/onosproject/ui/topo/HostHighlight.java
@@ -18,7 +18,7 @@
 package org.onosproject.ui.topo;
 
 /**
- * Denotes the types of highlight to apply to a link.
+ * Denotes the highlighting to apply to a host.
  */
 public class HostHighlight extends AbstractHighlight {
 
@@ -26,5 +26,8 @@
         super(TopoElementType.HOST, hostId);
     }
 
+    // TODO: implement host highlighting:
+    //   - visual highlight
+    //   - badging
 
 }
diff --git a/core/api/src/main/java/org/onosproject/ui/topo/LinkHighlight.java b/core/api/src/main/java/org/onosproject/ui/topo/LinkHighlight.java
index d8e4279..b4e4330 100644
--- a/core/api/src/main/java/org/onosproject/ui/topo/LinkHighlight.java
+++ b/core/api/src/main/java/org/onosproject/ui/topo/LinkHighlight.java
@@ -27,8 +27,8 @@
  * Denotes the highlighting to be applied to a link.
  * {@link Flavor} is a closed set of NO-, PRIMARY-, or SECONDARY- highlighting.
  * {@link Mod} is an open ended set of additional modifications (CSS classes)
- * to apply. Note that {@link #MOD_OPTICAL} and {@link #MOD_ANIMATED} are
- * pre-defined mods.
+ * that may also be applied.
+ * Note that {@link #MOD_OPTICAL} and {@link #MOD_ANIMATED} are pre-defined mods.
  * Label text may be set, which will also be displayed on the link.
  */
 public class LinkHighlight extends AbstractHighlight {
@@ -136,48 +136,6 @@
     }
 
     /**
-     * Link highlighting modification.
-     * <p>
-     * Note that this translates to a CSS class name that is applied to
-     * the link in the Topology UI.
-     */
-    public static final class Mod implements Comparable<Mod> {
-        private final String modId;
-
-        public Mod(String modId) {
-            this.modId = checkNotNull(modId);
-        }
-
-        @Override
-        public String toString() {
-            return modId;
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) {
-                return true;
-            }
-            if (o == null || getClass() != o.getClass()) {
-                return false;
-            }
-            Mod mod = (Mod) o;
-            return modId.equals(mod.modId);
-        }
-
-        @Override
-        public int hashCode() {
-            return modId.hashCode();
-        }
-
-
-        @Override
-        public int compareTo(Mod o) {
-            return this.modId.compareTo(o.modId);
-        }
-    }
-
-    /**
      * Denotes a link to be tagged as an optical link.
      */
     public static final Mod MOD_OPTICAL = new Mod("optical");
diff --git a/core/api/src/main/java/org/onosproject/ui/topo/Mod.java b/core/api/src/main/java/org/onosproject/ui/topo/Mod.java
new file mode 100644
index 0000000..d21a872
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/ui/topo/Mod.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2015 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.onosproject.ui.topo;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Highlighting modification.
+ * <p>
+ * Note that (for link highlights) this translates to a CSS class name
+ * that is applied to the link in the Topology UI.
+ */
+public final class Mod implements Comparable<Mod> {
+    private final String modId;
+
+    /**
+     * Constructs a mod with the given identifier.
+     *
+     * @param modId modification identifier
+     */
+    public Mod(String modId) {
+        this.modId = checkNotNull(modId);
+    }
+
+    @Override
+    public String toString() {
+        return modId;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        Mod mod = (Mod) o;
+        return modId.equals(mod.modId);
+    }
+
+    @Override
+    public int hashCode() {
+        return modId.hashCode();
+    }
+
+    @Override
+    public int compareTo(Mod o) {
+        return this.modId.compareTo(o.modId);
+    }
+}
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/LinkViewMessageHandler.java b/web/gui/src/main/java/org/onosproject/ui/impl/LinkViewMessageHandler.java
index 18a5acd..c182180 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/LinkViewMessageHandler.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/LinkViewMessageHandler.java
@@ -18,21 +18,18 @@
 
 import com.fasterxml.jackson.databind.node.ObjectNode;
 import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Maps;
 import org.onosproject.net.Link;
-import org.onosproject.net.LinkKey;
 import org.onosproject.net.link.LinkService;
 import org.onosproject.ui.RequestHandler;
 import org.onosproject.ui.UiMessageHandler;
-import org.onosproject.ui.impl.topo.BiLink;
-import org.onosproject.ui.impl.topo.TopoUtils;
+import org.onosproject.ui.impl.topo.BaseLink;
+import org.onosproject.ui.impl.topo.BaseLinkMap;
 import org.onosproject.ui.table.TableModel;
 import org.onosproject.ui.table.TableRequestHandler;
 import org.onosproject.ui.table.cell.ConnectPointFormatter;
 import org.onosproject.ui.table.cell.EnumFormatter;
 
 import java.util.Collection;
-import java.util.Map;
 
 /**
  * Message handler for link view related messages.
@@ -41,6 +38,7 @@
 
     private static final String A_BOTH_B = "A &harr; B";
     private static final String A_SINGLE_B = "A &rarr; B";
+    private static final String SLASH = " / ";
 
     private static final String LINK_DATA_REQ = "linkDataRequest";
     private static final String LINK_DATA_RESP = "linkDataResponse";
@@ -93,42 +91,37 @@
         @Override
         protected void populateTable(TableModel tm, ObjectNode payload) {
             LinkService ls = get(LinkService.class);
-
-            // First consolidate all uni-directional links into two-directional ones.
-            Map<LinkKey, BiLink> biLinks = Maps.newHashMap();
-            ls.getLinks().forEach(link -> TopoUtils.addLink(biLinks, link));
-
-            // Now scan over all bi-links and produce table rows from them.
-            biLinks.values().forEach(biLink -> populateRow(tm.addRow(), biLink));
+            BaseLinkMap linkMap = new BaseLinkMap();
+            ls.getLinks().forEach(linkMap::add);
+            linkMap.biLinks().forEach(blink -> populateRow(tm.addRow(), blink));
         }
 
-        private void populateRow(TableModel.Row row, BiLink biLink) {
-            row.cell(ONE, biLink.one().src())
-                .cell(TWO, biLink.one().dst())
-                .cell(TYPE, linkType(biLink))
-                .cell(STATE, linkState(biLink))
-                .cell(DIRECTION, linkDir(biLink))
-                .cell(DURABLE, biLink.one().isDurable());
+        private void populateRow(TableModel.Row row, BaseLink blink) {
+            row.cell(ONE, blink.one().src())
+                .cell(TWO, blink.one().dst())
+                .cell(TYPE, linkType(blink))
+                .cell(STATE, linkState(blink))
+                .cell(DIRECTION, linkDir(blink))
+                .cell(DURABLE, blink.one().isDurable());
         }
 
-        private String linkType(BiLink link) {
+        private String linkType(BaseLink link) {
             StringBuilder sb = new StringBuilder();
             sb.append(link.one().type());
             if (link.two() != null && link.two().type() != link.one().type()) {
-                sb.append(" / ").append(link.two().type());
+                sb.append(SLASH).append(link.two().type());
             }
             return sb.toString();
         }
 
-        private String linkState(BiLink link) {
+        private String linkState(BaseLink link) {
             return (link.one().state() == Link.State.ACTIVE ||
                     link.two().state() == Link.State.ACTIVE) ?
                     ICON_ID_ONLINE : ICON_ID_OFFLINE;
         }
 
-        private String linkDir(BiLink link) {
+        private String linkDir(BaseLink link) {
             return link.two() != null ? A_BOTH_B : A_SINGLE_B;
         }
     }
-
 }
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandler.java b/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandler.java
index c2f54e4..923c3db 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandler.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandler.java
@@ -56,7 +56,7 @@
 import org.onosproject.ui.JsonUtils;
 import org.onosproject.ui.RequestHandler;
 import org.onosproject.ui.UiConnection;
-import org.onosproject.ui.impl.TrafficMonitorObject.Mode;
+import org.onosproject.ui.impl.TrafficMonitor.Mode;
 import org.onosproject.ui.impl.topo.NodeSelection;
 import org.onosproject.ui.topo.Highlights;
 import org.onosproject.ui.topo.PropertyPanel;
@@ -162,9 +162,8 @@
     private final ExecutorService msgSender =
             newSingleThreadExecutor(groupedThreads("onos/gui", "msg-sender"));
 
-    private TrafficMonitorObject tmo;
-
     private TopoOverlayCache overlayCache;
+    private TrafficMonitor traffic;
 
     private TimerTask summaryTask = null;
     private boolean summaryRunning = false;
@@ -176,7 +175,7 @@
     public void init(UiConnection connection, ServiceDirectory directory) {
         super.init(connection, directory);
         appId = directory.get(CoreService.class).registerApplication(APP_ID);
-        tmo = new TrafficMonitorObject(TRAFFIC_PERIOD, servicesBundle, this);
+        traffic = new TrafficMonitor(TRAFFIC_PERIOD, servicesBundle, this);
     }
 
     @Override
@@ -275,7 +274,7 @@
         @Override
         public void process(long sid, ObjectNode payload) {
             stopSummaryMonitoring();
-            tmo.stop();
+            traffic.stopMonitoring();
         }
     }
 
@@ -400,7 +399,7 @@
                     .build();
 
             intentService.submit(intent);
-            tmo.monitor(intent);
+            traffic.monitor(intent);
         }
     }
 
@@ -433,7 +432,7 @@
                             .build();
 
             intentService.submit(intent);
-            tmo.monitor(intent);
+            traffic.monitor(intent);
         }
     }
 
@@ -446,7 +445,7 @@
 
         @Override
         public void process(long sid, ObjectNode payload) {
-            tmo.monitor(Mode.ALL_FLOW_TRAFFIC);
+            traffic.monitor(Mode.ALL_FLOW_TRAFFIC);
         }
     }
 
@@ -457,7 +456,7 @@
 
         @Override
         public void process(long sid, ObjectNode payload) {
-            tmo.monitor(Mode.ALL_PORT_TRAFFIC);
+            traffic.monitor(Mode.ALL_PORT_TRAFFIC);
         }
     }
 
@@ -470,7 +469,7 @@
         public void process(long sid, ObjectNode payload) {
             NodeSelection nodeSelection =
                     new NodeSelection(payload, deviceService, hostService);
-            tmo.monitor(Mode.DEV_LINK_FLOWS, nodeSelection);
+            traffic.monitor(Mode.DEV_LINK_FLOWS, nodeSelection);
         }
     }
 
@@ -483,7 +482,7 @@
         public void process(long sid, ObjectNode payload) {
             NodeSelection nodeSelection =
                     new NodeSelection(payload, deviceService, hostService);
-            tmo.monitor(Mode.RELATED_INTENTS, nodeSelection);
+            traffic.monitor(Mode.RELATED_INTENTS, nodeSelection);
         }
     }
 
@@ -494,7 +493,7 @@
 
         @Override
         public void process(long sid, ObjectNode payload) {
-            tmo.selectNextIntent();
+            traffic.selectNextIntent();
         }
     }
 
@@ -505,7 +504,7 @@
 
         @Override
         public void process(long sid, ObjectNode payload) {
-            tmo.selectPreviousIntent();
+            traffic.selectPreviousIntent();
         }
     }
 
@@ -516,7 +515,7 @@
 
         @Override
         public void process(long sid, ObjectNode payload) {
-            tmo.monitor(Mode.SEL_INTENT);
+            traffic.monitor(Mode.SELECTED_INTENT);
         }
     }
 
@@ -527,7 +526,7 @@
 
         @Override
         public void process(long sid, ObjectNode payload) {
-            tmo.stop();
+            traffic.stopMonitoring();
         }
     }
 
@@ -557,7 +556,7 @@
 
     private void cancelAllRequests() {
         stopSummaryMonitoring();
-        tmo.stop();
+        traffic.stopMonitoring();
     }
 
     // Sends all controller nodes to the client as node-added messages.
@@ -726,7 +725,7 @@
     private class InternalIntentListener implements IntentListener {
         @Override
         public void event(IntentEvent event) {
-            msgSender.execute(tmo::pokeIntent);
+            msgSender.execute(traffic::pokeIntent);
             eventAccummulator.add(event);
         }
     }
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/TrafficMonitorObject.java b/web/gui/src/main/java/org/onosproject/ui/impl/TrafficMonitor.java
similarity index 75%
rename from web/gui/src/main/java/org/onosproject/ui/impl/TrafficMonitorObject.java
rename to web/gui/src/main/java/org/onosproject/ui/impl/TrafficMonitor.java
index 5b9f6a4..cea5899 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/TrafficMonitorObject.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/TrafficMonitor.java
@@ -22,7 +22,6 @@
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.Host;
 import org.onosproject.net.Link;
-import org.onosproject.net.LinkKey;
 import org.onosproject.net.PortNumber;
 import org.onosproject.net.flow.FlowEntry;
 import org.onosproject.net.flow.TrafficTreatment;
@@ -35,14 +34,14 @@
 import org.onosproject.net.intent.OpticalPathIntent;
 import org.onosproject.net.intent.PathIntent;
 import org.onosproject.net.statistic.Load;
-import org.onosproject.ui.impl.topo.BiLink;
 import org.onosproject.ui.impl.topo.IntentSelection;
-import org.onosproject.ui.impl.topo.LinkStatsType;
 import org.onosproject.ui.impl.topo.NodeSelection;
 import org.onosproject.ui.impl.topo.ServicesBundle;
 import org.onosproject.ui.impl.topo.TopoUtils;
-import org.onosproject.ui.impl.topo.TopologyViewIntentFilter;
+import org.onosproject.ui.impl.topo.TopoIntentFilter;
 import org.onosproject.ui.impl.topo.TrafficClass;
+import org.onosproject.ui.impl.topo.TrafficLink;
+import org.onosproject.ui.impl.topo.TrafficLinkMap;
 import org.onosproject.ui.topo.Highlights;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -59,21 +58,21 @@
 import java.util.TimerTask;
 
 import static org.onosproject.net.DefaultEdgeLink.createEdgeLink;
-import static org.onosproject.ui.impl.TrafficMonitorObject.Mode.IDLE;
-import static org.onosproject.ui.impl.TrafficMonitorObject.Mode.SEL_INTENT;
+import static org.onosproject.ui.impl.TrafficMonitor.Mode.IDLE;
+import static org.onosproject.ui.impl.TrafficMonitor.Mode.SELECTED_INTENT;
 import static org.onosproject.ui.topo.LinkHighlight.Flavor.PRIMARY_HIGHLIGHT;
 import static org.onosproject.ui.topo.LinkHighlight.Flavor.SECONDARY_HIGHLIGHT;
 
 /**
  * Encapsulates the behavior of monitoring specific traffic patterns.
  */
-public class TrafficMonitorObject {
+public class TrafficMonitor {
 
     // 4 Kilo Bytes as threshold
     private static final double BPS_THRESHOLD = 4 * TopoUtils.KILO;
 
     private static final Logger log =
-            LoggerFactory.getLogger(TrafficMonitorObject.class);
+            LoggerFactory.getLogger(TrafficMonitor.class);
 
     /**
      * Designates the different modes of operation.
@@ -84,13 +83,13 @@
         ALL_PORT_TRAFFIC,
         DEV_LINK_FLOWS,
         RELATED_INTENTS,
-        SEL_INTENT
+        SELECTED_INTENT
     }
 
     private final long trafficPeriod;
     private final ServicesBundle servicesBundle;
-    private final TopologyViewMessageHandler messageHandler;
-    private final TopologyViewIntentFilter intentFilter;
+    private final TopologyViewMessageHandler msgHandler;
+    private final TopoIntentFilter intentFilter;
 
     private final Timer timer = new Timer("topo-traffic");
 
@@ -105,21 +104,35 @@
      *
      * @param trafficPeriod   traffic task period in ms
      * @param servicesBundle  bundle of services
-     * @param messageHandler  our message handler
+     * @param msgHandler  our message handler
      */
-    public TrafficMonitorObject(long trafficPeriod,
-                                ServicesBundle servicesBundle,
-                                TopologyViewMessageHandler messageHandler) {
+    public TrafficMonitor(long trafficPeriod, ServicesBundle servicesBundle,
+                          TopologyViewMessageHandler msgHandler) {
         this.trafficPeriod = trafficPeriod;
         this.servicesBundle = servicesBundle;
-        this.messageHandler = messageHandler;
+        this.msgHandler = msgHandler;
 
-        intentFilter = new TopologyViewIntentFilter(servicesBundle);
+        intentFilter = new TopoIntentFilter(servicesBundle);
     }
 
     // =======================================================================
-    // === API === // TODO: add javadocs
+    // === API ===
 
+    /**
+     * Monitor for traffic data to be sent back to the web client, under
+     * the given mode. This causes a background traffic task to be
+     * scheduled to repeatedly compute and transmit the appropriate traffic
+     * data to the client.
+     * <p>
+     * The monitoring mode is expected to be one of:
+     * <ul>
+     *     <li>ALL_FLOW_TRAFFIC</li>
+     *     <li>ALL_PORT_TRAFFIC</li>
+     *     <li>SELECTED_INTENT</li>
+     * </ul>
+     *
+     * @param mode monitoring mode
+     */
     public synchronized void monitor(Mode mode) {
         log.debug("monitor: {}", mode);
         this.mode = mode;
@@ -137,7 +150,7 @@
                 sendAllPortTraffic();
                 break;
 
-            case SEL_INTENT:
+            case SELECTED_INTENT:
                 scheduleTask();
                 sendSelectedIntentTraffic();
                 break;
@@ -149,6 +162,22 @@
         }
     }
 
+    /**
+     * Monitor for traffic data to be sent back to the web client, under
+     * the given mode, using the given selection of devices and hosts.
+     * In the case of "device link flows", this causes a background traffic
+     * task to be scheduled to repeatedly compute and transmit the appropriate
+     * traffic data to the client. In the case of "related intents", no
+     * repeating task is scheduled.
+     * <p>
+     * The monitoring mode is expected to be one of:
+     * <ul>
+     *     <li>DEV_LINK_FLOWS</li>
+     *     <li>RELATED_INTENTS</li>
+     * </ul>
+     *
+     * @param mode monitoring mode
+     */
     public synchronized void monitor(Mode mode, NodeSelection nodeSelection) {
         log.debug("monitor: {} -- {}", mode, nodeSelection);
         this.mode = mode;
@@ -185,15 +214,27 @@
         }
     }
 
+    // TODO: move this out to the "h2h/multi-intent app"
+    /**
+     * Monitor for traffic data to be sent back to the web client, for the
+     * given intent.
+     *
+     * @param intent the intent to monitor
+     */
     public synchronized void monitor(Intent intent) {
         log.debug("monitor intent: {}", intent.id());
         selectedNodes = null;
         selectedIntents = new IntentSelection(intent);
-        mode = SEL_INTENT;
+        mode = SELECTED_INTENT;
         scheduleTask();
         sendSelectedIntentTraffic();
     }
 
+    /**
+     * Selects the next intent in the select group (if there is one),
+     * and sends highlighting data back to the web client to display
+     * which path is selected.
+     */
     public synchronized void selectNextIntent() {
         if (selectedIntents != null) {
             selectedIntents.next();
@@ -201,6 +242,11 @@
         }
     }
 
+    /**
+     * Selects the previous intent in the select group (if there is one),
+     * and sends highlighting data back to the web client to display
+     * which path is selected.
+     */
     public synchronized void selectPreviousIntent() {
         if (selectedIntents != null) {
             selectedIntents.prev();
@@ -208,14 +254,21 @@
         }
     }
 
+    /**
+     * Resends selected intent traffic data. This is called, for example,
+     * when the system detects an intent update happened.
+     */
     public synchronized void pokeIntent() {
-        if (mode == SEL_INTENT) {
+        if (mode == SELECTED_INTENT) {
             sendSelectedIntentTraffic();
         }
     }
 
-    public synchronized void stop() {
-        log.debug("STOP");
+    /**
+     * Stop all traffic monitoring.
+     */
+    public synchronized void stopMonitoring() {
+        log.debug("STOP monitoring");
         if (mode != IDLE) {
             sendClearAll();
         }
@@ -244,7 +297,7 @@
     private synchronized void  scheduleTask() {
         if (trafficTask == null) {
             log.debug("Starting up background traffic task...");
-            trafficTask = new TrafficMonitor();
+            trafficTask = new TrafficUpdateTask();
             timer.schedule(trafficTask, trafficPeriod, trafficPeriod);
         } else {
             // TEMPORARY until we are sure this is working correctly
@@ -259,64 +312,56 @@
         }
     }
 
-    // ---
-
     private void sendAllFlowTraffic() {
         log.debug("sendAllFlowTraffic");
-        sendHighlights(trafficSummary(LinkStatsType.FLOW_STATS));
+        msgHandler.sendHighlights(trafficSummary(TrafficLink.StatsType.FLOW_STATS));
     }
 
     private void sendAllPortTraffic() {
         log.debug("sendAllPortTraffic");
-        sendHighlights(trafficSummary(LinkStatsType.PORT_STATS));
+        msgHandler.sendHighlights(trafficSummary(TrafficLink.StatsType.PORT_STATS));
     }
 
     private void sendDeviceLinkFlows() {
         log.debug("sendDeviceLinkFlows: {}", selectedNodes);
-        sendHighlights(deviceLinkFlows());
+        msgHandler.sendHighlights(deviceLinkFlows());
     }
 
     private void sendSelectedIntents() {
         log.debug("sendSelectedIntents: {}", selectedIntents);
-        sendHighlights(intentGroup());
+        msgHandler.sendHighlights(intentGroup());
     }
 
     private void sendSelectedIntentTraffic() {
         log.debug("sendSelectedIntentTraffic: {}", selectedIntents);
-        sendHighlights(intentTraffic());
+        msgHandler.sendHighlights(intentTraffic());
     }
 
     private void sendClearHighlights() {
         log.debug("sendClearHighlights");
-        sendHighlights(new Highlights());
+        msgHandler.sendHighlights(new Highlights());
     }
 
-    private void sendHighlights(Highlights highlights) {
-        messageHandler.sendHighlights(highlights);
-    }
-
-
     // =======================================================================
     // === Generate messages in JSON object node format
 
-    private Highlights trafficSummary(LinkStatsType type) {
+    private Highlights trafficSummary(TrafficLink.StatsType type) {
         Highlights highlights = new Highlights();
 
-        // compile a set of bilinks (combining pairs of unidirectional links)
-        Map<LinkKey, BiLink> linkMap = new HashMap<>();
+        TrafficLinkMap linkMap = new TrafficLinkMap();
         compileLinks(linkMap);
         addEdgeLinks(linkMap);
 
-        for (BiLink blink : linkMap.values()) {
-            if (type == LinkStatsType.FLOW_STATS) {
-                attachFlowLoad(blink);
-            } else if (type == LinkStatsType.PORT_STATS) {
-                attachPortLoad(blink);
+        for (TrafficLink tlink : linkMap.biLinks()) {
+            if (type == TrafficLink.StatsType.FLOW_STATS) {
+                attachFlowLoad(tlink);
+            } else if (type == TrafficLink.StatsType.PORT_STATS) {
+                attachPortLoad(tlink);
             }
 
             // we only want to report on links deemed to have traffic
-            if (blink.hasTraffic()) {
-                highlights.add(blink.generateHighlight(type));
+            if (tlink.hasTraffic()) {
+                highlights.add(tlink.highlight(type));
             }
         }
         return highlights;
@@ -328,19 +373,19 @@
 
         if (selectedNodes != null && !selectedNodes.devices().isEmpty()) {
             // capture flow counts on bilinks
-            Map<LinkKey, BiLink> linkMap = new HashMap<>();
+            TrafficLinkMap linkMap = new TrafficLinkMap();
 
             for (Device device : selectedNodes.devices()) {
                 Map<Link, Integer> counts = getLinkFlowCounts(device.id());
                 for (Link link : counts.keySet()) {
-                    BiLink blink = TopoUtils.addLink(linkMap, link);
-                    blink.addFlows(counts.get(link));
+                    TrafficLink tlink = linkMap.add(link);
+                    tlink.addFlows(counts.get(link));
                 }
             }
 
             // now report on our collated links
-            for (BiLink blink : linkMap.values()) {
-                highlights.add(blink.generateHighlight(LinkStatsType.FLOW_COUNT));
+            for (TrafficLink tlink : linkMap.biLinks()) {
+                highlights.add(tlink.highlight(TrafficLink.StatsType.FLOW_COUNT));
             }
 
         }
@@ -398,18 +443,16 @@
         return highlights;
     }
 
-
     // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
-    private void compileLinks(Map<LinkKey, BiLink> linkMap) {
-        servicesBundle.linkService().getLinks()
-                .forEach(link -> TopoUtils.addLink(linkMap, link));
+    private void compileLinks(TrafficLinkMap linkMap) {
+        servicesBundle.linkService().getLinks().forEach(linkMap::add);
     }
 
-    private void addEdgeLinks(Map<LinkKey, BiLink> biLinks) {
+    private void addEdgeLinks(TrafficLinkMap linkMap) {
         servicesBundle.hostService().getHosts().forEach(host -> {
-            TopoUtils.addLink(biLinks, createEdgeLink(host, true));
-            TopoUtils.addLink(biLinks, createEdgeLink(host, false));
+            linkMap.add(createEdgeLink(host, true));
+            linkMap.add(createEdgeLink(host, false));
         });
     }
 
@@ -420,12 +463,12 @@
         return null;
     }
 
-    private void attachFlowLoad(BiLink link) {
+    private void attachFlowLoad(TrafficLink link) {
         link.addLoad(getLinkFlowLoad(link.one()));
         link.addLoad(getLinkFlowLoad(link.two()));
     }
 
-    private void attachPortLoad(BiLink link) {
+    private void attachPortLoad(TrafficLink link) {
         // For bi-directional traffic links, use
         // the max link rate of either direction
         // (we choose 'one' since we know that is never null)
@@ -433,7 +476,7 @@
         Load egressSrc = servicesBundle.portStatsService().load(one.src());
         Load egressDst = servicesBundle.portStatsService().load(one.dst());
         link.addLoad(maxLoad(egressSrc, egressDst), BPS_THRESHOLD);
-//        link.addLoad(maxLoad(egressSrc, egressDst), 10);    // FIXME - debug only
+//        link.addLoad(maxLoad(egressSrc, egressDst), 10);    // DEBUG ONLY!!
     }
 
     private Load maxLoad(Load a, Load b) {
@@ -446,18 +489,18 @@
         return a.rate() > b.rate() ? a : b;
     }
 
-    // ---
-
     // Counts all flow entries that egress on the links of the given device.
     private Map<Link, Integer> getLinkFlowCounts(DeviceId deviceId) {
         // get the flows for the device
         List<FlowEntry> entries = new ArrayList<>();
-        for (FlowEntry flowEntry : servicesBundle.flowService().getFlowEntries(deviceId)) {
+        for (FlowEntry flowEntry : servicesBundle.flowService()
+                                            .getFlowEntries(deviceId)) {
             entries.add(flowEntry);
         }
 
         // get egress links from device, and include edge links
-        Set<Link> links = new HashSet<>(servicesBundle.linkService().getDeviceEgressLinks(deviceId));
+        Set<Link> links = new HashSet<>(servicesBundle.linkService()
+                                            .getDeviceEgressLinks(deviceId));
         Set<Host> hosts = servicesBundle.hostService().getConnectedHosts(deviceId);
         if (hosts != null) {
             for (Host host : hosts) {
@@ -489,22 +532,20 @@
         return count;
     }
 
-    // ---
     private void highlightIntents(Highlights highlights,
                                   TrafficClass... trafficClasses) {
-        Map<LinkKey, BiLink> linkMap = new HashMap<>();
-
+        TrafficLinkMap linkMap = new TrafficLinkMap();
 
         for (TrafficClass trafficClass : trafficClasses) {
             classifyLinkTraffic(linkMap, trafficClass);
         }
 
-        for (BiLink blink : linkMap.values()) {
-            highlights.add(blink.generateHighlight(LinkStatsType.TAGGED));
+        for (TrafficLink tlink : linkMap.biLinks()) {
+            highlights.add(tlink.highlight(TrafficLink.StatsType.TAGGED));
         }
     }
 
-    private void classifyLinkTraffic(Map<LinkKey, BiLink> linkMap,
+    private void classifyLinkTraffic(TrafficLinkMap linkMap,
                                      TrafficClass trafficClass) {
         for (Intent intent : trafficClass.intents()) {
             boolean isOptical = intent instanceof OpticalConnectivityIntent;
@@ -532,17 +573,17 @@
     }
 
     private void classifyLinks(TrafficClass trafficClass, boolean isOptical,
-                               Map<LinkKey, BiLink> linkMap,
+                               TrafficLinkMap linkMap,
                                Iterable<Link> links) {
         if (links != null) {
             for (Link link : links) {
-                BiLink blink = TopoUtils.addLink(linkMap, link);
+                TrafficLink tlink = linkMap.add(link);
                 if (trafficClass.showTraffic()) {
-                    blink.addLoad(getLinkFlowLoad(link));
-                    blink.setAntMarch(true);
+                    tlink.addLoad(getLinkFlowLoad(link));
+                    tlink.antMarch(true);
                 }
-                blink.setOptical(isOptical);
-                blink.tagFlavor(trafficClass.flavor());
+                tlink.optical(isOptical);
+                tlink.tagFlavor(trafficClass.flavor());
             }
         }
     }
@@ -559,7 +600,7 @@
     // === Background Task
 
     // Provides periodic update of traffic information to the client
-    private class TrafficMonitor extends TimerTask {
+    private class TrafficUpdateTask extends TimerTask {
         @Override
         public void run() {
             try {
@@ -573,7 +614,7 @@
                     case DEV_LINK_FLOWS:
                         sendDeviceLinkFlows();
                         break;
-                    case SEL_INTENT:
+                    case SELECTED_INTENT:
                         sendSelectedIntentTraffic();
                         break;
 
@@ -590,5 +631,4 @@
             }
         }
     }
-
 }
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/topo/BaseLink.java b/web/gui/src/main/java/org/onosproject/ui/impl/topo/BaseLink.java
new file mode 100644
index 0000000..043b471
--- /dev/null
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/topo/BaseLink.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2015 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.onosproject.ui.impl.topo;
+
+import org.onosproject.net.Link;
+import org.onosproject.net.LinkKey;
+import org.onosproject.ui.topo.LinkHighlight;
+
+/**
+ * A simple concrete implementation of a {@link BiLink}.
+ * Note that this implementation does not generate any link highlights.
+ */
+public class BaseLink extends BiLink {
+
+    /**
+     * Constructs a base link for the given key and initial link.
+     *
+     * @param key  canonical key for this base link
+     * @param link first link
+     */
+    public BaseLink(LinkKey key, Link link) {
+        super(key, link);
+    }
+
+    @Override
+    public LinkHighlight highlight(Enum<?> type) {
+        return null;
+    }
+}
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/topo/LinkStatsType.java b/web/gui/src/main/java/org/onosproject/ui/impl/topo/BaseLinkMap.java
similarity index 66%
copy from web/gui/src/main/java/org/onosproject/ui/impl/topo/LinkStatsType.java
copy to web/gui/src/main/java/org/onosproject/ui/impl/topo/BaseLinkMap.java
index 589cddd..14c66ea 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/topo/LinkStatsType.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/topo/BaseLinkMap.java
@@ -17,27 +17,15 @@
 
 package org.onosproject.ui.impl.topo;
 
+import org.onosproject.net.Link;
+import org.onosproject.net.LinkKey;
+
 /**
- * Designates type of stats to report on a highlighted link.
+ * Collection of {@link BaseLink}s.
  */
-public enum LinkStatsType {
-    /**
-     * Number of flows.
-     */
-    FLOW_COUNT,
-
-    /**
-     * Number of bytes.
-     */
-    FLOW_STATS,
-
-    /**
-     * Number of bits per second.
-     */
-    PORT_STATS,
-
-    /**
-     * Custom tagged information.
-     */
-    TAGGED
+public class BaseLinkMap extends BiLinkMap<BaseLink> {
+    @Override
+    public BaseLink create(LinkKey key, Link link) {
+        return new BaseLink(key, link);
+    }
 }
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/topo/BiLink.java b/web/gui/src/main/java/org/onosproject/ui/impl/topo/BiLink.java
index 5ab4e0e..8ccf543 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/topo/BiLink.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/topo/BiLink.java
@@ -19,226 +19,87 @@
 
 import org.onosproject.net.Link;
 import org.onosproject.net.LinkKey;
-import org.onosproject.net.statistic.Load;
 import org.onosproject.ui.topo.LinkHighlight;
 
-import static org.onosproject.ui.topo.LinkHighlight.Flavor.NO_HIGHLIGHT;
-import static org.onosproject.ui.topo.LinkHighlight.Flavor.PRIMARY_HIGHLIGHT;
-import static org.onosproject.ui.topo.LinkHighlight.Flavor.SECONDARY_HIGHLIGHT;
+import static com.google.common.base.Preconditions.checkNotNull;
 
 /**
- * Representation of a link and its inverse, and any associated traffic data.
- * This class understands how to generate {@link LinkHighlight}s for sending
- * back to the topology view.
+ * Representation of a link and its inverse, as a partial implementation.
+ * <p>
+ * Subclasses will decide how to generate the link highlighting (coloring
+ * and labeling) for the topology view.
  */
-public class BiLink {
-
-    private static final String EMPTY = "";
+public abstract class BiLink {
 
     private final LinkKey key;
     private final Link one;
     private Link two;
 
-    private boolean hasTraffic = false;
-    private long bytes = 0;
-    private long rate = 0;
-    private long flows = 0;
-    private boolean isOptical = false;
-    private LinkHighlight.Flavor taggedFlavor = NO_HIGHLIGHT;
-    private boolean antMarch = false;
-
     /**
-     * Constructs a bilink for the given key and initial link.
+     * Constructs a bi-link for the given key and initial link. It is expected
+     * that the caller will have used {@link TopoUtils#canonicalLinkKey(Link)}
+     * to generate the key.
      *
-     * @param key canonical key for this bilink
+     * @param key canonical key for this bi-link
      * @param link first link
      */
     public BiLink(LinkKey key, Link link) {
-        this.key = key;
-        this.one = link;
+        this.key = checkNotNull(key);
+        this.one = checkNotNull(link);
     }
 
     /**
-     * Sets the second link for this bilink.
+     * Sets the second link for this bi-link.
      *
      * @param link second link
      */
     public void setOther(Link link) {
-        this.two = link;
+        this.two = checkNotNull(link);
     }
 
     /**
-     * Sets the optical flag to the given value.
+     * Returns the link identifier in the form expected on the Topology View
+     * in the web client.
      *
-     * @param b true if an optical link
+     * @return link identifier
      */
-    public void setOptical(boolean b) {
-        isOptical = b;
-    }
-
-    /**
-     * Sets the ant march flag to the given value.
-     *
-     * @param b true if marching ants required
-     */
-    public void setAntMarch(boolean b) {
-        antMarch = b;
-    }
-
-    /**
-     * Tags this bilink with a link flavor to be used in visual rendering.
-     *
-     * @param flavor the flavor to tag
-     */
-    public void tagFlavor(LinkHighlight.Flavor flavor) {
-        this.taggedFlavor = flavor;
-    }
-
-    /**
-     * Adds load statistics, marks the bilink as having traffic.
-     *
-     * @param load load to add
-     */
-    public void addLoad(Load load) {
-        addLoad(load, 0);
-    }
-
-    /**
-     * Adds load statistics, marks the bilink as having traffic, if the
-     * load rate is greater than the given threshold.
-     *
-     * @param load load to add
-     * @param threshold threshold to register traffic
-     */
-    public void addLoad(Load load, double threshold) {
-        if (load != null) {
-            this.hasTraffic = hasTraffic || load.rate() > threshold;
-            this.bytes += load.latest();
-            this.rate += load.rate();
-        }
-    }
-
-    /**
-     * Adds the given count of flows to this bilink.
-     *
-     * @param count count of flows
-     */
-    public void addFlows(int count) {
-        this.flows += count;
-    }
-
-    /**
-     * Generates a link highlight entity, based on state of this bilink.
-     *
-     * @param type the type of statistics to use to interpret the data
-     * @return link highlight data for this bilink
-     */
-    public LinkHighlight generateHighlight(LinkStatsType type) {
-        switch (type) {
-            case FLOW_COUNT:
-                return highlightForFlowCount(type);
-
-            case FLOW_STATS:
-            case PORT_STATS:
-                return highlightForStats(type);
-
-            case TAGGED:
-                return highlightForTagging(type);
-
-            default:
-                throw new IllegalStateException("unexpected case: " + type);
-        }
-    }
-
-    private LinkHighlight highlightForStats(LinkStatsType type) {
-        return new LinkHighlight(linkId(), SECONDARY_HIGHLIGHT)
-                .setLabel(generateLabel(type));
-    }
-
-    private LinkHighlight highlightForFlowCount(LinkStatsType type) {
-        LinkHighlight.Flavor flavor = flows() > 0 ?
-                PRIMARY_HIGHLIGHT : SECONDARY_HIGHLIGHT;
-        return new LinkHighlight(linkId(), flavor)
-                .setLabel(generateLabel(type));
-    }
-
-    private LinkHighlight highlightForTagging(LinkStatsType type) {
-        LinkHighlight hlite = new LinkHighlight(linkId(), flavor())
-                .setLabel(generateLabel(type));
-        if (isOptical()) {
-            hlite.addMod(LinkHighlight.MOD_OPTICAL);
-        }
-        if (isAntMarch()) {
-            hlite.addMod(LinkHighlight.MOD_ANIMATED);
-        }
-        return hlite;
-    }
-
-    // Generates a link identifier in the form that the Topology View on the
-    private String linkId() {
+    public String linkId() {
         return TopoUtils.compactLinkString(one);
     }
 
-    // Generates a string representation of the load, to be used as a label
-    private String generateLabel(LinkStatsType type) {
-        switch (type) {
-            case FLOW_COUNT:
-                return TopoUtils.formatFlows(flows());
-
-            case FLOW_STATS:
-                return TopoUtils.formatBytes(bytes());
-
-            case PORT_STATS:
-                return TopoUtils.formatBitRate(rate());
-
-            case TAGGED:
-                return hasTraffic() ? TopoUtils.formatBytes(bytes()) : EMPTY;
-
-            default:
-                return "?";
-        }
-    }
-
-    // === ----------------------------------------------------------------
-    // accessors
-
+    /**
+     * Returns the key for this bi-link.
+     *
+     * @return the key
+     */
     public LinkKey key() {
         return key;
     }
 
+    /**
+     * Returns the first link in this bi-link.
+     *
+     * @return the first link
+     */
     public Link one() {
         return one;
     }
 
+    /**
+     * Returns the second link in this bi-link.
+     *
+     * @return the second link
+     */
     public Link two() {
         return two;
     }
 
-    public boolean hasTraffic() {
-        return hasTraffic;
-    }
-
-    public boolean isOptical() {
-        return isOptical;
-    }
-
-    public boolean isAntMarch() {
-        return antMarch;
-    }
-
-    public LinkHighlight.Flavor flavor() {
-        return taggedFlavor;
-    }
-
-    public long bytes() {
-        return bytes;
-    }
-
-    public long rate() {
-        return rate;
-    }
-
-    public long flows() {
-        return flows;
-    }
+    /**
+     * Returns the link highlighting to use, based on this bi-link's current
+     * state.
+     *
+     * @param type optional highlighting type parameter
+     * @return link highlighting model
+     */
+    public abstract LinkHighlight highlight(Enum<?> type);
 }
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/topo/BiLinkMap.java b/web/gui/src/main/java/org/onosproject/ui/impl/topo/BiLinkMap.java
new file mode 100644
index 0000000..18565d7
--- /dev/null
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/topo/BiLinkMap.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2015 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.onosproject.ui.impl.topo;
+
+import org.onosproject.net.Link;
+import org.onosproject.net.LinkKey;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Represents a collection of {@link BiLink} concrete classes. These maps
+ * are used to collate a set of unidirectional {@link Link}s into a smaller
+ * set of bi-directional {@link BiLink} derivatives.
+ * <p>
+ * @param <B> the type of bi-link subclass
+ */
+public abstract class BiLinkMap<B extends BiLink> {
+
+    private final Map<LinkKey, B> map = new HashMap<>();
+
+    /**
+     * Creates a new instance of a bi-link. Concrete subclasses should
+     * instantiate and return the appropriate bi-link subclass.
+     *
+     * @param key the link key
+     * @param link the initial link
+     * @return a new instance
+     */
+    public abstract B create(LinkKey key, Link link);
+
+    /**
+     * Adds the given link to our collection, returning the corresponding
+     * bi-link (creating one if needed necessary).
+     *
+     * @param link the link to add to the collection
+     * @return the corresponding bi-link wrapper
+     */
+    public B add(Link link) {
+        LinkKey key = TopoUtils.canonicalLinkKey(checkNotNull(link));
+        B blink = map.get(key);
+        if (blink == null) {
+            // no bi-link yet exists for this link
+            blink = create(key, link);
+            map.put(key, blink);
+        } else {
+            // we have a bi-link for this link.
+            if (!blink.one().equals(link)) {
+                blink.setOther(link);
+            }
+        }
+        return blink;
+    }
+
+    /**
+     * Returns the bi-link instances in the collection.
+     *
+     * @return the bi-links in this map
+     */
+    public Collection<B> biLinks() {
+        return map.values();
+    }
+
+    /**
+     * Returns the number of bi-links in the collection.
+     *
+     * @return number of bi-links
+     */
+    public int size() {
+        return map.size();
+    }
+}
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/topo/IntentSelection.java b/web/gui/src/main/java/org/onosproject/ui/impl/topo/IntentSelection.java
index eb959c5..ae7eab4 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/topo/IntentSelection.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/topo/IntentSelection.java
@@ -47,7 +47,7 @@
      * @param nodes node selection
      * @param filter intent filter
      */
-    public IntentSelection(NodeSelection nodes, TopologyViewIntentFilter filter) {
+    public IntentSelection(NodeSelection nodes, TopoIntentFilter filter) {
         this.nodes = nodes;
         intents = filter.findPathIntents(nodes.hosts(), nodes.devices());
     }
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/topo/NodeSelection.java b/web/gui/src/main/java/org/onosproject/ui/impl/topo/NodeSelection.java
index fa776b3..c0597aa 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/topo/NodeSelection.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/topo/NodeSelection.java
@@ -41,7 +41,7 @@
  */
 public class NodeSelection {
 
-    protected static final Logger log =
+    private static final Logger log =
             LoggerFactory.getLogger(NodeSelection.class);
 
     private static final String IDS = "ids";
@@ -183,5 +183,4 @@
         }
         return unmatched;
     }
-
 }
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/topo/ServicesBundle.java b/web/gui/src/main/java/org/onosproject/ui/impl/topo/ServicesBundle.java
index 4282cdc..bcc4ad8 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/topo/ServicesBundle.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/topo/ServicesBundle.java
@@ -42,6 +42,7 @@
 
     /**
      * Creates the services bundle.
+     *
      * @param intentService     intent service reference
      * @param deviceService     device service reference
      * @param hostService       host service reference
@@ -66,30 +67,65 @@
         this.portStatsService = checkNotNull(portStatsService);
     }
 
+    /**
+     * Returns a reference to the intent service.
+     *
+     * @return intent service reference
+     */
     public IntentService intentService() {
         return intentService;
     }
 
+    /**
+     * Returns a reference to the device service.
+     *
+     * @return device service reference
+     */
     public DeviceService deviceService() {
         return deviceService;
     }
 
+    /**
+     * Returns a reference to the host service.
+     *
+     * @return host service reference
+     */
     public HostService hostService() {
         return hostService;
     }
 
+    /**
+     * Returns a reference to the link service.
+     *
+     * @return link service reference
+     */
     public LinkService linkService() {
         return linkService;
     }
 
+    /**
+     * Returns a reference to the flow rule service.
+     *
+     * @return flow service reference
+     */
     public FlowRuleService flowService() {
         return flowService;
     }
 
+    /**
+     * Returns a reference to the flow statistics service.
+     *
+     * @return flow statistics service reference
+     */
     public StatisticService flowStatsService() {
         return flowStatsService;
     }
 
+    /**
+     * Returns a reference to the port statistics service.
+     *
+     * @return port statistics service reference
+     */
     public PortStatisticsService portStatsService() {
         return portStatsService;
     }
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/topo/TopologyViewIntentFilter.java b/web/gui/src/main/java/org/onosproject/ui/impl/topo/TopoIntentFilter.java
similarity index 96%
rename from web/gui/src/main/java/org/onosproject/ui/impl/topo/TopologyViewIntentFilter.java
rename to web/gui/src/main/java/org/onosproject/ui/impl/topo/TopoIntentFilter.java
index 1bd2b58..8372ded 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/topo/TopologyViewIntentFilter.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/topo/TopoIntentFilter.java
@@ -48,7 +48,7 @@
  * Auxiliary facility to query the intent service based on the specified
  * set of end-station hosts, edge points or infrastructure devices.
  */
-public class TopologyViewIntentFilter {
+public class TopoIntentFilter {
 
     private final IntentService intentService;
     private final DeviceService deviceService;
@@ -60,19 +60,13 @@
      *
      * @param services service references bundle
      */
-    public TopologyViewIntentFilter(ServicesBundle services) {
+    public TopoIntentFilter(ServicesBundle services) {
         this.intentService = services.intentService();
         this.deviceService = services.deviceService();
         this.hostService = services.hostService();
         this.linkService = services.linkService();
     }
 
-
-    // TODO: Review - do we need this signature, with sourceIntents??
-//    public List<Intent> findPathIntents(Set<Host> hosts, Set<Device> devices,
-//                                        Iterable<Intent> sourceIntents) {
-//    }
-
     /**
      * Finds all path (host-to-host or point-to-point) intents that pertain
      * to the given hosts and devices.
@@ -277,5 +271,4 @@
         }
         return null;
     }
-
 }
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/topo/TopoUtils.java b/web/gui/src/main/java/org/onosproject/ui/impl/topo/TopoUtils.java
index 8d6b319..d43b376 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/topo/TopoUtils.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/topo/TopoUtils.java
@@ -21,15 +21,16 @@
 import org.onosproject.net.LinkKey;
 
 import java.text.DecimalFormat;
-import java.util.Map;
 
 import static org.onosproject.net.LinkKey.linkKey;
 
 /**
- * Utility methods for helping out with the topology view.
+ * Utility methods for helping out with formatting data for the Topology View
+ * in the web client.
  */
 public final class TopoUtils {
 
+    // explicit decision made to not 'javadoc' these self explanatory constants
     public static final double KILO = 1024;
     public static final double MEGA = 1024 * KILO;
     public static final double GIGA = 1024 * MEGA;
@@ -155,30 +156,4 @@
         }
         return String.valueOf(flows) + SPACE + (flows > 1 ? FLOWS : FLOW);
     }
-
-
-    /**
-     * Creates a new biLink with the supplied link (and adds it to the map),
-     * or attaches the link to an existing biLink (which already has the
-     * peer link).
-     *
-     * @param linkMap map of biLinks
-     * @param link the link to add
-     * @return the biLink to which the link was added
-     */
-    // creates a new biLink with supplied link, or attaches link to the
-    //  existing biLink (which already has its peer link)
-    public static BiLink addLink(Map<LinkKey, BiLink> linkMap, Link link) {
-        LinkKey key = TopoUtils.canonicalLinkKey(link);
-        BiLink biLink = linkMap.get(key);
-        if (biLink != null) {
-            biLink.setOther(link);
-        } else {
-            biLink = new BiLink(key, link);
-            linkMap.put(key, biLink);
-        }
-        return biLink;
-    }
-
-
 }
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/topo/TrafficClass.java b/web/gui/src/main/java/org/onosproject/ui/impl/topo/TrafficClass.java
index 1389aba..0fa1581 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/topo/TrafficClass.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/topo/TrafficClass.java
@@ -24,6 +24,7 @@
  * Auxiliary data carrier for assigning a highlight class to a set of
  * intents, for visualization in the topology view.
  */
+@Deprecated
 public class TrafficClass {
 
     private final LinkHighlight.Flavor flavor;
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/topo/TrafficLink.java b/web/gui/src/main/java/org/onosproject/ui/impl/topo/TrafficLink.java
new file mode 100644
index 0000000..e83f9fc
--- /dev/null
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/topo/TrafficLink.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright 2015 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.onosproject.ui.impl.topo;
+
+import org.onosproject.net.Link;
+import org.onosproject.net.LinkKey;
+import org.onosproject.net.statistic.Load;
+import org.onosproject.ui.topo.LinkHighlight;
+import org.onosproject.ui.topo.LinkHighlight.Flavor;
+
+import static org.onosproject.ui.topo.LinkHighlight.Flavor.NO_HIGHLIGHT;
+import static org.onosproject.ui.topo.LinkHighlight.Flavor.PRIMARY_HIGHLIGHT;
+import static org.onosproject.ui.topo.LinkHighlight.Flavor.SECONDARY_HIGHLIGHT;
+
+/**
+ * Representation of a link and its inverse, and associated traffic data.
+ * This class understands how to generate the appropriate
+ * {@link LinkHighlight}s for showing traffic data on the topology view.
+ */
+public class TrafficLink extends BiLink {
+
+    private static final String EMPTY = "";
+    private static final String QUE = "?";
+
+    private long bytes = 0;
+    private long rate = 0;
+    private long flows = 0;
+    private Flavor taggedFlavor = NO_HIGHLIGHT;
+    private boolean hasTraffic = false;
+    private boolean isOptical = false;
+    private boolean antMarch = false;
+
+    /**
+     * Constructs a traffic link for the given key and initial link.
+     *
+     * @param key  canonical key for this traffic link
+     * @param link first link
+     */
+    public TrafficLink(LinkKey key, Link link) {
+        super(key, link);
+    }
+
+    /**
+     * Sets the optical flag to the given value.
+     *
+     * @param b true if an optical link
+     * @return self, for chaining
+     */
+    public TrafficLink optical(boolean b) {
+        isOptical = b;
+        return this;
+    }
+
+    /**
+     * Sets the ant march flag to the given value.
+     *
+     * @param b true if marching ants required
+     * @return self, for chaining
+     */
+    public TrafficLink antMarch(boolean b) {
+        antMarch = b;
+        return this;
+    }
+
+    /**
+     * Tags this traffic link with the flavor to be used in visual rendering.
+     *
+     * @param flavor the flavor to tag
+     * @return self, for chaining
+     */
+    public TrafficLink tagFlavor(Flavor flavor) {
+        this.taggedFlavor = flavor;
+        return this;
+    }
+
+    /**
+     * Adds load statistics, marks the traffic link as having traffic.
+     *
+     * @param load load to add
+     */
+    public void addLoad(Load load) {
+        addLoad(load, 0);
+    }
+
+    /**
+     * Adds load statistics, marks the traffic link as having traffic, if the
+     * load {@link Load#rate rate} is greater than the given threshold
+     * (expressed in bytes per second).
+     *
+     * @param load load to add
+     * @param threshold threshold to register traffic
+     */
+    public void addLoad(Load load, double threshold) {
+        if (load != null) {
+            this.hasTraffic = hasTraffic || load.rate() > threshold;
+            this.bytes += load.latest();
+            this.rate += load.rate();
+        }
+    }
+
+    /**
+     * Adds the given count of flows to this traffic link.
+     *
+     * @param count count of flows
+     */
+    public void addFlows(int count) {
+        this.flows += count;
+    }
+
+    @Override
+    public LinkHighlight highlight(Enum<?> type) {
+        StatsType statsType = (StatsType) type;
+        switch (statsType) {
+            case FLOW_COUNT:
+                return highlightForFlowCount(statsType);
+
+            case FLOW_STATS:
+            case PORT_STATS:
+                return highlightForStats(statsType);
+
+            case TAGGED:
+                return highlightForTagging(statsType);
+
+            default:
+                throw new IllegalStateException("unexpected case: " + statsType);
+        }
+    }
+
+    private LinkHighlight highlightForStats(StatsType type) {
+        return new LinkHighlight(linkId(), SECONDARY_HIGHLIGHT)
+                .setLabel(generateLabel(type));
+    }
+
+    private LinkHighlight highlightForFlowCount(StatsType type) {
+        Flavor flavor = flows > 0 ? PRIMARY_HIGHLIGHT : SECONDARY_HIGHLIGHT;
+        return new LinkHighlight(linkId(), flavor)
+                .setLabel(generateLabel(type));
+    }
+
+    private LinkHighlight highlightForTagging(StatsType type) {
+        LinkHighlight hlite = new LinkHighlight(linkId(), taggedFlavor)
+                .setLabel(generateLabel(type));
+        if (isOptical) {
+            hlite.addMod(LinkHighlight.MOD_OPTICAL);
+        }
+        if (antMarch) {
+            hlite.addMod(LinkHighlight.MOD_ANIMATED);
+        }
+        return hlite;
+    }
+
+    // Generates a string representation of the load, to be used as a label
+    private String generateLabel(StatsType type) {
+        switch (type) {
+            case FLOW_COUNT:
+                return TopoUtils.formatFlows(flows);
+
+            case FLOW_STATS:
+                return TopoUtils.formatBytes(bytes);
+
+            case PORT_STATS:
+                return TopoUtils.formatBitRate(rate);
+
+            case TAGGED:
+                return hasTraffic ? TopoUtils.formatBytes(bytes) : EMPTY;
+
+            default:
+                return QUE;
+        }
+    }
+
+    /**
+     * Returns true if this link has been deemed to have enough traffic
+     * to register on the topology view in the web UI.
+     *
+     * @return true if this link has displayable traffic
+     */
+    public boolean hasTraffic() {
+        return hasTraffic;
+    }
+
+    /**
+     * Designates type of traffic statistics to report on a highlighted link.
+     */
+    public enum StatsType {
+        /**
+         * Number of flows.
+         */
+        FLOW_COUNT,
+
+        /**
+         * Number of bytes.
+         */
+        FLOW_STATS,
+
+        /**
+         * Number of bits per second.
+         */
+        PORT_STATS,
+
+        /**
+         * Custom tagged information.
+         */
+        TAGGED
+    }
+}
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/topo/LinkStatsType.java b/web/gui/src/main/java/org/onosproject/ui/impl/topo/TrafficLinkMap.java
similarity index 66%
rename from web/gui/src/main/java/org/onosproject/ui/impl/topo/LinkStatsType.java
rename to web/gui/src/main/java/org/onosproject/ui/impl/topo/TrafficLinkMap.java
index 589cddd..59965ad 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/topo/LinkStatsType.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/topo/TrafficLinkMap.java
@@ -17,27 +17,16 @@
 
 package org.onosproject.ui.impl.topo;
 
+import org.onosproject.net.Link;
+import org.onosproject.net.LinkKey;
+
 /**
- * Designates type of stats to report on a highlighted link.
+ * Collection of {@link TrafficLink}s.
  */
-public enum LinkStatsType {
-    /**
-     * Number of flows.
-     */
-    FLOW_COUNT,
+public class TrafficLinkMap extends BiLinkMap<TrafficLink> {
 
-    /**
-     * Number of bytes.
-     */
-    FLOW_STATS,
-
-    /**
-     * Number of bits per second.
-     */
-    PORT_STATS,
-
-    /**
-     * Custom tagged information.
-     */
-    TAGGED
+    @Override
+    public TrafficLink create(LinkKey key, Link link) {
+        return new TrafficLink(key, link);
+    }
 }