Simon Hunt

GUI -- Implemented table sorting in our simple table/row/cell model

- Introduced CellComparator (with default implementation)

Change-Id: I125f52c2c1ca219746b0e506e8837e24fb149038
1 +/*
2 + * Copyright 2015 Open Networking Laboratory
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + */
17 +
18 +package org.onosproject.ui.table;
19 +
20 +/**
21 + * Defines a comparator for cell values.
22 + */
23 +public interface CellComparator {
24 +
25 + /**
26 + * Compares its two arguments for order. Returns a negative integer,
27 + * zero, or a positive integer as the first argument is less than, equal
28 + * to, or greater than the second.<p>
29 + *
30 + * Note that nulls are permitted, and should be sorted to the beginning
31 + * of an ascending sort; i.e. null is considered to be "smaller" than
32 + * non-null values.
33 + *
34 + * @see java.util.Comparator#compare(Object, Object)
35 + *
36 + * @param o1 the first object to be compared.
37 + * @param o2 the second object to be compared.
38 + * @return a negative integer, zero, or a positive integer as the
39 + * first argument is less than, equal to, or greater than the
40 + * second.
41 + * @throws ClassCastException if the arguments' types prevent them from
42 + * being compared by this comparator.
43 + */
44 + int compare(Object o1, Object o2);
45 +
46 +}
1 +/*
2 + * Copyright 2015 Open Networking Laboratory
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + */
17 +
18 +package org.onosproject.ui.table;
19 +
20 +/**
21 + * A default cell comparator. Implements a lexicographical compare function
22 + * (i.e. string sorting). Uses the objects' toString() method and then
23 + * compares the resulting strings. Note that null values are acceptable and
24 + * are considered "smaller" than any non-null value.
25 + */
26 +public class DefaultCellComparator implements CellComparator {
27 + @Override
28 + public int compare(Object o1, Object o2) {
29 + if (o1 == null && o2 == null) {
30 + return 0; // o1 == o2
31 + }
32 + if (o1 == null) {
33 + return -1; // o1 < o2
34 + }
35 + if (o2 == null) {
36 + return 1; // o1 > o2
37 + }
38 + return o1.toString().compareTo(o2.toString());
39 + }
40 +}
...@@ -20,6 +20,8 @@ import com.google.common.collect.Sets; ...@@ -20,6 +20,8 @@ import com.google.common.collect.Sets;
20 20
21 import java.util.ArrayList; 21 import java.util.ArrayList;
22 import java.util.Arrays; 22 import java.util.Arrays;
23 +import java.util.Collections;
24 +import java.util.Comparator;
23 import java.util.HashMap; 25 import java.util.HashMap;
24 import java.util.List; 26 import java.util.List;
25 import java.util.Map; 27 import java.util.Map;
...@@ -29,14 +31,25 @@ import static com.google.common.base.Preconditions.checkArgument; ...@@ -29,14 +31,25 @@ import static com.google.common.base.Preconditions.checkArgument;
29 import static com.google.common.base.Preconditions.checkNotNull; 31 import static com.google.common.base.Preconditions.checkNotNull;
30 32
31 /** 33 /**
32 - * A model of table data. 34 + * A simple model of table data.
35 + * <p>
36 + * Note that this is not a full MVC type model; the expected usage pattern
37 + * is to create an empty table, add rows (by consulting the business model),
38 + * sort rows (based on client request parameters), and finally produce the
39 + * sorted list of rows.
40 + * <p>
41 + * The table also provides a mechanism for defining how cell values for a
42 + * particular column should be formatted into strings, to help facilitate
43 + * the encoding of the table data into a JSON structure.
33 */ 44 */
34 public class TableModel { 45 public class TableModel {
35 46
47 + private static final CellComparator DEF_CMP = new DefaultCellComparator();
36 private static final CellFormatter DEF_FMT = new DefaultCellFormatter(); 48 private static final CellFormatter DEF_FMT = new DefaultCellFormatter();
37 49
38 private final String[] columnIds; 50 private final String[] columnIds;
39 private final Set<String> idSet; 51 private final Set<String> idSet;
52 + private final Map<String, CellComparator> comparators = new HashMap<>();
40 private final Map<String, CellFormatter> formatters = new HashMap<>(); 53 private final Map<String, CellFormatter> formatters = new HashMap<>();
41 private final List<Row> rows = new ArrayList<>(); 54 private final List<Row> rows = new ArrayList<>();
42 55
...@@ -88,6 +101,7 @@ public class TableModel { ...@@ -88,6 +101,7 @@ public class TableModel {
88 * 101 *
89 * @return formatted table rows 102 * @return formatted table rows
90 */ 103 */
104 + // TODO: still need to decide if we need this
91 public TableRow[] getTableRows() { 105 public TableRow[] getTableRows() {
92 return new TableRow[0]; 106 return new TableRow[0];
93 } 107 }
...@@ -97,11 +111,36 @@ public class TableModel { ...@@ -97,11 +111,36 @@ public class TableModel {
97 * 111 *
98 * @return raw table rows 112 * @return raw table rows
99 */ 113 */
114 + // TODO: still need to decide if we should expose this
100 public Row[] getRows() { 115 public Row[] getRows() {
101 return rows.toArray(new Row[rows.size()]); 116 return rows.toArray(new Row[rows.size()]);
102 } 117 }
103 118
104 /** 119 /**
120 + * Sets a cell comparator for the specified column.
121 + *
122 + * @param columnId column identifier
123 + * @param comparator comparator to use
124 + */
125 + public void setComparator(String columnId, CellComparator comparator) {
126 + checkNotNull(comparator, "must provide a comparator");
127 + checkId(columnId);
128 + comparators.put(columnId, comparator);
129 + }
130 +
131 + /**
132 + * Returns the cell comparator to use on values in the specified column.
133 + *
134 + * @param columnId column identifier
135 + * @return an appropriate cell comparator
136 + */
137 + private CellComparator getComparator(String columnId) {
138 + checkId(columnId);
139 + CellComparator cmp = comparators.get(columnId);
140 + return cmp == null ? DEF_CMP : cmp;
141 + }
142 +
143 + /**
105 * Sets a cell formatter for the specified column. 144 * Sets a cell formatter for the specified column.
106 * 145 *
107 * @param columnId column identifier 146 * @param columnId column identifier
...@@ -137,6 +176,56 @@ public class TableModel { ...@@ -137,6 +176,56 @@ public class TableModel {
137 } 176 }
138 177
139 /** 178 /**
179 + * Sorts the table rows based on the specified column, in the
180 + * specified direction.
181 + *
182 + * @param columnId column identifier
183 + * @param dir sort direction
184 + */
185 + public void sort(String columnId, SortDir dir) {
186 + Collections.sort(rows, new RowComparator(columnId, dir));
187 + }
188 +
189 +
190 + /** Designates sorting direction. */
191 + public enum SortDir {
192 + /** Designates an ascending sort. */
193 + ASC,
194 + /** Designates a descending sort. */
195 + DESC
196 + }
197 +
198 + /**
199 + * Row comparator.
200 + */
201 + private class RowComparator implements Comparator<Row> {
202 + private final String columnId;
203 + private final SortDir dir;
204 + private final CellComparator cellComparator;
205 +
206 + /**
207 + * Constructs a row comparator based on the specified
208 + * column identifier and sort direction.
209 + *
210 + * @param columnId column identifier
211 + * @param dir sort direction
212 + */
213 + public RowComparator(String columnId, SortDir dir) {
214 + this.columnId = columnId;
215 + this.dir = dir;
216 + cellComparator = getComparator(columnId);
217 + }
218 +
219 + @Override
220 + public int compare(Row a, Row b) {
221 + Object cellA = a.get(columnId);
222 + Object cellB = b.get(columnId);
223 + int result = cellComparator.compare(cellA, cellB);
224 + return dir == SortDir.ASC ? result : -result;
225 + }
226 + }
227 +
228 + /**
140 * Model of a row. 229 * Model of a row.
141 */ 230 */
142 public class Row { 231 public class Row {
...@@ -166,4 +255,20 @@ public class TableModel { ...@@ -166,4 +255,20 @@ public class TableModel {
166 return cells.get(columnId); 255 return cells.get(columnId);
167 } 256 }
168 } 257 }
258 +
259 + private static final String DESC = "desc";
260 +
261 + /**
262 + * Returns the appropriate sort direction for the given string.
263 + * <p>
264 + * The expected strings are "asc" for {@link SortDir#ASC ascending} and
265 + * "desc" for {@link SortDir#DESC descending}. Any other value will
266 + * default to ascending.
267 + *
268 + * @param s sort direction string encoding
269 + * @return sort direction
270 + */
271 + public static SortDir sortDir(String s) {
272 + return !DESC.equals(s) ? SortDir.ASC : SortDir.DESC;
273 + }
169 } 274 }
......
1 +/*
2 + * Copyright 2015 Open Networking Laboratory
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + */
17 +
18 +package org.onosproject.ui.table;
19 +
20 +import org.junit.Test;
21 +
22 +import static org.junit.Assert.assertTrue;
23 +
24 +/**
25 + * Unit tests for {@link DefaultCellComparator}.
26 + */
27 +public class DefaultCellComparatorTest {
28 +
29 + private static class TestClass {
30 + @Override
31 + public String toString() {
32 + return SOME;
33 + }
34 + }
35 +
36 + private static final String SOME = "SoMeStRiNg";
37 + private static final String OTHER = "OtherSTRING";
38 + private static final int NUMBER = 42;
39 + private static final TestClass OBJECT = new TestClass();
40 +
41 + private CellComparator cmp = new DefaultCellComparator();
42 +
43 + @Test
44 + public void sameString() {
45 + assertTrue("same string", cmp.compare(SOME, SOME) == 0);
46 + }
47 +
48 + @Test
49 + public void someVsOther() {
50 + assertTrue("some vs other", cmp.compare(SOME, OTHER) > 0);
51 + }
52 +
53 + @Test
54 + public void otherVsSome() {
55 + assertTrue("other vs some", cmp.compare(OTHER, SOME) < 0);
56 + }
57 +
58 + @Test
59 + public void someVsObject() {
60 + assertTrue("some vs object", cmp.compare(SOME, OBJECT) == 0);
61 + }
62 +
63 + @Test
64 + public void otherVsObject() {
65 + assertTrue("other vs object", cmp.compare(OTHER, OBJECT) < 0);
66 + }
67 +
68 + @Test
69 + public void otherVsNumber() {
70 + assertTrue("other vs 42", cmp.compare(OTHER, NUMBER) > 0);
71 + }
72 +
73 + @Test
74 + public void someVsNull() {
75 + assertTrue("some vs null", cmp.compare(SOME, null) > 0);
76 + }
77 +
78 + @Test
79 + public void nullVsSome() {
80 + assertTrue("null vs some", cmp.compare(null, SOME) < 0);
81 + }
82 +
83 +}
...@@ -17,20 +17,30 @@ ...@@ -17,20 +17,30 @@
17 package org.onosproject.ui.table; 17 package org.onosproject.ui.table;
18 18
19 import org.junit.Test; 19 import org.junit.Test;
20 +import org.onosproject.ui.table.TableModel.SortDir;
20 21
21 -import static org.junit.Assert.assertEquals; 22 +import static org.junit.Assert.*;
22 -import static org.junit.Assert.assertTrue;
23 23
24 /** 24 /**
25 * Unit tests for {@link TableModel}. 25 * Unit tests for {@link TableModel}.
26 */ 26 */
27 public class TableModelTest { 27 public class TableModelTest {
28 28
29 + private static final String UNEX_SORT_ORDER = "unexpected sort: index ";
30 +
29 private static final String FOO = "foo"; 31 private static final String FOO = "foo";
30 private static final String BAR = "bar"; 32 private static final String BAR = "bar";
31 - private static final String BAZ = "baz";
32 private static final String ZOO = "zoo"; 33 private static final String ZOO = "zoo";
33 34
35 + private static class TestCmpr implements CellComparator {
36 + @Override
37 + public int compare(Object o1, Object o2) {
38 + int i1 = (int) o1;
39 + int i2 = (int) o2;
40 + return i1 - i2;
41 + }
42 + }
43 +
34 private static class TestFmtr implements CellFormatter { 44 private static class TestFmtr implements CellFormatter {
35 @Override 45 @Override
36 public String format(Object value) { 46 public String format(Object value) {
...@@ -39,7 +49,9 @@ public class TableModelTest { ...@@ -39,7 +49,9 @@ public class TableModelTest {
39 } 49 }
40 50
41 private TableModel tm; 51 private TableModel tm;
42 - private TableRow[] rows; 52 + private TableModel.Row[] rows;
53 + private TableModel.Row row;
54 + private TableRow[] tableRows;
43 private CellFormatter fmt; 55 private CellFormatter fmt;
44 56
45 @Test(expected = NullPointerException.class) 57 @Test(expected = NullPointerException.class)
...@@ -63,8 +75,8 @@ public class TableModelTest { ...@@ -63,8 +75,8 @@ public class TableModelTest {
63 assertEquals("column count", 2, tm.columnCount()); 75 assertEquals("column count", 2, tm.columnCount());
64 assertEquals("row count", 0, tm.rowCount()); 76 assertEquals("row count", 0, tm.rowCount());
65 77
66 - rows = tm.getTableRows(); 78 + tableRows = tm.getTableRows();
67 - assertEquals("row count alt", 0, rows.length); 79 + assertEquals("row count alt", 0, tableRows.length);
68 } 80 }
69 81
70 @Test 82 @Test
...@@ -118,8 +130,147 @@ public class TableModelTest { ...@@ -118,8 +130,147 @@ public class TableModelTest {
118 tm = new TableModel(FOO, BAR); 130 tm = new TableModel(FOO, BAR);
119 tm.addRow().cell(FOO, 3).cell(BAR, true); 131 tm.addRow().cell(FOO, 3).cell(BAR, true);
120 assertEquals("bad row count", 1, tm.rowCount()); 132 assertEquals("bad row count", 1, tm.rowCount());
121 - TableModel.Row r = tm.getRows()[0]; 133 + row = tm.getRows()[0];
122 - assertEquals("bad cell", 3, r.get(FOO)); 134 + assertEquals("bad cell", 3, row.get(FOO));
123 - assertEquals("bad cell", true, r.get(BAR)); 135 + assertEquals("bad cell", true, row.get(BAR));
136 + }
137 +
138 +
139 + private static final String ONE = "one";
140 + private static final String TWO = "two";
141 + private static final String THREE = "three";
142 + private static final String FOUR = "four";
143 + private static final String ELEVEN = "eleven";
144 + private static final String TWELVE = "twelve";
145 + private static final String TWENTY = "twenty";
146 + private static final String THIRTY = "thirty";
147 +
148 + private static final String[] NAMES = {
149 + FOUR,
150 + THREE,
151 + TWO,
152 + ONE,
153 + ELEVEN,
154 + TWELVE,
155 + THIRTY,
156 + TWENTY,
157 + };
158 + private static final String[] SORTED_NAMES = {
159 + ELEVEN,
160 + FOUR,
161 + ONE,
162 + THIRTY,
163 + THREE,
164 + TWELVE,
165 + TWENTY,
166 + TWO,
167 + };
168 +
169 + private static final int[] NUMBERS = {
170 + 4, 3, 2, 1, 11, 12, 30, 20
171 + };
172 +
173 + private static final int[] SORTED_NUMBERS = {
174 + 1, 2, 3, 4, 11, 12, 20, 30
175 + };
176 +
177 + @Test
178 + public void verifyTestData() {
179 + // not a unit test per se, but will fail if we don't keep
180 + // the three test arrays in sync
181 + int nalen = NAMES.length;
182 + int snlen = SORTED_NAMES.length;
183 + int nulen = NUMBERS.length;
184 +
185 + if (nalen != snlen || nalen != nulen) {
186 + fail("test data array size discrepancy");
187 + }
124 } 188 }
189 +
190 + private void initUnsortedTable() {
191 + tm = new TableModel(FOO, BAR);
192 + for (int i = 0; i < NAMES.length; i++) {
193 + tm.addRow().cell(FOO, NAMES[i]).cell(BAR, NUMBERS[i]);
194 + }
195 + }
196 +
197 + @Test
198 + public void tableStringSort() {
199 + initUnsortedTable();
200 +
201 + // sort by name
202 + tm.sort(FOO, SortDir.ASC);
203 +
204 + // verify results
205 + rows = tm.getRows();
206 + int nr = rows.length;
207 + assertEquals("row count", NAMES.length, nr);
208 + for (int i = 0; i < nr; i++) {
209 + assertEquals(UNEX_SORT_ORDER + i, SORTED_NAMES[i], rows[i].get(FOO));
210 + }
211 +
212 + // now the other way
213 + tm.sort(FOO, SortDir.DESC);
214 +
215 + // verify results
216 + rows = tm.getRows();
217 + nr = rows.length;
218 + assertEquals("row count", NAMES.length, nr);
219 + for (int i = 0; i < nr; i++) {
220 + assertEquals(UNEX_SORT_ORDER + i,
221 + SORTED_NAMES[nr - 1 - i], rows[i].get(FOO));
222 + }
223 + }
224 +
225 + @Test
226 + public void tableNumberSort() {
227 + initUnsortedTable();
228 +
229 + // first, tell the table to use an integer-based comparator
230 + tm.setComparator(BAR, new TestCmpr());
231 +
232 + // sort by number
233 + tm.sort(BAR, SortDir.ASC);
234 +
235 + // verify results
236 + rows = tm.getRows();
237 + int nr = rows.length;
238 + assertEquals("row count", NUMBERS.length, nr);
239 + for (int i = 0; i < nr; i++) {
240 + assertEquals(UNEX_SORT_ORDER + i, SORTED_NUMBERS[i], rows[i].get(BAR));
241 + }
242 +
243 + // now the other way
244 + tm.sort(BAR, SortDir.DESC);
245 +
246 + // verify results
247 + rows = tm.getRows();
248 + nr = rows.length;
249 + assertEquals("row count", NUMBERS.length, nr);
250 + for (int i = 0; i < nr; i++) {
251 + assertEquals(UNEX_SORT_ORDER + i,
252 + SORTED_NUMBERS[nr - 1 - i], rows[i].get(BAR));
253 + }
254 + }
255 +
256 + @Test
257 + public void sortDirAsc() {
258 + assertEquals("asc sort dir", SortDir.ASC, TableModel.sortDir("asc"));
259 + }
260 +
261 + @Test
262 + public void sortDirDesc() {
263 + assertEquals("desc sort dir", SortDir.DESC, TableModel.sortDir("desc"));
264 + }
265 +
266 + @Test
267 + public void sortDirOther() {
268 + assertEquals("other sort dir", SortDir.ASC, TableModel.sortDir("other"));
269 + }
270 +
271 + @Test
272 + public void sortDirNull() {
273 + assertEquals("null sort dir", SortDir.ASC, TableModel.sortDir(null));
274 + }
275 +
125 } 276 }
......