ONOS-2186 - GUI Topo Overlay - (WIP)
- Re-worked JSONification of LinkHighlights to simplify code.
- added a couple of static imports to clean up code.

Change-Id: Ia210c17dfb10972b52241b7a01c0906eef0a1f2a
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 1c629c7..1e8f403 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
@@ -53,12 +53,11 @@
 import org.onosproject.net.intent.MultiPointToSinglePointIntent;
 import org.onosproject.net.link.LinkEvent;
 import org.onosproject.net.link.LinkListener;
-import org.onosproject.ui.JsonUtils;
 import org.onosproject.ui.RequestHandler;
 import org.onosproject.ui.UiConnection;
 import org.onosproject.ui.impl.TrafficMonitor.Mode;
-import org.onosproject.ui.topo.NodeSelection;
 import org.onosproject.ui.topo.Highlights;
+import org.onosproject.ui.topo.NodeSelection;
 import org.onosproject.ui.topo.PropertyPanel;
 
 import java.util.ArrayList;
@@ -80,6 +79,8 @@
 import static org.onosproject.net.device.DeviceEvent.Type.*;
 import static org.onosproject.net.host.HostEvent.Type.HOST_ADDED;
 import static org.onosproject.net.link.LinkEvent.Type.LINK_ADDED;
+import static org.onosproject.ui.JsonUtils.envelope;
+import static org.onosproject.ui.impl.topo.TopoJson.json;
 
 /**
  * Web socket capable of interacting with the GUI topology view.
@@ -349,8 +350,7 @@
                 overlayCache.currentOverlay().modifyHostDetails(pp);
             }
 
-            ObjectNode json = JsonUtils.envelope(SHOW_DETAILS, sid, json(pp));
-            sendMessage(json);
+            sendMessage(envelope(SHOW_DETAILS, sid, json(pp)));
         }
     }
 
@@ -538,7 +538,7 @@
 
     // Converts highlights to JSON format and sends the message to the client
     protected void sendHighlights(Highlights highlights) {
-        sendMessage(JsonUtils.envelope(SHOW_HIGHLIGHTS, json(highlights)));
+        sendMessage(envelope(SHOW_HIGHLIGHTS, json(highlights)));
     }
 
     // Sends the specified data to the client.
@@ -553,8 +553,7 @@
     private synchronized void requestSummary(long sid) {
         PropertyPanel pp = summmaryMessage(sid);
         overlayCache.currentOverlay().modifySummary(pp);
-        ObjectNode json = JsonUtils.envelope(SHOW_SUMMARY, sid, json(pp));
-        sendMessage(json);
+        sendMessage(envelope(SHOW_SUMMARY, sid, json(pp)));
     }
 
 
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandlerBase.java b/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandlerBase.java
index 6f3577f..840e89f 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandlerBase.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandlerBase.java
@@ -63,11 +63,6 @@
 import org.onosproject.ui.UiConnection;
 import org.onosproject.ui.UiMessageHandler;
 import org.onosproject.ui.impl.topo.ServicesBundle;
-import org.onosproject.ui.topo.ButtonId;
-import org.onosproject.ui.topo.DeviceHighlight;
-import org.onosproject.ui.topo.Highlights;
-import org.onosproject.ui.topo.HostHighlight;
-import org.onosproject.ui.topo.LinkHighlight;
 import org.onosproject.ui.topo.PropertyPanel;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -96,9 +91,9 @@
 import static org.onosproject.net.host.HostEvent.Type.HOST_REMOVED;
 import static org.onosproject.net.link.LinkEvent.Type.LINK_ADDED;
 import static org.onosproject.net.link.LinkEvent.Type.LINK_REMOVED;
-import static org.onosproject.ui.topo.TopoUtils.compactLinkString;
 import static org.onosproject.ui.topo.TopoConstants.CoreButtons;
 import static org.onosproject.ui.topo.TopoConstants.Properties;
+import static org.onosproject.ui.topo.TopoUtils.compactLinkString;
 
 /**
  * Facility for creating messages bound for the topology viewer.
@@ -511,118 +506,4 @@
         return pp;
     }
 
-
-    // ----------------------------------------------------------------------
-
-    /**
-     * Transforms the given highlights model into a JSON message payload.
-     *
-     * @param highlights the model to transform
-     * @return JSON payload
-     */
-    protected ObjectNode json(Highlights highlights) {
-        ObjectNode payload = objectNode();
-
-        ArrayNode devices = arrayNode();
-        ArrayNode hosts = arrayNode();
-        ArrayNode links = arrayNode();
-
-        payload.set("devices", devices);
-        payload.set("hosts", hosts);
-        payload.set("links", links);
-
-        highlights.devices().forEach(dh -> devices.add(json(dh)));
-        highlights.hosts().forEach(hh -> hosts.add(json(hh)));
-        jsonifyLinks(links, highlights.links());
-
-        return payload;
-    }
-
-    private void jsonifyLinks(ArrayNode links, Set<LinkHighlight> hilites) {
-        // a little more complicated than devices or hosts, since we are
-        //  grouping the link highlights by CSS classes
-
-        // TODO: refactor this method (including client side) to use new format
-        //       as a more compact representation of the data...
-        // * links:
-        //   * "primary animated":
-        //     * "link01" -> "label"
-        //     * "link02" -> "label"
-        //   * "secondary":
-        //     * "link04" -> "label"
-        //     * "link05" -> ""
-
-
-        Map<String, List<String>> linkIdMap = new HashMap<>();
-        Map<String, List<String>> linkLabelMap = new HashMap<>();
-        List<String> ids;
-        List<String> labels;
-
-        for (LinkHighlight lh : hilites) {
-            String cls = lh.cssClasses();
-            ids = linkIdMap.get(cls);
-            labels = linkLabelMap.get(cls);
-
-            if (ids == null) {  // labels will be null also
-                ids = new ArrayList<>();
-                linkIdMap.put(cls, ids);
-                labels = new ArrayList<>();
-                linkLabelMap.put(cls, labels);
-            }
-
-            ids.add(lh.elementId());
-            labels.add(lh.label());
-        }
-
-        for (String cls : linkIdMap.keySet()) {
-            ObjectNode group = objectNode();
-            links.add(group);
-
-            group.put("class", cls);
-
-            ArrayNode lnks = arrayNode();
-            ArrayNode labs = arrayNode();
-            group.set("links", lnks);
-            group.set("labels", labs);
-
-            linkIdMap.get(cls).forEach(lnks::add);
-            linkLabelMap.get(cls).forEach(labs::add);
-        }
-    }
-
-
-    protected ObjectNode json(DeviceHighlight dh) {
-        // TODO: implement this once we know what a device highlight looks like
-        return objectNode();
-    }
-
-    protected ObjectNode json(HostHighlight hh) {
-        // TODO: implement this once we know what a host highlight looks like
-        return objectNode();
-    }
-
-    // translates the property panel into JSON, for returning to the client
-    protected ObjectNode json(PropertyPanel pp) {
-        ObjectNode result = objectNode()
-                .put("title", pp.title())
-                .put("type", pp.typeId())
-                .put("id", pp.id());
-
-        ObjectNode pnode = objectNode();
-        ArrayNode porder = arrayNode();
-        for (PropertyPanel.Prop p : pp.properties()) {
-            porder.add(p.key());
-            pnode.put(p.key(), p.value());
-        }
-        result.set("propOrder", porder);
-        result.set("props", pnode);
-
-        ArrayNode buttons = arrayNode();
-        for (ButtonId b : pp.buttons()) {
-            buttons.add(b.id());
-        }
-        result.set("buttons", buttons);
-        return result;
-    }
-
 }
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/topo/TopoJson.java b/web/gui/src/main/java/org/onosproject/ui/impl/topo/TopoJson.java
new file mode 100644
index 0000000..f382e22
--- /dev/null
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/topo/TopoJson.java
@@ -0,0 +1,133 @@
+/*
+ * 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 com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.ui.topo.ButtonId;
+import org.onosproject.ui.topo.DeviceHighlight;
+import org.onosproject.ui.topo.Highlights;
+import org.onosproject.ui.topo.HostHighlight;
+import org.onosproject.ui.topo.LinkHighlight;
+import org.onosproject.ui.topo.PropertyPanel;
+
+/**
+ * JSON utilities for the Topology View.
+ */
+public final class TopoJson {
+    private static final String DEVICES = "devices";
+    private static final String HOSTS = "hosts";
+    private static final String LINKS = "links";
+
+    private static final String ID = "id";
+    private static final String LABEL = "label";
+    private static final String CSS = "css";
+
+    private static final String TITLE = "title";
+    private static final String TYPE = "type";
+    private static final String PROP_ORDER = "propOrder";
+    private static final String PROPS = "props";
+    private static final String BUTTONS = "buttons";
+
+
+    private static final ObjectMapper MAPPER = new ObjectMapper();
+
+    private static ObjectNode objectNode() {
+        return MAPPER.createObjectNode();
+    }
+
+    private static ArrayNode arrayNode() {
+        return MAPPER.createArrayNode();
+    }
+
+    // non-instantiable
+    private TopoJson() { }
+
+    /**
+     * Transforms the given highlights model into a JSON message payload.
+     *
+     * @param highlights the model to transform
+     * @return JSON payload
+     */
+    public static ObjectNode json(Highlights highlights) {
+        ObjectNode payload = objectNode();
+
+        ArrayNode devices = arrayNode();
+        ArrayNode hosts = arrayNode();
+        ArrayNode links = arrayNode();
+
+        payload.set(DEVICES, devices);
+        payload.set(HOSTS, hosts);
+        payload.set(LINKS, links);
+
+        highlights.devices().forEach(dh -> devices.add(json(dh)));
+        highlights.hosts().forEach(hh -> hosts.add(json(hh)));
+        highlights.links().forEach(lh -> links.add(json(lh)));
+
+        return payload;
+    }
+
+    private static ObjectNode json(DeviceHighlight dh) {
+        // TODO: implement this once we know what a device highlight looks like
+        return objectNode();
+    }
+
+    private static ObjectNode json(HostHighlight hh) {
+        // TODO: implement this once we know what a host highlight looks like
+        return objectNode();
+    }
+
+    private static ObjectNode json(LinkHighlight lh) {
+        return objectNode()
+                .put(ID, lh.elementId())
+                .put(LABEL, lh.label())
+                .put(CSS, lh.cssClasses());
+    }
+
+    /**
+     * Translates the given property panel into JSON, for returning
+     * to the client.
+     *
+     * @param pp the property panel model
+     * @return JSON payload
+     */
+    public static ObjectNode json(PropertyPanel pp) {
+        ObjectNode result = objectNode()
+                .put(TITLE, pp.title())
+                .put(TYPE, pp.typeId())
+                .put(ID, pp.id());
+
+        ObjectNode pnode = objectNode();
+        ArrayNode porder = arrayNode();
+        for (PropertyPanel.Prop p : pp.properties()) {
+            porder.add(p.key());
+            pnode.put(p.key(), p.value());
+        }
+        result.set(PROP_ORDER, porder);
+        result.set(PROPS, pnode);
+
+        ArrayNode buttons = arrayNode();
+        for (ButtonId b : pp.buttons()) {
+            buttons.add(b.id());
+        }
+        result.set(BUTTONS, buttons);
+        return result;
+    }
+
+}
diff --git a/web/gui/src/main/webapp/app/view/topo/topoOverlay.js b/web/gui/src/main/webapp/app/view/topo/topoOverlay.js
index e7f10ef..85dc0ff 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoOverlay.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoOverlay.js
@@ -293,7 +293,6 @@
         tss = _tss_;
     }
 
