[WIP][ONOS-3722] Augment TableModel with Annotations Mechanism

Change-Id: I815ce0b0fde254dd730153c34905d9454f019d9a
diff --git a/apps/dhcp/app/src/main/java/org/onosproject/dhcp/impl/DhcpViewMessageHandler.java b/apps/dhcp/app/src/main/java/org/onosproject/dhcp/impl/DhcpViewMessageHandler.java
index 9ce65d5..1614985 100644
--- a/apps/dhcp/app/src/main/java/org/onosproject/dhcp/impl/DhcpViewMessageHandler.java
+++ b/apps/dhcp/app/src/main/java/org/onosproject/dhcp/impl/DhcpViewMessageHandler.java
@@ -57,6 +57,8 @@
     // handler for dhcp table requests
     private final class DataRequestHandler extends TableRequestHandler {
 
+        private static final String NO_ROWS_MESSAGE = "No mappings found";
+
         private DataRequestHandler() {
             super(DHCP_DATA_REQ, DHCP_DATA_RESP, DHCP);
         }
@@ -72,6 +74,11 @@
         }
 
         @Override
+        protected String noRowsMessage() {
+            return NO_ROWS_MESSAGE;
+        }
+
+        @Override
         protected void populateTable(TableModel tm, ObjectNode payload) {
             DhcpService dhcpService = AbstractShellCommand.get(DhcpService.class);
             Map<HostId, IpAssignment> allocationMap = dhcpService.listMapping();
diff --git a/apps/drivermatrix/src/main/java/org/onosproject/drivermatrix/DriverMatrixMessageHandler.java b/apps/drivermatrix/src/main/java/org/onosproject/drivermatrix/DriverMatrixMessageHandler.java
index a4e77f4..4a7decd 100644
--- a/apps/drivermatrix/src/main/java/org/onosproject/drivermatrix/DriverMatrixMessageHandler.java
+++ b/apps/drivermatrix/src/main/java/org/onosproject/drivermatrix/DriverMatrixMessageHandler.java
@@ -63,6 +63,8 @@
     // handler for sample table requests
     private final class SampleTableDataRequestHandler extends TableRequestHandler {
 
+        private static final String NO_ROWS_MESSAGE = "No data found";
+
         private SampleTableDataRequestHandler() {
             super(SAMPLE_TABLE_DATA_REQ, SAMPLE_TABLE_DATA_RESP, SAMPLE_TABLES);
         }
@@ -74,6 +76,11 @@
             return COLUMN_IDS;
         }
 
+        @Override
+        protected String noRowsMessage() {
+            return NO_ROWS_MESSAGE;
+        }
+
         // if required, override createTableModel() to set column formatters / comparators
 
         @Override
diff --git a/apps/faultmanagement/fmgui/src/main/java/org/onosproject/faultmanagement/alarms/gui/AlarmTableMessageHandler.java b/apps/faultmanagement/fmgui/src/main/java/org/onosproject/faultmanagement/alarms/gui/AlarmTableMessageHandler.java
index df475e2..e3538ad 100644
--- a/apps/faultmanagement/fmgui/src/main/java/org/onosproject/faultmanagement/alarms/gui/AlarmTableMessageHandler.java
+++ b/apps/faultmanagement/fmgui/src/main/java/org/onosproject/faultmanagement/alarms/gui/AlarmTableMessageHandler.java
@@ -74,6 +74,8 @@
     // handler for alarm table requests
     private final class AlarmTableDataRequestHandler extends TableRequestHandler {
 
+        private static final String NO_ROWS_MESSAGE = "No alarms found";
+
         private AlarmTableDataRequestHandler() {
             super(ALARM_TABLE_DATA_REQ, ALARM_TABLE_DATA_RESP, ALARM_TABLES);
         }
@@ -90,6 +92,11 @@
         }
 
         @Override
+        protected String noRowsMessage() {
+            return NO_ROWS_MESSAGE;
+        }
+
+        @Override
         protected TableModel createTableModel() {
             // if required, override createTableModel() to set column formatters / comparators
             TableModel tm = super.createTableModel();
diff --git a/core/api/src/main/java/org/onosproject/ui/table/TableModel.java b/core/api/src/main/java/org/onosproject/ui/table/TableModel.java
index d0fccb6..bf5d95d 100644
--- a/core/api/src/main/java/org/onosproject/ui/table/TableModel.java
+++ b/core/api/src/main/java/org/onosproject/ui/table/TableModel.java
@@ -22,6 +22,7 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
@@ -57,7 +58,7 @@
     private final Map<String, CellComparator> comparators = new HashMap<>();
     private final Map<String, CellFormatter> formatters = new HashMap<>();
     private final List<Row> rows = new ArrayList<>();
-
+    private final Map<String, Annot> annotations = new HashMap<>();
 
     /**
      * Constructs a table (devoid of data) with the given column IDs.
@@ -124,6 +125,28 @@
     }
 
     /**
+     * Inserts a new annotation.
+     *
+     * @param key key of annotation
+     * @param value value of annotation
+     */
+    public void addAnnotation(String key, Object value) {
+        Annot annot = new Annot(key, value);
+        annotations.put(key, annot);
+    }
+
+    /**
+     * Returns the annotations in this table.
+     *
+     * @return annotations
+     */
+    public Collection<Annot> getAnnotations() {
+        Collection<Annot> annots = new ArrayList<>(annotations.size());
+        annotations.forEach((k, v) -> annots.add(v));
+        return annots;
+    }
+
+    /**
      * Sets a cell comparator for the specified column.
      *
      * @param columnId column identifier
@@ -233,6 +256,53 @@
     }
 
     /**
+     * Model of an annotation.
+     */
+    public class Annot {
+        private final String key;
+        private final Object value;
+
+        /**
+         * Constructs an annotation with the given key and value.
+         *
+         * @param key the key
+         * @param value the value
+         */
+        public Annot(String key, Object value) {
+            this.key = key;
+            this.value = value;
+        }
+
+        /**
+         * Returns the annotation's key.
+         *
+         * @return key
+         */
+        public String key() {
+            return key;
+        }
+
+        /**
+         * Returns the annotation's value.
+         *
+         * @return value
+         */
+        public Object value() {
+            return value;
+        }
+
+        /**
+         * Returns the value as a string.
+         * This default implementation uses the value's toString() method.
+         *
+         * @return the value as a string
+         */
+        public String valueAsString() {
+            return value.toString();
+        }
+    }
+
+    /**
      * Model of a row.
      */
     public class Row {
diff --git a/core/api/src/main/java/org/onosproject/ui/table/TableRequestHandler.java b/core/api/src/main/java/org/onosproject/ui/table/TableRequestHandler.java
index b8d4857..43700db 100644
--- a/core/api/src/main/java/org/onosproject/ui/table/TableRequestHandler.java
+++ b/core/api/src/main/java/org/onosproject/ui/table/TableRequestHandler.java
@@ -25,6 +25,8 @@
  */
 public abstract class TableRequestHandler extends RequestHandler {
 
+    private static final String ANNOTS = "annots";
+    private static final String NO_ROWS_MSG_KEY = "no_rows_msg";
     private final String respType;
     private final String nodeName;
 
@@ -53,8 +55,11 @@
         String sortDir = JsonUtils.string(payload, "sortDir", "asc");
         tm.sort(sortCol, TableModel.sortDir(sortDir));
 
+        addTableConfigAnnotations(tm);
+
         ObjectNode rootNode = MAPPER.createObjectNode();
-        rootNode.set(nodeName, TableUtils.generateArrayNode(tm));
+        rootNode.set(nodeName, TableUtils.generateRowArrayNode(tm));
+        rootNode.set(ANNOTS, TableUtils.generateAnnotObjectNode(tm));
         sendMessage(respType, 0, rootNode);
     }
 
@@ -72,6 +77,15 @@
     }
 
     /**
+     * Adds all annotations to table model.
+     *
+     * @param tm a table model
+     */
+    protected void addTableConfigAnnotations(TableModel tm) {
+        tm.addAnnotation(NO_ROWS_MSG_KEY, noRowsMessage());
+    }
+
+    /**
      * Returns the default column ID to be used when one is not supplied in
      * the payload as the column on which to sort.
      * <p>
@@ -92,6 +106,15 @@
     protected abstract String[] getColumnIds();
 
     /**
+     * Subclasses should return the message to display in the table when there
+     * are no rows to display. For example, a host table might return
+     * "No hosts found".
+     *
+     * @return the message
+     */
+    protected abstract String noRowsMessage();
+
+    /**
      * Subclasses should populate the table model by adding
      * {@link TableModel.Row rows}.
      * <pre>
@@ -108,4 +131,4 @@
      * @param payload request payload
      */
     protected abstract void populateTable(TableModel tm, ObjectNode payload);
-}
+}
\ No newline at end of file
diff --git a/core/api/src/main/java/org/onosproject/ui/table/TableUtils.java b/core/api/src/main/java/org/onosproject/ui/table/TableUtils.java
index eb2dff7..14c6743 100644
--- a/core/api/src/main/java/org/onosproject/ui/table/TableUtils.java
+++ b/core/api/src/main/java/org/onosproject/ui/table/TableUtils.java
@@ -32,12 +32,12 @@
     private TableUtils() { }
 
     /**
-     * Generates a JSON array node from a table model.
+     * Generates a JSON array node from the rows of the given table model.
      *
      * @param tm the table model
-     * @return the array node representation
+     * @return the array node representation of rows
      */
