Added UI_TYPE annotation key, and augmented BasicDeviceConfig and BasicHostConfig to support defining a custom "uiType" value to override the glyph used in rendering on the Topology View.

Change-Id: I615540419bce6e89e3761ef6ed3b9906be64b266
diff --git a/core/api/src/main/java/org/onosproject/net/AnnotationKeys.java b/core/api/src/main/java/org/onosproject/net/AnnotationKeys.java
index 9fb7e33..5345e34 100644
--- a/core/api/src/main/java/org/onosproject/net/AnnotationKeys.java
+++ b/core/api/src/main/java/org/onosproject/net/AnnotationKeys.java
@@ -21,7 +21,6 @@
  * Number of the annotation keys have been deprecated as the use of annotations
  * is being phased out and instead network configuration subsystem is being
  * phased-in for majority of model meta-data.
- * </p>
  */
 public final class AnnotationKeys {
 
@@ -43,6 +42,11 @@
     public static final String TYPE = "type";
 
     /**
+     * Annotation key for UI type (the glyph ID for rendering).
+     */
+    public static final String UI_TYPE = "uiType";
+
+    /**
      * Annotation key for latitude (e.g. latitude of device).
      */
     public static final String LATITUDE = "latitude";
diff --git a/core/api/src/main/java/org/onosproject/net/config/basics/BasicDeviceConfig.java b/core/api/src/main/java/org/onosproject/net/config/basics/BasicDeviceConfig.java
index 6401ab4..ebcdd82 100644
--- a/core/api/src/main/java/org/onosproject/net/config/basics/BasicDeviceConfig.java
+++ b/core/api/src/main/java/org/onosproject/net/config/basics/BasicDeviceConfig.java
@@ -18,6 +18,7 @@
 import org.onosproject.net.Device;
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.key.DeviceKeyId;
+
 /**
  * Basic configuration for network infrastructure devices.
  */
@@ -34,9 +35,9 @@
 
     @Override
     public boolean isValid() {
-        return hasOnlyFields(ALLOWED, NAME, LATITUDE, LONGITUDE, RACK_ADDRESS, OWNER,
-                             TYPE, DRIVER, MANUFACTURER, HW_VERSION, SW_VERSION, SERIAL,
-                             MANAGEMENT_ADDRESS, DEVICE_KEY_ID);
+        return hasOnlyFields(ALLOWED, NAME, LATITUDE, LONGITUDE, UI_TYPE,
+                RACK_ADDRESS, OWNER, TYPE, DRIVER, MANUFACTURER, HW_VERSION,
+                SW_VERSION, SERIAL, MANAGEMENT_ADDRESS, DEVICE_KEY_ID);
     }
 
     /**
@@ -185,12 +186,12 @@
     /**
      * Sets the device key id.
      *
-     * @param deviceKeyId new device key id; null to clear
+     * @param deviceKeyId the new device key id; null to clear
      * @return self
      */
     public BasicDeviceConfig deviceKeyId(DeviceKeyId deviceKeyId) {
         return (BasicDeviceConfig) setOrClear(DEVICE_KEY_ID,
-                                              deviceKeyId != null ? deviceKeyId.id() : null);
+                deviceKeyId != null ? deviceKeyId.id() : null);
     }
 
     // TODO: device port meta-data to be configured via BasicPortsConfig
diff --git a/core/api/src/main/java/org/onosproject/net/config/basics/BasicElementConfig.java b/core/api/src/main/java/org/onosproject/net/config/basics/BasicElementConfig.java
index 74257e6..7c2a5b8 100644
--- a/core/api/src/main/java/org/onosproject/net/config/basics/BasicElementConfig.java
+++ b/core/api/src/main/java/org/onosproject/net/config/basics/BasicElementConfig.java
@@ -23,6 +23,7 @@
 public abstract class BasicElementConfig<S> extends AllowedEntityConfig<S> {
 
     protected static final String NAME = "name";
+    protected static final String UI_TYPE = "uiType";
 
     protected static final String LATITUDE = "latitude";
     protected static final String LONGITUDE = "longitude";
@@ -52,15 +53,36 @@
         return (BasicElementConfig) setOrClear(NAME, name);
     }
 
