Added CLI support for showing key/value annotations for devices, ports, links & hosts.
diff --git a/cli/src/main/java/org/onlab/onos/cli/AbstractShellCommand.java b/cli/src/main/java/org/onlab/onos/cli/AbstractShellCommand.java
index 628754a..68b3244 100644
--- a/cli/src/main/java/org/onlab/onos/cli/AbstractShellCommand.java
+++ b/cli/src/main/java/org/onlab/onos/cli/AbstractShellCommand.java
@@ -18,10 +18,13 @@
  */
 package org.onlab.onos.cli;
 
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
 import org.apache.karaf.shell.commands.Option;
 import org.apache.karaf.shell.console.OsgiCommandSupport;
 import org.onlab.onos.ApplicationId;
 import org.onlab.onos.CoreService;
+import org.onlab.onos.net.Annotations;
 import org.onlab.osgi.DefaultServiceDirectory;
 import org.onlab.osgi.ServiceNotFoundException;
 
@@ -76,6 +79,34 @@
     }
 
     /**
+     * Produces a string image of the specified key/value annotations.
+     *
+     * @param annotations key/value annotations
+     * @return string image with ", k1=v1, k2=v2, ..." pairs
+     */
+    public static String annotations(Annotations annotations) {
+        StringBuilder sb = new StringBuilder();
+        for (String key : annotations.keys()) {
+            sb.append(", ").append(key).append('=').append(annotations.value(key));
+        }
+        return sb.toString();
+    }
+
+    /**
+     * Produces a JSON object from the specified key/value annotations.
+     *
+     * @param annotations key/value annotations
+     * @return JSON object
+     */
+    public static ObjectNode annotations(ObjectMapper mapper, Annotations annotations) {
+        ObjectNode result = mapper.createObjectNode();
+        for (String key : annotations.keys()) {
+            result.put(key, annotations.value(key));
+        }
+        return result;
+    }
+
+    /**
      * Executes this command.
      */
     protected abstract void execute();
diff --git a/cli/src/main/java/org/onlab/onos/cli/net/DevicePortsListCommand.java b/cli/src/main/java/org/onlab/onos/cli/net/DevicePortsListCommand.java
index f2c280e..3be8e5c 100644
--- a/cli/src/main/java/org/onlab/onos/cli/net/DevicePortsListCommand.java
+++ b/cli/src/main/java/org/onlab/onos/cli/net/DevicePortsListCommand.java
@@ -43,7 +43,7 @@
          description = "Lists all ports or all ports of a device")
 public class DevicePortsListCommand extends DevicesListCommand {
 
-    private static final String FMT = "  port=%s, state=%s";
+    private static final String FMT = "  port=%s, state=%s%s";
 
     @Option(name = "-e", aliases = "--enabled", description = "Show only enabled ports",
             required = false, multiValued = false)
@@ -112,7 +112,8 @@
             if (isIncluded(port)) {
                 ports.add(mapper.createObjectNode()
                                   .put("port", port.number().toString())
-                                  .put("isEnabled", port.isEnabled()));
+                                  .put("isEnabled", port.isEnabled())
+                                  .set("annotations", annotations(mapper, port.annotations())));
             }
         }
         return result.put("device", device.id().toString()).set("ports", ports);
@@ -131,7 +132,8 @@
         Collections.sort(ports, Comparators.PORT_COMPARATOR);
         for (Port port : ports) {
             if (isIncluded(port)) {
-                print(FMT, port.number(), port.isEnabled() ? "enabled" : "disabled");
+                print(FMT, port.number(), port.isEnabled() ? "enabled" : "disabled",
+                      annotations(port.annotations()));
             }
         }
     }
