GUI -- Initial cut at a simple table/row/cell model to allow us more control over the comparison / formatting of cell values for GUI tables.

Change-Id: I1a163259f1e80b2765a92cda654ffe092c835e6d
diff --git a/core/api/src/main/java/org/onosproject/ui/table/CellFormatter.java b/core/api/src/main/java/org/onosproject/ui/table/CellFormatter.java
new file mode 100644
index 0000000..49a316d
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/ui/table/CellFormatter.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.onosproject.ui.table;
+
+/**
+ * Defines a formatter for cell values.
+ */
+public interface CellFormatter {
+
+    /**
+     * Formats the specified value into a string appropriate for displaying
+     * in a table cell.
+     *
+     * @param value the value
+     * @return the formatted string
+     */
+    String format(Object value);
+}
diff --git a/core/api/src/main/java/org/onosproject/ui/table/DefaultCellFormatter.java b/core/api/src/main/java/org/onosproject/ui/table/DefaultCellFormatter.java
new file mode 100644
index 0000000..4078673
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/ui/table/DefaultCellFormatter.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.onosproject.ui.table;
+
+/**
+ * A default cell formatter. Uses the object's toString() method.
+ */
+public class DefaultCellFormatter implements CellFormatter {
+    @Override
+    public String format(Object value) {
+        return value == null ? "" : value.toString();
+    }
+}
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
new file mode 100644
index 0000000..3ff78d9
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/ui/table/TableModel.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.ui.table;
+
+import com.google.common.collect.Sets;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * A model of table data.
+ */
+public class TableModel {
+
+    private static final CellFormatter DEF_FMT = new DefaultCellFormatter();
+
+    private final String[] columnIds;
+    private final Set<String> idSet;
+    private final Map<String, CellFormatter> formatters = new HashMap<>();
+    private final List<Row> rows = new ArrayList<>();
+
+
+    /**
+     * Constructs a table (devoid of data) with the given column IDs.
+     *
+     * @param columnIds column identifiers
+     */
+    public TableModel(String... columnIds) {
+        checkNotNull(columnIds, "columnIds cannot be null");
+        checkArgument(columnIds.length > 0, "must be at least one column");
+
+        idSet = Sets.newHashSet(columnIds);
+        if (idSet.size() != columnIds.length) {
+            throw new IllegalArgumentException("duplicate column ID(s) detected");
+        }
+
+        this.columnIds = Arrays.copyOf(columnIds, columnIds.length);
+    }
+
+    private void checkId(String id) {
+        checkNotNull(id, "must provide a column ID");
+        if (!idSet.contains(id)) {
+            throw new IllegalArgumentException("unknown column id: " + id);
+        }
+    }
+
+    /**
+     * Returns the number of rows in this table model.
+     *
+     * @return number of rows
+     */
+    public int rowCount() {
+        return rows.size();
+    }
+
+    /**
+     * Returns the number of columns in this table model.
+     *
+     * @return number of columns
+     */
+    public int columnCount() {
+        return columnIds.length;
+    }
+
+    /**
+     * Returns the {@link TableRow} representation of the rows in this table.
+     *
+     * @return formatted table rows
+     */
+    public TableRow[] getTableRows() {
+        return new TableRow[0];
+    }
+
+    /**
+     * Returns the raw {@link Row} representation of the rows in this table.
+     *
+     * @return raw table rows
+     */
+    public Row[] getRows() {
+        return rows.toArray(new Row[rows.size()]);
+    }
+
+    /**
+     * Sets a cell formatter for the specified column.
+     *
+     * @param columnId column identifier
+     * @param formatter formatter to use
+     */
+    public void setFormatter(String columnId, CellFormatter formatter) {
+        checkNotNull(formatter, "must provide a formatter");
+        checkId(columnId);
+        formatters.put(columnId, formatter);
+    }
+
+    /**
+     * Returns the cell formatter to use on values in the specified column.
+     *
+     * @param columnId column identifier
+     * @return an appropriate cell formatter
+     */
+    public CellFormatter getFormatter(String columnId) {
+        checkId(columnId);
+        CellFormatter fmt = formatters.get(columnId);
+        return fmt == null ? DEF_FMT : fmt;
+    }
+
+    /**
+     * Adds a row to the table model.
+     *
+     * @return the row, for chaining
+     */
+    public Row addRow() {
+        Row r = new Row();
+        rows.add(r);
+        return r;
+    }
+
+    /**
+     * Model of a row.
+     */
+    public class Row {
+        private final Map<String, Object> cells = new HashMap<>();
+
+        /**
+         * Sets the cell value for the given column of this row.
+         *
+         * @param columnId column identifier
+         * @param value value to set
+         * @return self, for chaining
+         */
+        public Row cell(String columnId, Object value) {
+            checkNotNull(value, "Must supply some value");
+            checkId(columnId);
+            cells.put(columnId, value);
+            return this;
+        }
+
+        /**
+         * Returns the value of the cell in the given column for this row.
+         *
+         * @param columnId column identifier
+         * @return cell value
+         */
+        public Object get(String columnId) {
+            return cells.get(columnId);
+        }
+    }
+}
diff --git a/core/api/src/test/java/org/onosproject/ui/table/DefaultCellFormatterTest.java b/core/api/src/test/java/org/onosproject/ui/table/DefaultCellFormatterTest.java
new file mode 100644
index 0000000..bbb0104
--- /dev/null
+++ b/core/api/src/test/java/org/onosproject/ui/table/DefaultCellFormatterTest.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.onosproject.ui.table;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Unit tests for {@link DefaultCellFormatter}.
+ */
+public class DefaultCellFormatterTest {
+
+    private static final String UNEX = "Unexpected result";
+    private static final String SOME_STRING = "SoMeStRiNg";
+
+    private static class TestClass {
+        @Override
+        public String toString() {
+            return SOME_STRING;
+        }
+    }
+
+    private CellFormatter fmt = new DefaultCellFormatter();
+
+    @Test
+    public void formatNull() {
+        assertEquals(UNEX, "", fmt.format(null));
+    }
+
+    @Test
+    public void formatInteger() {
+        assertEquals(UNEX, "3", fmt.format(3));
+    }
+
+    @Test
+    public void formatTrue() {
+        assertEquals(UNEX, "true", fmt.format(true));
+    }
+
+    @Test
+    public void formatFalse() {
+        assertEquals(UNEX, "false", fmt.format(false));
+    }
+
+    @Test
+    public void formatString() {
+        assertEquals(UNEX, "FOO", fmt.format("FOO"));
+    }
+
+    @Test
+    public void formatObject() {
+        assertEquals(UNEX, SOME_STRING, fmt.format(new TestClass()));
+    }
+}
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
new file mode 100644
index 0000000..c111597
--- /dev/null
+++ b/core/api/src/test/java/org/onosproject/ui/table/TableModelTest.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.ui.table;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Unit tests for {@link TableModel}.
+ */
+public class TableModelTest {
+
+    private static final String FOO = "foo";
+    private static final String BAR = "bar";
+    private static final String BAZ = "baz";
+    private static final String ZOO = "zoo";
+
+    private static class TestFmtr implements CellFormatter {
+        @Override
+        public String format(Object value) {
+            return "(" + value + ")";
+        }
+    }
+
+    private TableModel tm;
+    private TableRow[] rows;
+    private CellFormatter fmt;
+
+    @Test(expected = NullPointerException.class)
+    public void guardAgainstNull() {
+        tm = new TableModel(null);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void guardAgainstEmpty() {
+        tm = new TableModel();
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void guardAgainstDuplicateCols() {
+        tm = new TableModel(FOO, BAR, FOO);
+    }
+
+    @Test
+    public void basic() {
+        tm = new TableModel(FOO, BAR);
+        assertEquals("column count", 2, tm.columnCount());
+        assertEquals("row count", 0, tm.rowCount());
+
+        rows = tm.getTableRows();
+        assertEquals("row count alt", 0, rows.length);
+    }
+
+    @Test
+    public void defaultFormatter() {
+        tm = new TableModel(FOO);
+        fmt = tm.getFormatter(FOO);
+        assertTrue("Wrong formatter", fmt instanceof DefaultCellFormatter);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void formatterBadColumn() {
+        tm = new TableModel(FOO);
+        fmt = tm.getFormatter(BAR);
+    }
+
+    @Test
+    public void altFormatter() {
+        tm = new TableModel(FOO, BAR);
+        tm.setFormatter(BAR, new TestFmtr());
+
+        fmt = tm.getFormatter(FOO);
+        assertTrue("Wrong formatter", fmt instanceof DefaultCellFormatter);
+        assertEquals("Wrong result", "2", fmt.format(2));
+
+        fmt = tm.getFormatter(BAR);
+        assertTrue("Wrong formatter", fmt instanceof TestFmtr);
+        assertEquals("Wrong result", "(2)", fmt.format(2));
+    }
+
+    @Test
+    public void emptyRow() {
+        tm = new TableModel(FOO, BAR);
+        tm.addRow();
+        assertEquals("bad row count", 1, tm.rowCount());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void rowBadColumn() {
+        tm = new TableModel(FOO, BAR);
+        tm.addRow().cell(ZOO, 2);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void rowNullValue() {
+        tm = new TableModel(FOO, BAR);
+        tm.addRow().cell(FOO, null);
+    }
+
+    @Test
+    public void simpleRow() {
+        tm = new TableModel(FOO, BAR);
+        tm.addRow().cell(FOO, 3).cell(BAR, true);
+        assertEquals("bad row count", 1, tm.rowCount());
+        TableModel.Row r = tm.getRows()[0];
+        assertEquals("bad cell", 3, r.get(FOO));
+        assertEquals("bad cell", true, r.get(BAR));
+    }
+}