GUI Topo -- Cleaned up NodeBadge, now that we have a clearer idea of what we want to model.

Change-Id: I754a94cbd1fbe2a2c8affccaca88c48c3fa33b92
diff --git a/core/api/src/main/java/org/onosproject/ui/topo/NodeBadge.java b/core/api/src/main/java/org/onosproject/ui/topo/NodeBadge.java
index d19b9be..7b51711 100644
--- a/core/api/src/main/java/org/onosproject/ui/topo/NodeBadge.java
+++ b/core/api/src/main/java/org/onosproject/ui/topo/NodeBadge.java
@@ -23,18 +23,15 @@
 
     private static final String EMPTY = "";
 
-    /** Designates the type of badge. */
-    public enum Type {
+    /** Designates the badge status. */
+    public enum Status {
         INFO("i"),
         WARN("w"),
-        ERROR("e"),
-        CHECK_MARK("/"),
-        X_MARK("X"),
-        NUMBER("n");
+        ERROR("e");
 
         private String code;
 
-        Type(String code) {
+        Status(String code) {
             this.code = code;
         }
 
@@ -43,33 +40,60 @@
             return "{" + code + "}";
         }
 
-        /** Returns the type's code in string form. */
+        /** Returns the status code in string form. */
         public String code() {
             return code;
         }
     }
 
-    private final Type type;
+    private final Status status;
+    private final boolean isGlyph;
+    private final String text;
     private final String message;
 
     // only instantiated through static methods.
-    private NodeBadge(Type type, String message) {
-        this.type = type;
+    private NodeBadge(Status status, boolean isGlyph, String text, String message) {
+        this.status = status == null ? Status.INFO : status;
+        this.isGlyph = isGlyph;
+        this.text = text;
         this.message = message;
     }
 
     @Override
     public String toString() {
-        return "{Badge " + type + " \"" + message + "\"}";
+        return "{Badge " + status +
+                " (" + text + ")" +
+                (isGlyph ? "*G " : " ") +
+                "\"" + message + "\"}";
     }
 
     /**
-     * Returns the badge type.
+     * Returns the badge status.
      *
-     * @return badge type
+     * @return badge status
      */
-    public Type type() {
-        return type;
+    public Status status() {
+        return status;
+    }
+
+    /**
+     * Returns true if the text for this badge designates a glyph ID.
+     *
+     * @return true if badge uses glyph
+     */
+    public boolean isGlyph() {
+        return isGlyph;
+    }
+
+    /**
+     * Returns the text for the badge.
+     * Note that if {@link #isGlyph} is true, the text is a glyph ID, otherwise
+     * the text is displayed verbatim in the badge.
+     *
+     * @return text for badge
+     */
+    public String text() {
+        return text;
     }
 
     /**
@@ -86,64 +110,111 @@
     }
 
     /**
-     * Returns an informational badge, with associated message.
+     * Returns an arbitrary text badge, with default status.
      *
-     * @param message the message
-     * @return INFO type node badge
+     * @param txt the text
+     * @return node badge to display text
      */
-    public static NodeBadge info(String message) {
-        return new NodeBadge(Type.INFO, nonNull(message));
+    public static NodeBadge text(String txt) {
+        // TODO: consider length constraint on txt (3 chars?)
+        return new NodeBadge(Status.INFO, false, nonNull(txt), null);
     }
 
     /**
-     * Returns a warning badge, with associated message.
+     * Returns a glyph badge, with default status.
      *
-     * @param message the message
-     * @return WARN type node badge
+     * @param gid the glyph ID
+     * @return node badge to display glyph
      */
-    public static NodeBadge warn(String message) {
-        return new NodeBadge(Type.WARN, nonNull(message));
+    public static NodeBadge glyph(String gid) {
+        return new NodeBadge(Status.INFO, true, nonNull(gid), null);
     }
 
     /**
-     * Returns an error badge, with associated message.
-     *
-     * @param message the message
-     * @return ERROR type node badge
-     */
-    public static NodeBadge error(String message) {
-        return new NodeBadge(Type.ERROR, nonNull(message));
-    }
-
-    /**
-     * Returns a check-mark badge, with associated message.
-     *
-     * @param message the message
-     * @return CHECK_MARK type node badge
-     */
-    public static NodeBadge checkMark(String message) {
-        return new NodeBadge(Type.CHECK_MARK, nonNull(message));
-    }
-
-    /**
-     * Returns an X-mark badge, with associated message.
-     *
-     * @param message the message
-     * @return X_MARK type node badge
-     */
-    public static NodeBadge xMark(String message) {
-        return new NodeBadge(Type.X_MARK, nonNull(message));
-    }
-
-    /**
-     * Returns a numeric badge.
+     * Returns a numeric badge, with default status.
      *
      * @param n the number
-     * @return NUMBER type node badge
+     * @return node badge to display a number
      */
     public static NodeBadge number(int n) {
-        // TODO: consider constraints, e.g. 1 <= n <= 99
-        return new NodeBadge(Type.NUMBER, Integer.toString(n));
+        // TODO: consider constraints, e.g. 1 <= n <= 999
+        return new NodeBadge(Status.INFO, false, Integer.toString(n), null);
+    }
+
+    /**
+     * Returns an arbitrary text badge, with the given status.
+     *
+     * @param s the status
+     * @param txt the text
+     * @return node badge to display text
+     */
+    public static NodeBadge text(Status s, String txt) {
+        // TODO: consider length constraint on txt (3 chars?)
+        return new NodeBadge(s, false, nonNull(txt), null);
+    }
+
+    /**
+     * Returns a glyph badge, with the given status.
+     *
+     * @param s the status
+     * @param gid the glyph ID
+     * @return node badge to display glyph
+     */
+    public static NodeBadge glyph(Status s, String gid) {
+        return new NodeBadge(s, true, nonNull(gid), null);
+    }
+
+
+    /**
+     * Returns a numeric badge, with the given status and optional message.
+     *
+     * @param s the status
+     * @param n the number
+     * @return node badge to display a number
+     */
+    public static NodeBadge number(Status s, int n) {
+        // TODO: consider constraints, e.g. 1 <= n <= 999
+        return new NodeBadge(s, false, Integer.toString(n), null);
+    }
+
+    /**
+     * Returns an arbitrary text badge, with the given status and optional
+     * message.
+     *
+     * @param s the status
+     * @param txt the text
+     * @param msg the optional message
+     * @return node badge to display text
+     */
+    public static NodeBadge text(Status s, String txt, String msg) {
+        // TODO: consider length constraint on txt (3 chars?)
+        return new NodeBadge(s, false, nonNull(txt), msg);
+    }
+
+    /**
+     * Returns a glyph badge, with the given status and optional message.
+     *
+     * @param s the status
+     * @param gid the glyph ID
+     * @param msg the optional message
+     * @return node badge to display glyph
+     */
+    public static NodeBadge glyph(Status s, String gid, String msg) {
+        return new NodeBadge(s, true, nonNull(gid), msg);
+    }
+
+
+    /**
+     * Returns a numeric badge, with the given status and optional message.
+     *
+     * @param s the status
+     * @param n the number
+     * @param msg the optional message
+     * @return node badge to display a number
+     */
+    public static NodeBadge number(Status s, int n, String msg) {
+        // TODO: consider constraints, e.g. 1 <= n <= 999
+        return new NodeBadge(s, false, Integer.toString(n), msg);
     }
 
 }
diff --git a/core/api/src/main/java/org/onosproject/ui/topo/TopoJson.java b/core/api/src/main/java/org/onosproject/ui/topo/TopoJson.java
index d4990f3..4030abd 100644
--- a/core/api/src/main/java/org/onosproject/ui/topo/TopoJson.java
+++ b/core/api/src/main/java/org/onosproject/ui/topo/TopoJson.java
@@ -38,6 +38,9 @@
     static final String LABEL = "label";
     static final String CSS = "css";
     static final String BADGE = "badge";
+    static final String STATUS = "status";
+    static final String TXT = "txt";
+    static final String GID = "gid";
     static final String MSG = "msg";
 
     static final String TITLE = "title";
@@ -99,6 +102,16 @@
         return payload;
     }
 
+    private static ObjectNode json(NodeBadge b) {
+        ObjectNode n = objectNode()
+                .put(STATUS, b.status().code())
+                .put(b.isGlyph() ? GID : TXT, b.text());
+        if (b.message() != null) {
+            n.put(MSG, b.message());
+        }
+        return n;
+    }
+
     private static ObjectNode json(DeviceHighlight dh) {
         ObjectNode n = objectNode()
                 .put(ID, dh.elementId());
@@ -107,10 +120,7 @@
         }
         NodeBadge badge = dh.badge();
         if (badge != null) {
-            ObjectNode b = objectNode()
-                    .put(TYPE, badge.type().code())
-                    .put(MSG, badge.message());
-            n.set(BADGE, b);
+            n.set(BADGE, json(badge));
         }
         return n;
     }
diff --git a/core/api/src/test/java/org/onosproject/ui/topo/NodeBadgeTest.java b/core/api/src/test/java/org/onosproject/ui/topo/NodeBadgeTest.java
index 12b0db6..c824369 100644
--- a/core/api/src/test/java/org/onosproject/ui/topo/NodeBadgeTest.java
+++ b/core/api/src/test/java/org/onosproject/ui/topo/NodeBadgeTest.java
@@ -17,6 +17,7 @@
 package org.onosproject.ui.topo;
 
 import org.junit.Test;
+import org.onosproject.ui.topo.NodeBadge.Status;
 
 import static org.junit.Assert.assertEquals;
 
@@ -25,92 +26,87 @@
  */
 public class NodeBadgeTest {
 
-    private static final String SOME_MSG = "a msg";
-    private static final String WR_T = "wrong type";
+    private static final String MSG = "a msg";
+    private static final String TXT = "text";
+    private static final String GID = "glyph-id";
+    private static final int NUM = 42;
+    private static final String NUM_STR = Integer.toString(NUM);
+
+    private static final String WR_S = "wrong status";
+    private static final String WR_B = "wrong boolean";
+    private static final String WR_T = "wrong text";
     private static final String WR_M = "wrong message";
     private static final String WR_SF = "wrong string format";
 
     private NodeBadge badge;
 
-    private String expStr(String t) {
-        return "{Badge {" + t + "} \"" + SOME_MSG + "\"}";
-    }
-
-    private String expNumStr(String n) {
-        return "{Badge {n} \"" + n + "\"}";
-    }
-
-    @Test
-    public void info() {
-        badge = NodeBadge.info(SOME_MSG);
-        assertEquals(WR_T, NodeBadge.Type.INFO, badge.type());
-        assertEquals(WR_M, SOME_MSG, badge.message());
-        assertEquals(WR_SF, expStr("i"), badge.toString());
-    }
-
-    @Test
-    public void warn() {
-        badge = NodeBadge.warn(SOME_MSG);
-        assertEquals(WR_T, NodeBadge.Type.WARN, badge.type());
-        assertEquals(WR_M, SOME_MSG, badge.message());
-        assertEquals(WR_SF, expStr("w"), badge.toString());
-    }
-
-    @Test
-    public void error() {
-        badge = NodeBadge.error(SOME_MSG);
-        assertEquals(WR_T, NodeBadge.Type.ERROR, badge.type());
-        assertEquals(WR_M, SOME_MSG, badge.message());
-        assertEquals(WR_SF, expStr("e"), badge.toString());
-    }
-
-    @Test
-    public void checkMark() {
-        badge = NodeBadge.checkMark(SOME_MSG);
-        assertEquals(WR_T, NodeBadge.Type.CHECK_MARK, badge.type());
-        assertEquals(WR_M, SOME_MSG, badge.message());
-        assertEquals(WR_SF, expStr("/"), badge.toString());
-    }
-
-    @Test
-    public void xMark() {
-        badge = NodeBadge.xMark(SOME_MSG);
-        assertEquals(WR_T, NodeBadge.Type.X_MARK, badge.type());
-        assertEquals(WR_M, SOME_MSG, badge.message());
-        assertEquals(WR_SF, expStr("X"), badge.toString());
-    }
-
-    @Test
-    public void number0() {
-        badge = NodeBadge.number(0);
-        assertEquals(WR_T, NodeBadge.Type.NUMBER, badge.type());
-        assertEquals(WR_M, "0", badge.message());
-        assertEquals(WR_SF, expNumStr("0"), badge.toString());
-    }
-
-    @Test
-    public void number5() {
-        badge = NodeBadge.number(5);
-        assertEquals(WR_T, NodeBadge.Type.NUMBER, badge.type());
-        assertEquals(WR_M, "5", badge.message());
-        assertEquals(WR_SF, expNumStr("5"), badge.toString());
-    }
-
-    @Test
-    public void number99() {
-        badge = NodeBadge.number(99);
-        assertEquals(WR_T, NodeBadge.Type.NUMBER, badge.type());
-        assertEquals(WR_M, "99", badge.message());
-        assertEquals(WR_SF, expNumStr("99"), badge.toString());
+    private void checkFields(NodeBadge b, Status s, boolean g,
+                             String txt, String msg) {
+        assertEquals(WR_S, s, b.status());
+        assertEquals(WR_B, g, b.isGlyph());
+        assertEquals(WR_T, txt, b.text());
+        assertEquals(WR_M, msg, b.message());
     }
 
     @Test
     public void badgeTypes() {
-        assertEquals(WR_SF, "i", NodeBadge.Type.INFO.code());
-        assertEquals(WR_SF, "w", NodeBadge.Type.WARN.code());
-        assertEquals(WR_SF, "e", NodeBadge.Type.ERROR.code());
-        assertEquals(WR_SF, "/", NodeBadge.Type.CHECK_MARK.code());
-        assertEquals(WR_SF, "X", NodeBadge.Type.X_MARK.code());
-        assertEquals(WR_SF, "n", NodeBadge.Type.NUMBER.code());
+        assertEquals(WR_SF, "i", Status.INFO.code());
+        assertEquals(WR_SF, "w", Status.WARN.code());
+        assertEquals(WR_SF, "e", Status.ERROR.code());
+        assertEquals("unexpected size", 3, Status.values().length);
+    }
+
+    @Test
+    public void textOnly() {
+        badge = NodeBadge.text(TXT);
+        checkFields(badge, Status.INFO, false, TXT, null);
+    }
+
+    @Test
+    public void glyphOnly() {
+        badge = NodeBadge.glyph(GID);
+        checkFields(badge, Status.INFO, true, GID, null);
+    }
+
+    @Test
+    public void numberOnly() {
+        badge = NodeBadge.number(NUM);
+        checkFields(badge, Status.INFO, false, NUM_STR, null);
+    }
+
+    @Test
+    public void textInfo() {
+        badge = NodeBadge.text(Status.INFO, TXT);
+        checkFields(badge, Status.INFO, false, TXT, null);
+    }
+
+    @Test
+    public void glyphWarn() {
+        badge = NodeBadge.glyph(Status.WARN, GID);
+        checkFields(badge, Status.WARN, true, GID, null);
+    }
+
+    @Test
+    public void numberError() {
+        badge = NodeBadge.number(Status.ERROR, NUM);
+        checkFields(badge, Status.ERROR, false, NUM_STR, null);
+    }
+
+    @Test
+    public void textInfoMsg() {
+        badge = NodeBadge.text(Status.INFO, TXT, MSG);
+        checkFields(badge, Status.INFO, false, TXT, MSG);
+    }
+
+    @Test
+    public void glyphWarnMsg() {
+        badge = NodeBadge.glyph(Status.WARN, GID, MSG);
+        checkFields(badge, Status.WARN, true, GID, MSG);
+    }
+
+    @Test
+    public void numberErrorMsg() {
+        badge = NodeBadge.number(Status.ERROR, NUM, MSG);
+        checkFields(badge, Status.ERROR, false, NUM_STR, MSG);
     }
 }
diff --git a/core/api/src/test/java/org/onosproject/ui/topo/TopoJsonTest.java b/core/api/src/test/java/org/onosproject/ui/topo/TopoJsonTest.java
index 3b572a97..50aabf8 100644
--- a/core/api/src/test/java/org/onosproject/ui/topo/TopoJsonTest.java
+++ b/core/api/src/test/java/org/onosproject/ui/topo/TopoJsonTest.java
@@ -21,9 +21,11 @@
 import org.junit.Test;
 import org.onosproject.ui.JsonUtils;
 import org.onosproject.ui.topo.Highlights.Amount;
+import org.onosproject.ui.topo.NodeBadge.Status;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 
 /**
  * Unit tests for {@link TopoJson}.
@@ -32,7 +34,8 @@
 
     private static final String DEV1 = "device-1";
     private static final String DEV2 = "device-2";
-    private static final String BADGE_MSG = "Hello there";
+    private static final String SOME_MSG = "Hello there";
+    private static final String GID = "glyph-ID";
 
     private ObjectNode payload;
 
@@ -78,15 +81,15 @@
     public void badgedDevice() {
         Highlights h = new Highlights();
         DeviceHighlight dh = new DeviceHighlight(DEV1);
-        dh.setBadge(NodeBadge.info(BADGE_MSG));
-        h.add(dh);
-
-        dh = new DeviceHighlight(DEV2);
         dh.setBadge(NodeBadge.number(7));
         h.add(dh);
 
+        dh = new DeviceHighlight(DEV2);
+        dh.setBadge(NodeBadge.glyph(Status.WARN, GID, SOME_MSG));
+        h.add(dh);
+
         payload = TopoJson.json(h);
-        System.out.println(payload);
+//        System.out.println(payload);
 
         // dig into the payload, and verify the badges are set on the devices
         ArrayNode a = (ArrayNode) payload.get(TopoJson.DEVICES);
@@ -96,15 +99,19 @@
 
         ObjectNode b = (ObjectNode) d.get(TopoJson.BADGE);
         assertNotNull("missing badge", b);
-        assertEquals("wrong type code", "i", b.get(TopoJson.TYPE).asText());
-        assertEquals("wrong message", BADGE_MSG, b.get(TopoJson.MSG).asText());
+        assertEquals("wrong status code", "i", b.get(TopoJson.STATUS).asText());
+        assertEquals("wrong text", "7", b.get(TopoJson.TXT).asText());
+        assertNull("glyph?", b.get(TopoJson.GID));
+        assertNull("msg?", b.get(TopoJson.MSG));
 
         d = (ObjectNode) a.get(1);
         assertEquals("wrong device id", DEV2, d.get(TopoJson.ID).asText());
 
         b = (ObjectNode) d.get(TopoJson.BADGE);
         assertNotNull("missing badge", b);
-        assertEquals("wrong type code", "n", b.get(TopoJson.TYPE).asText());
-        assertEquals("wrong message", "7", b.get(TopoJson.MSG).asText());
+        assertEquals("wrong status code", "w", b.get(TopoJson.STATUS).asText());
+        assertNull("text?", b.get(TopoJson.TXT));
+        assertEquals("wrong text", GID, b.get(TopoJson.GID).asText());
+        assertEquals("wrong message", SOME_MSG, b.get(TopoJson.MSG).asText());
     }
 }
diff --git a/web/gui/src/main/webapp/app/view/topo/topoD3.js b/web/gui/src/main/webapp/app/view/topo/topoD3.js
index d29748b..1f061dd 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoD3.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoD3.js
@@ -218,6 +218,24 @@
             .attr('transform', sus.translate(dx, dy));
     }
 
+    function updateDeviceBadge(d) {
+        // TODO: Fix this WIP
+        var node = d.el,
+            bsel;
+
+        if (d.badge) {
+            bsel = node.append('g')
+                .classed('badge', true)
+                .attr('transform', sus.translate(-14, -14));
+
+            bsel.append('circle')
+                .attr('r', 14);
+            bsel.append('text')
+                .attr('transform', sus.translate(-5, 3))
+                .text('42');
+        }
+    }
+
     function updateHostLabel(d) {
         var label = trimLabel(hostLabel(d));
         d.el.select('text').text(label);
@@ -241,6 +259,7 @@
         var node = d.el;
         node.classed('online', d.online);
         updateDeviceLabel(d);
+        updateDeviceBadge(d);
         api.posNode(d, true);
     }
 
diff --git a/web/gui/src/main/webapp/app/view/topo/topoForce.js b/web/gui/src/main/webapp/app/view/topo/topoForce.js
index dbe8f9f..f00b87f 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoForce.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoForce.js
@@ -860,6 +860,16 @@
         });
     }
 
+    function clearNodeDeco() {
+        node.selectAll('g.badge').remove();
+    }
+
+    function removeNodeBadges() {
+        network.nodes.forEach(function (d) {
+            d.badge = null;
+        });
+    }
+
     function updateLinkLabelModel() {
         // create the backing data for showing labels..
         var data = [];
@@ -923,6 +933,8 @@
 
     function mkOverlayApi() {
         return {
+            clearNodeDeco: clearNodeDeco,
+            removeNodeBadges: removeNodeBadges,
             clearLinkTrafficStyle: clearLinkTrafficStyle,
             removeLinkLabels: removeLinkLabels,
             findLinkById: tms.findLinkById,
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 74fa2f2..9a3b435 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoOverlay.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoOverlay.js
@@ -294,7 +294,8 @@
              unsupLink( key, [less] )
          */
 
-        // TODO: clear node highlighting
+        api.clearNodeDeco();
+        api.removeNodeBadges();
         api.clearLinkTrafficStyle();
         api.removeLinkLabels();
 
@@ -319,8 +320,11 @@
         });
 
         data.devices.forEach(function (device) {
-            var ddata = api.findNodeById(device.id);
+            var ddata = api.findNodeById(device.id),
+                badgeData = device.badge || null;
+
             if (ddata && !ddata.el.empty()) {
+                ddata.badge = badgeData;
                 if (!device.subdue) {
                     api.unsupNode(ddata.id, less);
                 }
diff --git a/web/gui/src/test/_karma/ev/badges/ev_6_showHighlights_stuff.json b/web/gui/src/test/_karma/ev/badges/ev_6_showHighlights_stuff.json
index 7e72853..74c42c5 100644
--- a/web/gui/src/test/_karma/ev/badges/ev_6_showHighlights_stuff.json
+++ b/web/gui/src/test/_karma/ev/badges/ev_6_showHighlights_stuff.json
@@ -1,7 +1,23 @@
 {
   "event": "showHighlights",
   "payload": {
-    "devices": [],
+    "devices": [
+      {
+        "id": "of:0000000000000001",
+        "badge": {
+          "status": "e",
+          "gid": "xMark",
+          "msg": "x marks the spot"
+        }
+      },
+      {
+        "id": "of:0000000000000002",
+        "badge": {
+          "status": "w",
+          "txt": "7"
+        }
+      }
+    ],
     "hosts": [],
     "links": [
       {