diff --git a/cli/src/main/java/org/onlab/onos/cli/net/DevicesListCommand.java b/cli/src/main/java/org/onlab/onos/cli/net/DevicesListCommand.java
index 095e2c0..543954e 100644
--- a/cli/src/main/java/org/onlab/onos/cli/net/DevicesListCommand.java
+++ b/cli/src/main/java/org/onlab/onos/cli/net/DevicesListCommand.java
@@ -41,7 +41,7 @@
 public class DevicesListCommand extends AbstractShellCommand {
 
     private static final String FMT =
-            "id=%s, available=%s, role=%s, type=%s, mfr=%s, hw=%s, sw=%s, serial=%s";
+            "id=%s, available=%s, role=%s, type=%s, mfr=%s, hw=%s, sw=%s, serial=%s%s";
 
     @Override
     protected void execute() {
@@ -89,7 +89,8 @@
                     .put("mfr", device.manufacturer())
                     .put("hw", device.hwVersion())
                     .put("sw", device.swVersion())
-                    .put("serial", device.serialNumber());
+                    .put("serial", device.serialNumber())
+                    .set("annotations", annotations(mapper, device.annotations()));
         }
         return result;
     }
@@ -117,7 +118,7 @@
             print(FMT, device.id(), service.isAvailable(device.id()),
                   service.getRole(device.id()), device.type(),
                   device.manufacturer(), device.hwVersion(), device.swVersion(),
-                  device.serialNumber());
+                  device.serialNumber(), annotations(device.annotations()));
         }
     }
 
diff --git a/cli/src/main/java/org/onlab/onos/cli/net/HostsListCommand.java b/cli/src/main/java/org/onlab/onos/cli/net/HostsListCommand.java
index 2291f4e..0265116 100644
--- a/cli/src/main/java/org/onlab/onos/cli/net/HostsListCommand.java
+++ b/cli/src/main/java/org/onlab/onos/cli/net/HostsListCommand.java
@@ -42,7 +42,7 @@
 public class HostsListCommand extends AbstractShellCommand {
 
     private static final String FMT =
-            "id=%s, mac=%s, location=%s/%s, vlan=%s, ip(s)=%s";
+            "id=%s, mac=%s, location=%s/%s, vlan=%s, ip(s)=%s%s";
 
     @Override
     protected void execute() {
@@ -80,6 +80,7 @@
                 .put("vlan", host.vlan().toString());
         result.set("location", loc);
         result.set("ips", ips);
+        result.set("annotations", annotations(mapper, host.annotations()));
         return result;
     }
 
@@ -105,7 +106,8 @@
             print(FMT, host.id(), host.mac(),
                   host.location().deviceId(),
                   host.location().port(),
-                  host.vlan(), host.ipAddresses());
+                  host.vlan(), host.ipAddresses(),
+                  annotations(host.annotations()));
         }
     }
 }
diff --git a/cli/src/main/java/org/onlab/onos/cli/net/LinksListCommand.java b/cli/src/main/java/org/onlab/onos/cli/net/LinksListCommand.java
index e6867f6..7889dcf 100644
--- a/cli/src/main/java/org/onlab/onos/cli/net/LinksListCommand.java
+++ b/cli/src/main/java/org/onlab/onos/cli/net/LinksListCommand.java
@@ -38,7 +38,7 @@
          description = "Lists all infrastructure links")
 public class LinksListCommand extends AbstractShellCommand {
 
-    private static final String FMT = "src=%s/%s, dst=%s/%s, type=%s";
+    private static final String FMT = "src=%s/%s, dst=%s/%s, type=%s%s";
     private static final String COMPACT = "%s/%s-%s/%s";
 
     @Argument(index = 0, name = "uri", description = "Device ID",
@@ -85,6 +85,7 @@
         ObjectNode result = mapper.createObjectNode();
         result.set("src", json(mapper, link.src()));
         result.set("dst", json(mapper, link.dst()));
+        result.set("annotations", annotations(mapper, link.annotations()));
         return result;
     }
 
@@ -109,7 +110,8 @@
      */
     public static String linkString(Link link) {
         return String.format(FMT, link.src().deviceId(), link.src().port(),
-                             link.dst().deviceId(), link.dst().port(), link.type());
+                             link.dst().deviceId(), link.dst().port(), link.type(),
+                             annotations(link.annotations()));
     }
 
     /**