-    // TODO: refactor this (currently using showTraffic data structure)
     function showHighlights(data) {
         /*
            API to topoForce
@@ -303,43 +302,39 @@
              findLinkById( id )
          */
 
-        var paths = data.links;
-
+        // TODO: clear node highlighting
         api.clearLinkTrafficStyle();
         api.removeLinkLabels();
 
-        // Now highlight all links in the paths payload, and attach
-        //  labels to them, if they are defined.
-        paths.forEach(function (p) {
-            var n = p.links.length,
-                i, ldata, lab, units, magnitude, portcls;
+        // TODO: device and host highlights
 
-            for (i=0; i<n; i++) {
-                ldata = api.findLinkById(p.links[i]);
-                lab = p.labels[i];
+        data.links.forEach(function (lnk) {
+            var ldata = api.findLinkById(lnk.id),
+                lab = lnk.label,
+                units, portcls, magnitude;
 
-                if (ldata && !ldata.el.empty()) {
-                    ldata.el.classed(p.class, true);
-                    ldata.label = lab;
+            if (ldata && !ldata.el.empty()) {
+                ldata.el.classed(lnk.css, true);
+                ldata.label = lab;
 
-                    if (fs.endsWith(lab, 'bps')) {
-                        // inject additional styling for port-based traffic
-                        units = lab.substring(lab.length-4);
-                        portcls = 'port-traffic-' + units;
+                // inject additional styling for port-based traffic
+                if (fs.endsWith(lab, 'bps')) {
+                    units = lab.substring(lab.length-4);
+                    portcls = 'port-traffic-' + units;
 
-                        // for GBps
-                        if (units.substring(0,1) === 'G') {
-                            magnitude = fs.parseBitRate(lab);
-                            if (magnitude >= 9) {
-                                portcls += '-choked'
-                            }
+                    // for GBps
+                    if (units.substring(0,1) === 'G') {
+                        magnitude = fs.parseBitRate(lab);
+                        if (magnitude >= 9) {
+                            portcls += '-choked'
                         }
-                        ldata.el.classed(portcls, true);
                     }
+                    ldata.el.classed(portcls, true);
                 }
             }
         });
 
+        // TODO: api.updateNodes()
         api.updateLinks();
     }
 
diff --git a/web/gui/src/main/webapp/app/view/topo/topoTraffic.js b/web/gui/src/main/webapp/app/view/topo/topoTraffic.js
index a2cd818..ca37936 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoTraffic.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoTraffic.js
@@ -211,6 +211,7 @@
                 // invoked from mouseover/mouseout and selection change
                 requestTrafficForMode: requestTrafficForMode,
 
+                // TODO: these should move to new UI demo app
                 // invoked from buttons on detail (multi-select) panel
                 addHostIntent: addHostIntent,
                 addMultiSourceIntent: addMultiSourceIntent