-    private static boolean doubleIsZero(double value) {
+    /**
+     * Returns the UI type (glyph image to be used) for the element in
+     * the Topology View.
+     *
+     * @return the UI type
+     */
+    public String uiType() {
+        return get(UI_TYPE, null);
+    }
+
+    /**
+     * Sets the UI type (glyph image to be used) for the element in
+     * the Topology View.
+     *
+     * @param uiType the UI type; null for default
+     * @return self
+     */
+    public BasicElementConfig uiType(String uiType) {
+        return (BasicElementConfig) setOrClear(UI_TYPE, uiType);
+    }
+
+    private boolean doubleIsZero(double value) {
         return value >= -ZERO_THRESHOLD && value <= ZERO_THRESHOLD;
     }
 
     /**
      * Returns true if the geographical coordinates (latitude and longitude)
-     * are set on this element.
+     * are set on this element; false otherwise.
      *
-     * @return true if geo-coordinates are set
+     * @return true if geo-coordinates are set; false otherwise
      */
     public boolean geoCoordsSet() {
         return !doubleIsZero(latitude()) || !doubleIsZero(longitude());
diff --git a/core/api/src/main/java/org/onosproject/net/config/basics/BasicHostConfig.java b/core/api/src/main/java/org/onosproject/net/config/basics/BasicHostConfig.java
index fe3a3b7..3758c4e 100644
--- a/core/api/src/main/java/org/onosproject/net/config/basics/BasicHostConfig.java
+++ b/core/api/src/main/java/org/onosproject/net/config/basics/BasicHostConfig.java
@@ -23,8 +23,6 @@
 import java.util.HashSet;
 import java.util.Set;
 
-import static org.onosproject.net.config.basics.AllowedEntityConfig.ALLOWED;
-
 /**
  * Basic configuration for network end-station hosts.
  */
@@ -38,12 +36,12 @@
         // Location and IP addresses can be absent, but if present must be valid.
         this.location();
         this.ipAddresses();
-        return hasOnlyFields(ALLOWED, NAME, LATITUDE, LONGITUDE, RACK_ADDRESS, OWNER,
-                             IPS, LOCATION);
+        return hasOnlyFields(ALLOWED, NAME, LATITUDE, LONGITUDE, UI_TYPE,
+                RACK_ADDRESS, OWNER, IPS, LOCATION);
     }
 
     /**
-     * Returns location of the host.
+     * Returns the location of the host.
      *
      * @return location of the host or null if not set
      * @throws IllegalArgumentException if not specified with correct format
diff --git a/core/api/src/test/java/org/onosproject/net/config/basics/BasicElementConfigTest.java b/core/api/src/test/java/org/onosproject/net/config/basics/BasicElementConfigTest.java
index 63040af..8c750ff 100644
--- a/core/api/src/test/java/org/onosproject/net/config/basics/BasicElementConfigTest.java
+++ b/core/api/src/test/java/org/onosproject/net/config/basics/BasicElementConfigTest.java
@@ -95,4 +95,13 @@
         assertEquals("lat", 3.1415, cfg.latitude(), ZERO_THRESHOLD);
         assertEquals("lon", 2.71828, cfg.longitude(), ZERO_THRESHOLD);
     }
+
+    @Test
+    public void uiType() {
+        print(cfg);
+        assertEquals("not default type", null, cfg.uiType());
+        cfg.uiType("someOtherType");
+        print(cfg);
+        assertEquals("not other type", "someOtherType", cfg.uiType());
+    }
 }
diff --git a/core/net/src/main/java/org/onosproject/net/device/impl/BasicDeviceOperator.java b/core/net/src/main/java/org/onosproject/net/device/impl/BasicDeviceOperator.java
index ede33a5..a9da3cc 100644
--- a/core/net/src/main/java/org/onosproject/net/device/impl/BasicDeviceOperator.java
+++ b/core/net/src/main/java/org/onosproject/net/device/impl/BasicDeviceOperator.java
@@ -98,10 +98,11 @@
         if (bdc.name() != null) {
             newBuilder.set(AnnotationKeys.NAME, bdc.name());
         }
-        if (bdc.latitude() != DEFAULT_COORD) {
-            newBuilder.set(AnnotationKeys.LATITUDE, Double.toString(bdc.latitude()));
+        if (bdc.uiType() != null) {
+            newBuilder.set(AnnotationKeys.UI_TYPE, bdc.uiType());
         }
-        if (bdc.longitude() != DEFAULT_COORD) {
+        if (bdc.geoCoordsSet()) {
+            newBuilder.set(AnnotationKeys.LATITUDE, Double.toString(bdc.latitude()));
             newBuilder.set(AnnotationKeys.LONGITUDE, Double.toString(bdc.longitude()));
         }
         if (bdc.rackAddress() != null) {
diff --git a/core/net/src/main/java/org/onosproject/net/host/impl/BasicHostOperator.java b/core/net/src/main/java/org/onosproject/net/host/impl/BasicHostOperator.java
index 3c60240..a35eecf 100644
--- a/core/net/src/main/java/org/onosproject/net/host/impl/BasicHostOperator.java
+++ b/core/net/src/main/java/org/onosproject/net/host/impl/BasicHostOperator.java
@@ -45,7 +45,8 @@
      * @param descr a HostDescription
      * @return HostDescription based on both sources
      */
-    public static HostDescription combine(BasicHostConfig cfg, HostDescription descr) {
+    public static HostDescription combine(BasicHostConfig cfg,
+                                          HostDescription descr) {
         if (cfg == null) {
             return descr;
         }
@@ -64,7 +65,8 @@
 
         SparseAnnotations sa = combine(cfg, descr.annotations());
         return new DefaultHostDescription(descr.hwAddress(), descr.vlan(),
-                                          location, ipAddresses, descr.configured(), sa);
+                                          location, ipAddresses,
+                                          descr.configured(), sa);
     }
 
     /**
@@ -74,11 +76,15 @@
      * @param an  the annotation
      * @return annotation combining both sources
      */
-    public static SparseAnnotations combine(BasicHostConfig cfg, SparseAnnotations an) {
+    public static SparseAnnotations combine(BasicHostConfig cfg,
+                                            SparseAnnotations an) {
         DefaultAnnotations.Builder newBuilder = DefaultAnnotations.builder();
         if (cfg.name() != null) {
             newBuilder.set(AnnotationKeys.NAME, cfg.name());
         }
+        if (cfg.uiType() != null) {
+            newBuilder.set(AnnotationKeys.UI_TYPE, cfg.uiType());
+        }
         if (cfg.geoCoordsSet()) {
             newBuilder.set(AnnotationKeys.LATITUDE, Double.toString(cfg.latitude()));
             newBuilder.set(AnnotationKeys.LONGITUDE, Double.toString(cfg.longitude()));
diff --git a/tools/test/topos/sdn-ip.json b/tools/test/topos/sdn-ip.json
index 153d995..1eee4f3 100644
--- a/tools/test/topos/sdn-ip.json
+++ b/tools/test/topos/sdn-ip.json
@@ -1,26 +1,26 @@
 {
-    "devices": [
-        { "alias":  "s1", "uri": "of:0000000000000001", "mac": "000000000001", "annotations": { "name": "DEN", "latitude": 39.739317, "longitude": -104.983791 }, "type": "SWITCH" },
-        { "alias":  "s2", "uri": "of:0000000000000002", "mac": "000000000002", "annotations": { "name": "IND", "latitude": 39.769089, "longitude": -86.158039  }, "type": "SWITCH" },
-        { "alias":  "s3", "uri": "of:0000000000000003", "mac": "000000000003", "annotations": { "name": "ABQ", "latitude": 35.116541, "longitude": -106.604146 }, "type": "SWITCH" },
-        { "alias":  "s4", "uri": "of:0000000000000004", "mac": "000000000004", "annotations": { "name": "DFW", "latitude": 32.779501, "longitude": -96.801104  }, "type": "SWITCH" },
-        { "alias":  "s5", "uri": "of:0000000000000005", "mac": "000000000005", "annotations": { "name": "PDX", "latitude": 45.522585, "longitude": -122.677890 }, "type": "SWITCH" },
-        { "alias":  "s6", "uri": "of:0000000000000006", "mac": "000000000006", "annotations": { "name": "SFO", "latitude": 37.785286, "longitude": -122.406509 }, "type": "SWITCH" },
-        { "alias":  "s7", "uri": "of:0000000000000007", "mac": "000000000007", "annotations": { "name": "LAX", "latitude": 34.055604, "longitude": -118.248567 }, "type": "SWITCH" },
-        { "alias":  "s8", "uri": "of:0000000000000008", "mac": "000000000008", "annotations": { "name": "JFK", "latitude": 40.769487, "longitude": -73.972520  }, "type": "SWITCH" },
-        { "alias":  "s9", "uri": "of:0000000000000009", "mac": "000000000009", "annotations": { "name": "IAD", "latitude": 38.897676, "longitude": -77.036525  }, "type": "SWITCH" },
-        { "alias": "s10", "uri": "of:0000000000000010", "mac": "000000000010", "annotations": { "name": "ATL", "latitude": 33.756298, "longitude": -84.388507  }, "type": "SWITCH" }
-    ],
+  "devices": {
+    "of:0000000000000001": { "basic": { "name": "DEN", "latitude": 39.739317, "longitude": -104.983791}},
+    "of:0000000000000002": { "basic": { "name": "IND", "latitude": 39.769089, "longitude": -86.158039}},
+    "of:0000000000000003": { "basic": { "name": "ABQ", "latitude": 35.116541, "longitude": -106.604146}},
+    "of:0000000000000004": { "basic": { "name": "DFW", "latitude": 32.779501, "longitude": -96.801104}},
+    "of:0000000000000005": { "basic": { "name": "PDX", "latitude": 45.522585, "longitude": -122.677890}},
+    "of:0000000000000006": { "basic": { "name": "SFO", "latitude": 37.785286, "longitude": -122.406509}},
+    "of:0000000000000007": { "basic": { "name": "LAX", "latitude": 34.055604, "longitude": -118.248567}},
+    "of:0000000000000008": { "basic": { "name": "JFK", "latitude": 40.769487, "longitude": -73.972520}},
+    "of:0000000000000009": { "basic": { "name": "IAD", "latitude": 38.897676, "longitude": -77.036525}},
+    "of:000000000000000a": { "basic": { "name": "ATL", "latitude": 33.756298, "longitude": -84.388507}}
+  },
 
-    "hosts" : [
-        { "mac": "00:00:00:00:00:01", "vlan": -1, "location": "of:0000000000000001/10", "ip": "10.0.1.2, 10.0.2.2",                         "annotations": { "type": "bgpSpeaker", "latitude": 42.292306, "longitude": -104.084378 } },
-        { "mac": "00:00:00:00:00:02", "vlan": -1, "location": "of:0000000000000002/10", "ip": "10.0.11.2, 10.0.22.2, 10.0.3.2, 10.0.4.2",   "annotations": { "type": "bgpSpeaker", "latitude": 41.019068, "longitude": -91.079570 } },
-        { "mac": "00:00:00:00:00:03", "vlan": -1, "location": "of:0000000000000003/10", "ip": "10.0.33.2, 10.0.44.2",                       "annotations": { "type": "bgpSpeaker", "latitude": 32.043892, "longitude": -105.644437 } },
-        { "mac": "00:00:00:00:01:01", "vlan": -1, "location": "of:0000000000000005/10", "ip": "10.0.1.1",                                   "annotations": { "type": "router", "latitude": 42.985256, "longitude": -127.074018 } },
-        { "mac": "00:00:00:00:01:02", "vlan": -1, "location": "of:0000000000000006/10", "ip": "10.0.11.1",                                  "annotations": { "type": "router", "latitude": 40.593824, "longitude": -127.074018 } },
-        { "mac": "00:00:00:00:02:01", "vlan": -1, "location": "of:0000000000000007/10", "ip": "10.0.2.1, 10.0.22.1",                        "annotations": { "type": "router", "latitude": 31.277098, "longitude": -121.35488 } },
-        { "mac": "00:00:00:00:03:01", "vlan": -1, "location": "of:0000000000000008/10", "ip": "10.0.3.1, 10.0.33.1",                        "annotations": { "type": "router", "latitude": 39.368502, "longitude": -69.976271 } },
-        { "mac": "00:00:00:00:04:01", "vlan": -1, "location": "of:0000000000000009/10", "ip": "10.0.4.1",                                   "annotations": { "type": "router", "latitude": 34.936264, "longitude": -75.526168 } },
-        { "mac": "00:00:00:00:44:01", "vlan": -1, "location": "of:0000000000000010/10", "ip": "10.0.44.1",                                  "annotations": { "type": "router", "latitude": 32.863420, "longitude": -77.505795 } }
-    ]
+  "hosts": {
+    "00:00:00:00:00:01/-1": { "basic": { "location": "of:0000000000000001/10", "uiType": "bgpSpeaker", "latitude": 42.292306, "longitude": -104.084378, "ips": [ "10.0.1.2",  "10.0.2.2" ] }},
+    "00:00:00:00:00:02/-1": { "basic": { "location": "of:0000000000000002/10", "uiType": "bgpSpeaker", "latitude": 41.019068, "longitude":  -91.079570, "ips": [ "10.0.11.2", "10.0.22.2", "10.0.3.2", "10.0.4.2" ] }},
+    "00:00:00:00:00:03/-1": { "basic": { "location": "of:0000000000000003/10", "uiType": "bgpSpeaker", "latitude": 32.043892, "longitude": -105.644437, "ips": [ "10.0.33.2", "10.0.44.2" ] }},
+    "00:00:00:00:01:01/-1": { "basic": { "location": "of:0000000000000005/10", "uiType": "router", "latitude": 42.985256, "longitude": -127.074018, "ips": [ "10.0.1.1" ] }},
+    "00:00:00:00:01:02/-1": { "basic": { "location": "of:0000000000000006/10", "uiType": "router", "latitude": 40.593824, "longitude": -127.074018, "ips": [ "10.0.11.1" ] }},
+    "00:00:00:00:02:01/-1": { "basic": { "location": "of:0000000000000007/10", "uiType": "router", "latitude": 31.277098, "longitude": -121.354880, "ips": [ "10.0.2.1", "10.0.22.1" ] }},
+    "00:00:00:00:03:01/-1": { "basic": { "location": "of:0000000000000008/10", "uiType": "router", "latitude": 39.368502, "longitude":  -69.976271, "ips": [ "10.0.3.1", "10.0.33.1" ] }},
+    "00:00:00:00:04:01/-1": { "basic": { "location": "of:0000000000000009/10", "uiType": "router", "latitude": 34.936264, "longitude":  -75.526168, "ips": [ "10.0.4.1" ] }},
+    "00:00:00:00:44:01/-1": { "basic": { "location": "of:000000000000000a/10", "uiType": "router", "latitude": 32.863420, "longitude":  -77.505795, "ips": [ "10.0.44.1" ] }}
+  }
 }
diff --git a/tools/test/topos/uk.json b/tools/test/topos/uk.json
index 7e187cb..e7f5f98 100644
--- a/tools/test/topos/uk.json
+++ b/tools/test/topos/uk.json
@@ -1,17 +1,17 @@
 {
   "devices": {
-    "of:0000000000000001": { "basic": { "name": "LONDON", "latitude": 51.5072, "longitude": -0.1275   }},
-    "of:0000000000000002": { "basic": { "name": "BRISTL", "latitude": 51.4500, "longitude": -2.5833  }},
-    "of:0000000000000003": { "basic": { "name": "BIRMHM", "latitude": 52.4831, "longitude": -1.8936  }},
-    "of:0000000000000004": { "basic": { "name": "PLYMTH", "latitude": 50.3714, "longitude": -4.1422  }},
-    "of:0000000000000005": { "basic": { "name": "DOVER",  "latitude": 51.1295, "longitude":  1.3089  }},
-    "of:0000000000000006": { "basic": { "name": "BRGHTN", "latitude": 50.8429, "longitude": -0.1313  }},
-    "of:0000000000000007": { "basic": { "name": "LIVRPL", "latitude": 53.4000, "longitude": -3.0000  }},
-    "of:0000000000000008": { "basic": { "name": "YORK",   "latitude": 53.9583, "longitude": -1.0803  }},
-    "of:0000000000000009": { "basic": { "name": "NWCSTL", "latitude": 54.9667, "longitude": -1.6000  }},
-    "of:000000000000000a": { "basic": { "name": "NRWICH", "latitude": 52.6283, "longitude":  1.2967  }},
-    "of:000000000000000b": { "basic": { "name": "EDBUGH", "latitude": 55.9531, "longitude": -3.1889  }},
-    "of:000000000000000c": { "basic": { "name": "ABYSTW", "latitude": 52.4140, "longitude": -4.0810  }}
+    "of:0000000000000001": { "basic": { "name": "LONDON", "latitude": 51.5072, "longitude": -0.1275 }},
+    "of:0000000000000002": { "basic": { "name": "BRISTL", "latitude": 51.4500, "longitude": -2.5833 }},
+    "of:0000000000000003": { "basic": { "name": "BIRMHM", "latitude": 52.4831, "longitude": -1.8936 }},
+    "of:0000000000000004": { "basic": { "name": "PLYMTH", "latitude": 50.3714, "longitude": -4.1422 }},
+    "of:0000000000000005": { "basic": { "name": "DOVER",  "latitude": 51.1295, "longitude":  1.3089 }},
+    "of:0000000000000006": { "basic": { "name": "BRGHTN", "latitude": 50.8429, "longitude": -0.1313 }},
+    "of:0000000000000007": { "basic": { "name": "LIVRPL", "latitude": 53.4000, "longitude": -3.0000 }},
+    "of:0000000000000008": { "basic": { "name": "YORK",   "latitude": 53.9583, "longitude": -1.0803 }},
+    "of:0000000000000009": { "basic": { "name": "NWCSTL", "latitude": 54.9667, "longitude": -1.6000 }},
+    "of:000000000000000a": { "basic": { "name": "NRWICH", "latitude": 52.6283, "longitude":  1.2967 }},
+    "of:000000000000000b": { "basic": { "name": "EDBUGH", "latitude": 55.9531, "longitude": -3.1889 }},
+    "of:000000000000000c": { "basic": { "name": "ABYSTW", "latitude": 52.4140, "longitude": -4.0810 }}
   },
 
   "hosts": {
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 985cb17..c2c14f2 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
@@ -253,9 +253,13 @@
     // Produces a device event message to the client.
     protected ObjectNode deviceMessage(DeviceEvent event) {
         Device device = event.subject();
+        String uiType = device.annotations().value(AnnotationKeys.UI_TYPE);
+        String devType = uiType != null ? uiType :
+                device.type().toString().toLowerCase();
+
         ObjectNode payload = objectNode()
                 .put("id", device.id().toString())
-                .put("type", device.type().toString().toLowerCase())
+                .put("type", devType)
                 .put("online", deviceService.isAvailable(device.id()))
                 .put("master", master(device.id()));
 
@@ -297,7 +301,7 @@
     protected ObjectNode hostMessage(HostEvent event) {
         Host host = event.subject();
         Host prevHost = event.prevSubject();
-        String hostType = host.annotations().value(AnnotationKeys.TYPE);
+        String hostType = host.annotations().value(AnnotationKeys.UI_TYPE);
 
         ObjectNode payload = objectNode()
                 .put("id", host.id().toString())