ONOS-1479 -- GUI - augmenting topology view for extensibility:
- Cleaning up PropertyPanel methods - overloading values (string, int, long, object)

Change-Id: I84c86a48de8a776b407982687bc02def18eaef61
diff --git a/core/api/src/main/java/org/onosproject/ui/topo/PropertyPanel.java b/core/api/src/main/java/org/onosproject/ui/topo/PropertyPanel.java
index 88dad9a..87b6839 100644
--- a/core/api/src/main/java/org/onosproject/ui/topo/PropertyPanel.java
+++ b/core/api/src/main/java/org/onosproject/ui/topo/PropertyPanel.java
@@ -29,6 +29,8 @@
  */
 public class PropertyPanel {
 
+    private static final DecimalFormat DF0 = new DecimalFormat("#,###");
+
     private String title;
     private String typeId;
     private String id;
@@ -47,17 +49,6 @@
     }
 
     /**
-     * Adds a property to the panel.
-     *
-     * @param p the property
-     * @return self, for chaining
-     */
-    public PropertyPanel add(Prop p) {
-        properties.add(p);
-        return this;
-    }
-
-    /**
      * Adds an ID field to the panel data, to be included in
      * the returned JSON data to the client.
      *
@@ -69,6 +60,82 @@
         return this;
     }
 
+    /**
+     * Adds a property to the panel data.
+     *
+     * @param key property key
+     * @param value property value
+     * @return self, for chaining
+     */
+    public PropertyPanel addProp(String key, String value) {
+        properties.add(new Prop(key, value));
+        return this;
+    }
+
+    /**
+     * Adds a property to the panel data, using a decimal formatter.
+     *
+     * @param key property key
+     * @param value property value
+     * @return self, for chaining
+     */
+    public PropertyPanel addProp(String key, int value) {
+        properties.add(new Prop(key, DF0.format(value)));
+        return this;
+    }
+
+    /**
+     * Adds a property to the panel data, using a decimal formatter.
+     *
+     * @param key property key
+     * @param value property value
+     * @return self, for chaining
+     */
+    public PropertyPanel addProp(String key, long value) {
+        properties.add(new Prop(key, DF0.format(value)));
+        return this;
+    }
+
+    /**
+     * Adds a property to the panel data. Note that the value's
+     * {@link Object#toString toString()} method is used to convert the
+     * value to a string.
+     *
+     * @param key property key
+     * @param value property value
+     * @return self, for chaining
+     */
+    public PropertyPanel addProp(String key, Object value) {
+        properties.add(new Prop(key, value.toString()));
+        return this;
+    }
+
+    /**
+     * Adds a property to the panel data. Note that the value's
+     * {@link Object#toString toString()} method is used to convert the
+     * value to a string, from which the characters defined in the given
+     * regular expression string are stripped.
+     *
+     * @param key property key
+     * @param value property value
+     * @param reStrip regexp characters to strip from value string
+     * @return self, for chaining
+     */
+    public PropertyPanel addProp(String key, Object value, String reStrip) {
+        String val = value.toString().replaceAll(reStrip, "");
+        properties.add(new Prop(key, val));
+        return this;
+    }
+
+    /**
+     * Adds a separator to the panel data.
+     *
+     * @return self, for chaining
+     */
+    public PropertyPanel addSeparator() {
+        properties.add(new Separator());
+        return this;
+    }
 
     /**
      * Returns the title text.
@@ -161,7 +228,6 @@
 
     // ====================
 
-    private static final DecimalFormat DF0 = new DecimalFormat("#,###");
 
     /**
      * Simple data carrier for a property, composed of a key/value pair.
@@ -182,26 +248,6 @@
         }
 
         /**
-         * Constructs a property data value.
-         * @param key property key
-         * @param value property value
-         */
-        public Prop(String key, int value) {
-            this.key = key;
-            this.value = DF0.format(value);
-        }
-
-        /**
-         * Constructs a property data value.
-         * @param key property key
-         * @param value property value
-         */
-        public Prop(String key, long value) {
-            this.key = key;
-            this.value = DF0.format(value);
-        }
-
-        /**
          * Returns the property's key.
          *
          * @return the key
diff --git a/core/api/src/test/java/org/onosproject/ui/topo/PropertyPanelTest.java b/core/api/src/test/java/org/onosproject/ui/topo/PropertyPanelTest.java
index 65bb167..7ca9ce5 100644
--- a/core/api/src/test/java/org/onosproject/ui/topo/PropertyPanelTest.java
+++ b/core/api/src/test/java/org/onosproject/ui/topo/PropertyPanelTest.java
@@ -17,12 +17,17 @@
 
 package org.onosproject.ui.topo;
 
+import org.junit.BeforeClass;
 import org.junit.Test;
 import org.onosproject.ui.topo.PropertyPanel.Prop;
 
+import java.util.HashMap;
 import java.util.Iterator;
+import java.util.Map;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
 
 /**
  * Unit tests for {@link PropertyPanel}.
@@ -33,20 +38,49 @@
     private static final String TYPE_ORIG = "Original type ID";
     private static final String TITLE_NEW = "New Title";
     private static final String TYPE_NEW = "New type";
+    private static final String SOME_IDENTIFICATION = "It's Me!";
 
-    private static final Prop PROP_A = new Prop("A", "Hay");
-    private static final Prop PROP_B = new Prop("B", "Bee");
-    private static final Prop PROP_C = new Prop("C", "Sea");
-    private static final Prop PROP_Z = new Prop("Z", "Zed");
+    private static final String KEY_A = "A";
+    private static final String KEY_B = "B";
+    private static final String KEY_C = "C";
+    private static final String KEY_Z = "Z";
+    private static final String VALUE_A = "Hay";
+    private static final String VALUE_B = "Bee";
+    private static final String VALUE_C = "Sea";
+    private static final String VALUE_Z = "Zed";
+
+    private static final Map<String, Prop> PROP_MAP = new HashMap<>();
+
+    private static class FooClass {
+        private final String s;
+        FooClass(String s) {
+            this.s = s;
+        }
+
+        @Override
+        public String toString() {
+            return ">" + s + "<";
+        }
+    }
 
     private PropertyPanel pp;
 
 
+
+    @BeforeClass
+    public static void setUpClass() {
+        PROP_MAP.put(KEY_A, new Prop(KEY_A, VALUE_A));
+        PROP_MAP.put(KEY_B, new Prop(KEY_B, VALUE_B));
+        PROP_MAP.put(KEY_C, new Prop(KEY_C, VALUE_C));
+        PROP_MAP.put(KEY_Z, new Prop(KEY_Z, VALUE_Z));
+    }
+
     @Test
     public void basic() {
         pp = new PropertyPanel(TITLE_ORIG, TYPE_ORIG);
         assertEquals("wrong title", TITLE_ORIG, pp.title());
         assertEquals("wrong type", TYPE_ORIG, pp.typeId());
+        assertNull("id?", pp.id());
         assertEquals("unexpected props", 0, pp.properties().size());
     }
 
@@ -64,20 +98,46 @@
         assertEquals("wrong type", TYPE_NEW, pp.typeId());
     }
 
-    private void validateProps(Prop... props) {
+    @Test
+    public void setId() {
+        basic();
+        pp.id(SOME_IDENTIFICATION);
+        assertEquals("wrong id", SOME_IDENTIFICATION, pp.id());
+    }
+
+    private void validateProps(String... keys) {
         Iterator<Prop> iter = pp.properties().iterator();
-        for (Prop p: props) {
-            Prop ppProp = iter.next();
-            assertEquals("Bad prop sequence", p, ppProp);
+        for (String k: keys) {
+            Prop exp = PROP_MAP.get(k);
+            Prop act = iter.next();
+            assertEquals("Bad prop sequence", exp, act);
         }
     }
 
+    private void validateProp(String key, String expValue) {
+        Iterator<Prop> iter = pp.properties().iterator();
+        Prop prop = null;
+        while (iter.hasNext()) {
+            Prop p = iter.next();
+            if (p.key().equals(key)) {
+                prop = p;
+                break;
+            }
+        }
+        if (prop == null) {
+            fail("no prop found with key: " + key);
+        }
+        assertEquals("Wrong prop value", expValue, prop.value());
+    }
+
     @Test
     public void props() {
         basic();
-        pp.add(PROP_A).add(PROP_B).add(PROP_C);
+        pp.addProp(KEY_A, VALUE_A)
+            .addProp(KEY_B, VALUE_B)
+            .addProp(KEY_C, VALUE_C);
         assertEquals("bad props", 3, pp.properties().size());
-        validateProps(PROP_A, PROP_B, PROP_C);
+        validateProps(KEY_A, KEY_B, KEY_C);
     }
 
     @Test
@@ -91,8 +151,45 @@
     @Test
     public void adjustProps() {
         props();
-        pp.removeProps("B", "A");
-        pp.add(PROP_Z);
-        validateProps(PROP_C, PROP_Z);
+        pp.removeProps(KEY_B, KEY_A);
+        pp.addProp(KEY_Z, VALUE_Z);
+        validateProps(KEY_C, KEY_Z);
     }
+
+    @Test
+    public void intValues() {
+        basic();
+        pp.addProp(KEY_A, 200)
+          .addProp(KEY_B, 2000)
+          .addProp(KEY_C, 1234567);
+
+        validateProp(KEY_A, "200");
+        validateProp(KEY_B, "2,000");
+        validateProp(KEY_C, "1,234,567");
+    }
+
+    @Test
+    public void longValues() {
+        basic();
+        pp.addProp(KEY_A, 200L)
+          .addProp(KEY_B, 2000L)
+          .addProp(KEY_C, 1234567L)
+          .addProp(KEY_Z, Long.MAX_VALUE);
+
+        validateProp(KEY_A, "200");
+        validateProp(KEY_B, "2,000");
+        validateProp(KEY_C, "1,234,567");
+        validateProp(KEY_Z, "9,223,372,036,854,775,807");
+    }
+
+    @Test
+    public void objectValue() {
+        basic();
+        pp.addProp(KEY_A, new FooClass("a"))
+            .addProp(KEY_B, new FooClass("bxyyzy"), "[xz]");
+
+        validateProp(KEY_A, ">a<");
+        validateProp(KEY_B, ">byyy<");
+    }
+
 }
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 d32ad1d..017925b 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
@@ -446,18 +446,17 @@
     // Returns property panel model for summary response.
     protected PropertyPanel summmaryMessage(long sid) {
         Topology topology = topologyService.currentTopology();
-        PropertyPanel pp = new PropertyPanel("ONOS Summary", "node")
-            .add(new PropertyPanel.Prop("Devices", topology.deviceCount()))
-            .add(new PropertyPanel.Prop("Links", topology.linkCount()))
-            .add(new PropertyPanel.Prop("Hosts", hostService.getHostCount()))
-            .add(new PropertyPanel.Prop("Topology SCCs", topology.clusterCount()))
-            .add(new PropertyPanel.Separator())
-            .add(new PropertyPanel.Prop("Intents", intentService.getIntentCount()))
-            .add(new PropertyPanel.Prop("Tunnels", tunnelService.tunnelCount()))
-            .add(new PropertyPanel.Prop("Flows", flowService.getFlowRuleCount()))
-            .add(new PropertyPanel.Prop("Version", version));
 
-        return pp;
+        return new PropertyPanel("ONOS Summary", "node")
+            .addProp("Devices", topology.deviceCount())
+            .addProp("Links", topology.linkCount())
+            .addProp("Hosts", hostService.getHostCount())
+            .addProp("Topology SCCs", topology.clusterCount())
+            .addSeparator()
+            .addProp("Intents", intentService.getIntentCount())
+            .addProp("Tunnels", tunnelService.tunnelCount())
+            .addProp("Flows", flowService.getFlowRuleCount())
+            .addProp("Version", version);
     }
 
     // Returns property panel model for device details response.
@@ -474,19 +473,21 @@
 
         PropertyPanel pp = new PropertyPanel(title, typeId)
                 .id(deviceId.toString())
-                .add(new PropertyPanel.Prop("URI", deviceId.toString()))
-                .add(new PropertyPanel.Prop("Vendor", device.manufacturer()))
-                .add(new PropertyPanel.Prop("H/W Version", device.hwVersion()))
-                .add(new PropertyPanel.Prop("S/W Version", device.swVersion()))
-                .add(new PropertyPanel.Prop("Serial Number", device.serialNumber()))
-                .add(new PropertyPanel.Prop("Protocol", annot.value(AnnotationKeys.PROTOCOL)))
-                .add(new PropertyPanel.Separator())
-                .add(new PropertyPanel.Prop("Latitude", annot.value(AnnotationKeys.LATITUDE)))
-                .add(new PropertyPanel.Prop("Longitude", annot.value(AnnotationKeys.LONGITUDE)))
-                .add(new PropertyPanel.Separator())
-                .add(new PropertyPanel.Prop("Ports", portCount))
-                .add(new PropertyPanel.Prop("Flows", flowCount))
-                .add(new PropertyPanel.Prop("Tunnels", tunnelCount));
+                .addProp("URI", deviceId.toString())
+                .addProp("Vendor", device.manufacturer())
+                .addProp("H/W Version", device.hwVersion())
+                .addProp("S/W Version", device.swVersion())
+                .addProp("Serial Number", device.serialNumber())
+                .addProp("Protocol", annot.value(AnnotationKeys.PROTOCOL))
+                .addSeparator()
+                .addProp("Latitude", annot.value(AnnotationKeys.LATITUDE))
+                .addProp("Longitude", annot.value(AnnotationKeys.LONGITUDE))
+                .addSeparator()
+                .addProp("Ports", portCount)
+                .addProp("Flows", flowCount)
+                .addProp("Tunnels", tunnelCount);
+
+        // TODO: add button descriptors
 
         return pp;
     }
@@ -570,13 +571,14 @@
 
         PropertyPanel pp = new PropertyPanel(title, typeId)
                 .id(hostId.toString())
-                .add(new PropertyPanel.Prop("MAC", host.mac().toString()))
-                .add(new PropertyPanel.Prop("IP", host.ipAddresses().toString().replaceAll("[\\[\\]]", "")))
-                .add(new PropertyPanel.Prop("VLAN", vlan.equals("-1") ? "none" : vlan))
-                .add(new PropertyPanel.Separator())
-                .add(new PropertyPanel.Prop("Latitude", annot.value(AnnotationKeys.LATITUDE)))
-                .add(new PropertyPanel.Prop("Longitude", annot.value(AnnotationKeys.LONGITUDE)));
+                .addProp("MAC", host.mac())
+                .addProp("IP", host.ipAddresses(), "[\\[\\]]")
+                .addProp("VLAN", vlan.equals("-1") ? "none" : vlan)
+                .addSeparator()
+                .addProp("Latitude", annot.value(AnnotationKeys.LATITUDE))
+                .addProp("Longitude", annot.value(AnnotationKeys.LONGITUDE));
 
+        // TODO: add button descriptors
         return pp;
     }
 
@@ -860,21 +862,6 @@
         return result;
     }
 
-    // Produces JSON property details.
-    private ObjectNode json(String id, String type, Prop... props) {
-        ObjectNode result = objectNode()
-                .put("id", id).put("type", type);
-        ObjectNode pnode = objectNode();
-        ArrayNode porder = arrayNode();
-        for (Prop p : props) {
-            porder.add(p.key);
-            pnode.put(p.key, p.value);
-        }
-        result.set("propOrder", porder);
-        result.set("props", pnode);
-        return result;
-    }
-
     // Produces canonical link key, i.e. one that will match link and its inverse.
     static LinkKey canonicalLinkKey(Link link) {
         String sn = link.src().elementId().toString();
@@ -946,25 +933,6 @@
         }
     }
 
-    // Auxiliary key/value carrier.
-    @Deprecated
-    static class Prop {
-        public final String key;
-        public final String value;
-
-        protected Prop(String key, String value) {
-            this.key = key;
-            this.value = value;
-        }
-    }
-
-    // Auxiliary properties separator
-    @Deprecated
-    static class Separator extends Prop {
-        protected Separator() {
-            super("-", "");
-        }
-    }
 
     // TODO: move this to traffic overlay component
     // Auxiliary carrier of data for requesting traffic message.