-    public static ArrayNode generateArrayNode(TableModel tm) {
+    public static ArrayNode generateRowArrayNode(TableModel tm) {
         ArrayNode array = MAPPER.createArrayNode();
         for (TableModel.Row r : tm.getRows()) {
             array.add(toJsonNode(r, tm));
@@ -45,6 +45,20 @@
         return array;
     }
 
+    /**
+     * Generates a JSON object node from the annotations of the given table model.
+     *
+     * @param tm the table model
+     * @return the object node representation of the annotations
+     */
+    public static ObjectNode generateAnnotObjectNode(TableModel tm) {
+        ObjectNode node = MAPPER.createObjectNode();
+        for (TableModel.Annot a : tm.getAnnotations()) {
+            node.put(a.key(), a.valueAsString());
+        }
+        return node;
+    }
+
     private static JsonNode toJsonNode(TableModel.Row row, TableModel tm) {
         ObjectNode result = MAPPER.createObjectNode();
         String[] keys = tm.getColumnIds();
diff --git a/core/api/src/test/java/org/onosproject/ui/table/TableModelTest.java b/core/api/src/test/java/org/onosproject/ui/table/TableModelTest.java
index 7524bcb..8c79a05 100644
--- a/core/api/src/test/java/org/onosproject/ui/table/TableModelTest.java
+++ b/core/api/src/test/java/org/onosproject/ui/table/TableModelTest.java
@@ -21,6 +21,8 @@
 import org.onosproject.ui.table.cell.DefaultCellFormatter;
 import org.onosproject.ui.table.cell.HexFormatter;
 
+import java.util.Collection;
+
 import static org.junit.Assert.*;
 
 /**
@@ -122,7 +124,6 @@
         assertEquals("bad cell", true, row.get(BAR));
     }
 
-
     private static final String ONE = "one";
     private static final String TWO = "two";
     private static final String THREE = "three";
@@ -313,7 +314,6 @@
         assertEquals("null sort dir", SortDir.ASC, TableModel.sortDir(null));
     }
 
-
     @Test
     public void enumSort() {
         tm = new TableModel(FOO);
@@ -335,4 +335,83 @@
             assertEquals(UNEX_SORT + i, ordered[i], rows[i].get(FOO));
         }
     }
+
+    @Test
+    public void stringAnnotation() {
+        tm = new TableModel(FOO);
+        tm.addAnnotation(BAR, ZOO);
+        Collection<TableModel.Annot> annots = tm.getAnnotations();
+        assertEquals("wrong size", 1, annots.size());
+
+        TableModel.Annot annot = annots.iterator().next();
+        assertEquals("wrong key", BAR, annot.key());
+        assertEquals("wrong value", ZOO, annot.value());
+    }
+
+    private static final String K_INT = "int";
+    private static final String K_BOOL = "bool";
+    private static final String K_FLOAT = "float";
+    private static final String K_DOUBLE = "double";
+    private static final String K_ENUM = "enum";
+
+    private TableModel.Annot getAnnotation(Collection<TableModel.Annot> annots, String key) {
+        final TableModel.Annot[] annot = {null};
+        annots.forEach(a -> {
+            if (a.key().equals(key)) {
+                annot[0] = a;
+            }
+        });
+        return annot[0];
+    }
+
+    private void verifyCollectionContains(Collection<TableModel.Annot> annots,
+                                          String key, int i) {
+        TableModel.Annot a = getAnnotation(annots, key);
+        assertEquals("wrong int value", i, a.value());
+    }
+
+    private void verifyCollectionContains(Collection<TableModel.Annot> annots,
+                                          String key, boolean b) {
+        TableModel.Annot a = getAnnotation(annots, key);
+        assertEquals("wrong boolean value", b, a.value());
+    }
+
+    private void verifyCollectionContains(Collection<TableModel.Annot> annots,
+                                          String key, float f) {
+        TableModel.Annot a = getAnnotation(annots, key);
+        assertEquals("wrong float value", f, a.value());
+    }
+
+    private void verifyCollectionContains(Collection<TableModel.Annot> annots,
+                                          String key, double d) {
+        TableModel.Annot a = getAnnotation(annots, key);
+        assertEquals("wrong double value", d, a.value());
+    }
+
+    private void verifyCollectionContains(Collection<TableModel.Annot> annots,
+                                          String key, Enum<?> e) {
+        TableModel.Annot a = getAnnotation(annots, key);
+        assertEquals("wrong double value", e, a.value());
+    }
+
+    @Test
+    public void primitivesAnnotation() {
+        tm = new TableModel(FOO);
+        tm.addAnnotation(K_INT, 1);
+        tm.addAnnotation(K_BOOL, true);
+        tm.addAnnotation(K_FLOAT, 3.14f);
+        tm.addAnnotation(K_DOUBLE, 2.71828);
+        tm.addAnnotation(K_ENUM, StarWars.LUKE_SKYWALKER);
+
+        Collection<TableModel.Annot> annots = tm.getAnnotations();
+        assertEquals("wrong size", 5, annots.size());
+
+        verifyCollectionContains(annots, K_INT, 1);
+        verifyCollectionContains(annots, K_BOOL, true);
+        verifyCollectionContains(annots, K_FLOAT, 3.14f);
+        verifyCollectionContains(annots, K_DOUBLE, 2.71828);
+        verifyCollectionContains(annots, K_ENUM, StarWars.LUKE_SKYWALKER);
+    }
+
+    // TODO: add support for compound object value
 }
diff --git a/core/api/src/test/java/org/onosproject/ui/table/TableUtilsTest.java b/core/api/src/test/java/org/onosproject/ui/table/TableUtilsTest.java
index 4456dd3..66ac647 100644
--- a/core/api/src/test/java/org/onosproject/ui/table/TableUtilsTest.java
+++ b/core/api/src/test/java/org/onosproject/ui/table/TableUtilsTest.java
@@ -37,7 +37,7 @@
         tm.addRow().cell(FOO, 1).cell(BAR, 2);
         tm.addRow().cell(FOO, 3).cell(BAR, 4);
 
-        ArrayNode array = TableUtils.generateArrayNode(tm);
+        ArrayNode array = TableUtils.generateRowArrayNode(tm);
         Assert.assertEquals("wrong results", ARRAY_AS_STRING, array.toString());
     }
 
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/ApplicationViewMessageHandler.java b/web/gui/src/main/java/org/onosproject/ui/impl/ApplicationViewMessageHandler.java
index 77a5897..2e1e7a0 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/ApplicationViewMessageHandler.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/ApplicationViewMessageHandler.java
@@ -66,6 +66,8 @@
 
     // handler for application table requests
     private final class AppDataRequest extends TableRequestHandler {
+        private static final String NO_ROWS_MESSAGE = "No applications found";
+
         private AppDataRequest() {
             super(APP_DATA_REQ, APP_DATA_RESP, APPS);
         }
@@ -76,6 +78,11 @@
         }
 
         @Override
+        protected String noRowsMessage() {
+            return NO_ROWS_MESSAGE;
+        }
+
+        @Override
         protected void populateTable(TableModel tm, ObjectNode payload) {
             ApplicationService as = get(ApplicationService.class);
             for (Application app : as.getApplications()) {
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/ClusterViewMessageHandler.java b/web/gui/src/main/java/org/onosproject/ui/impl/ClusterViewMessageHandler.java
index da0eae0..8c6c1eb 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/ClusterViewMessageHandler.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/ClusterViewMessageHandler.java
@@ -60,6 +60,8 @@
 
     // handler for cluster table requests
     private final class ClusterDataRequest extends TableRequestHandler {
+        private static final String NO_ROWS_MESSAGE = "No cluster nodes found";
+
         private ClusterDataRequest() {
             super(CLUSTER_DATA_REQ, CLUSTER_DATA_RESP, CLUSTERS);
         }
@@ -70,6 +72,11 @@
         }
 
         @Override
+        protected String noRowsMessage() {
+            return NO_ROWS_MESSAGE;
+        }
+
+        @Override
         protected TableModel createTableModel() {
             TableModel tm = super.createTableModel();
             tm.setFormatter(UPDATED, new TimeFormatter());
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/DeviceViewMessageHandler.java b/web/gui/src/main/java/org/onosproject/ui/impl/DeviceViewMessageHandler.java
index a740a73..bbcbc91 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/DeviceViewMessageHandler.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/DeviceViewMessageHandler.java
@@ -127,6 +127,8 @@
 
     // handler for device table requests
     private final class DataRequestHandler extends TableRequestHandler {
+        private static final String NO_ROWS_MESSAGE = "No devices found";
+
         private DataRequestHandler() {
             super(DEV_DATA_REQ, DEV_DATA_RESP, DEVICES);
         }
@@ -137,6 +139,11 @@
         }
 
         @Override
+        protected String noRowsMessage() {
+            return NO_ROWS_MESSAGE;
+        }
+
+        @Override
         protected void populateTable(TableModel tm, ObjectNode payload) {
             DeviceService ds = get(DeviceService.class);
             MastershipService ms = get(MastershipService.class);
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/FlowViewMessageHandler.java b/web/gui/src/main/java/org/onosproject/ui/impl/FlowViewMessageHandler.java
index e7a29dd..5860a6d 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/FlowViewMessageHandler.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/FlowViewMessageHandler.java
@@ -75,6 +75,8 @@
     // handler for flow table requests
     private final class FlowDataRequest extends TableRequestHandler {
 
+        private static final String NO_ROWS_MESSAGE = "No flows found";
+
         private FlowDataRequest() {
             super(FLOW_DATA_REQ, FLOW_DATA_RESP, FLOWS);
         }
@@ -85,6 +87,11 @@
         }
 
         @Override
+        protected String noRowsMessage() {
+            return NO_ROWS_MESSAGE;
+        }
+
+        @Override
         protected TableModel createTableModel() {
             TableModel tm = super.createTableModel();
             tm.setFormatter(ID, HexLongFormatter.INSTANCE);
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/GroupViewMessageHandler.java b/web/gui/src/main/java/org/onosproject/ui/impl/GroupViewMessageHandler.java
index 5780e9f..ab61a6a 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/GroupViewMessageHandler.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/GroupViewMessageHandler.java
@@ -65,6 +65,8 @@
     // handler for group table requests
     private final class GroupDataRequest extends TableRequestHandler {
 
+        private static final String NO_ROWS_MESSAGE = "No groups found";
+
         private GroupDataRequest() {
             super(GROUP_DATA_REQ, GROUP_DATA_RESP, GROUPS);
         }
@@ -75,6 +77,12 @@
         }
 
         @Override
+        protected String noRowsMessage() {
+            // TODO: if devices with OF 1.0, should return not support message
+            return NO_ROWS_MESSAGE;
+        }
+
+        @Override
         protected TableModel createTableModel() {
             TableModel tm = super.createTableModel();
             tm.setFormatter(ID, HexFormatter.INSTANCE);
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/HostViewMessageHandler.java b/web/gui/src/main/java/org/onosproject/ui/impl/HostViewMessageHandler.java
index 12a2b66..47137ee 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/HostViewMessageHandler.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/HostViewMessageHandler.java
@@ -62,6 +62,8 @@
 
     // handler for host table requests
     private final class HostDataRequest extends TableRequestHandler {
+        private static final String NO_ROWS_MESSAGE = "No hosts found";
+
         private HostDataRequest() {
             super(HOST_DATA_REQ, HOST_DATA_RESP, HOSTS);
         }
@@ -72,6 +74,11 @@
         }
 
         @Override
+        protected String noRowsMessage() {
+            return NO_ROWS_MESSAGE;
+        }
+
+        @Override
         protected TableModel createTableModel() {
             TableModel tm = super.createTableModel();
             tm.setFormatter(LOCATION, HostLocationFormatter.INSTANCE);
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/IntentViewMessageHandler.java b/web/gui/src/main/java/org/onosproject/ui/impl/IntentViewMessageHandler.java
index bacb76d..7f9b5a9 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/IntentViewMessageHandler.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/IntentViewMessageHandler.java
@@ -71,6 +71,8 @@
 
     // handler for intent table requests
     private final class IntentDataRequest extends TableRequestHandler {
+        private static final String NO_ROWS_MESSAGE = "No intents found";
+
         private IntentDataRequest() {
             super(INTENT_DATA_REQ, INTENT_DATA_RESP, INTENTS);
         }
@@ -86,6 +88,11 @@
         }
 
         @Override
+        protected String noRowsMessage() {
+            return NO_ROWS_MESSAGE;
+        }
+
+        @Override
         protected TableModel createTableModel() {
             TableModel tm = super.createTableModel();
             tm.setFormatter(APP_ID, AppIdFormatter.INSTANCE);
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/LinkViewMessageHandler.java b/web/gui/src/main/java/org/onosproject/ui/impl/LinkViewMessageHandler.java
index 7de6d62..9b8750e 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/LinkViewMessageHandler.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/LinkViewMessageHandler.java
@@ -65,6 +65,8 @@
 
     // handler for link table requests
     private final class LinkDataRequest extends TableRequestHandler {
+        private static final String NO_ROWS_MESSAGE = "No links found";
+
         private LinkDataRequest() {
             super(LINK_DATA_REQ, LINK_DATA_RESP, LINKS);
         }
@@ -75,6 +77,11 @@
         }
 
         @Override
+        protected String noRowsMessage() {
+            return NO_ROWS_MESSAGE;
+        }
+
+        @Override
         protected String defaultColumnId() {
             return ONE;
         }
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/MeterViewMessageHandler.java b/web/gui/src/main/java/org/onosproject/ui/impl/MeterViewMessageHandler.java
index f48fc81..ceb643a 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/MeterViewMessageHandler.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/MeterViewMessageHandler.java
@@ -61,6 +61,8 @@
     // handler for meter table requests
     private final class MeterDataRequest extends TableRequestHandler {
 
+        private static final String NO_ROWS_MESSAGE = "No meters found";
+
         private MeterDataRequest() {
             super(METER_DATA_REQ, METER_DATA_RESP, METERS);
         }
@@ -71,6 +73,12 @@
         }
 
         @Override
+        protected String noRowsMessage() {
+            // TODO: if the device with OF 1.0, return not support message
+            return NO_ROWS_MESSAGE;
+        }
+
+        @Override
         protected TableModel createTableModel() {
             TableModel tm = super.createTableModel();
             tm.setFormatter(ID, HexLongFormatter.INSTANCE);
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/PortViewMessageHandler.java b/web/gui/src/main/java/org/onosproject/ui/impl/PortViewMessageHandler.java
index 9c2f211..c196642 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/PortViewMessageHandler.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/PortViewMessageHandler.java
@@ -62,6 +62,8 @@
     // handler for port table requests
     private final class PortDataRequest extends TableRequestHandler {
 
+        private static final String NO_ROWS_MESSAGE = "No ports found";
+
         private PortDataRequest() {
             super(PORT_DATA_REQ, PORT_DATA_RESP, PORTS);
         }
@@ -72,6 +74,11 @@
         }
 
         @Override
+        protected String noRowsMessage() {
+            return NO_ROWS_MESSAGE;
+        }
+
+        @Override
         protected TableModel createTableModel() {
             TableModel tm = super.createTableModel();
             tm.setFormatter(PKT_RX, NumberFormatter.INTEGER);
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/ProcessorViewMessageHandler.java b/web/gui/src/main/java/org/onosproject/ui/impl/ProcessorViewMessageHandler.java
index 00b221b..1353538 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/ProcessorViewMessageHandler.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/ProcessorViewMessageHandler.java
@@ -64,6 +64,8 @@
 
     // handler for packet processor table requests
     private final class ProcessorDataRequest extends TableRequestHandler {
+        private static final String NO_ROWS_MESSAGE = "No packet processors found";
+
         private ProcessorDataRequest() {
             super(PROCESSOR_DATA_REQ, PROCESSOR_DATA_RESP, PROCESSORS);
         }
@@ -74,6 +76,11 @@
         }
 
         @Override
+        protected String noRowsMessage() {
+            return NO_ROWS_MESSAGE;
+        }
+
+        @Override
         protected TableModel createTableModel() {
             TableModel tm = super.createTableModel();
             tm.setFormatter(AVG_MS, NumberFormatter.TO_5DP);
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/SettingsViewMessageHandler.java b/web/gui/src/main/java/org/onosproject/ui/impl/SettingsViewMessageHandler.java
index ed413fa..76171c0 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/SettingsViewMessageHandler.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/SettingsViewMessageHandler.java
@@ -53,6 +53,8 @@
 
     // handler for host table requests
     private final class SettingsRequest extends TableRequestHandler {
+        private static final String NO_ROWS_MESSAGE = "No settings found";
+
         private SettingsRequest() {
             super(DATA_REQUEST, DATA_RESPONSE, SETTINGS);
         }
@@ -63,6 +65,11 @@
         }
 
         @Override
+        protected String noRowsMessage() {
+            return NO_ROWS_MESSAGE;
+        }
+
+        @Override
         protected String defaultColumnId() {
             return COMPONENT;
         }
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/TunnelViewMessageHandler.java b/web/gui/src/main/java/org/onosproject/ui/impl/TunnelViewMessageHandler.java
index 1684818..2abc321 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/TunnelViewMessageHandler.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/TunnelViewMessageHandler.java
@@ -55,6 +55,8 @@
 
     private final class TunnelDataRequestHandler extends TableRequestHandler {
 
+        private static final String NO_ROWS_MESSAGE = "No tunnels found";
+
         public TunnelDataRequestHandler() {
             super(TUNNEL_DATA_REQ, TUNNEL_DATA_RESP, TUNNELS);
         }
@@ -65,6 +67,11 @@
         }
 
         @Override
+        protected String noRowsMessage() {
+            return NO_ROWS_MESSAGE;
+        }
+
+        @Override
         protected TableModel createTableModel() {
             TableModel tm = super.createTableModel();
             //TODO add more formater class so that we can get a more readable table