blob: ed5db2e83805628a3f07ddf2f13a2486b97e834d [file] [log] [blame]
Simon Hunte9828152015-05-01 17:54:25 -07001/*
Brian O'Connor5ab426f2016-04-09 01:19:45 -07002 * Copyright 2015-present Open Networking Laboratory
Simon Hunte9828152015-05-01 17:54:25 -07003 *
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
17package org.onosproject.ui.table;
18
19import com.google.common.collect.Sets;
Simon Hunt3ee7f742015-05-05 10:18:20 -070020import org.onosproject.ui.table.cell.DefaultCellComparator;
21import org.onosproject.ui.table.cell.DefaultCellFormatter;
Simon Hunte9828152015-05-01 17:54:25 -070022
23import java.util.ArrayList;
24import java.util.Arrays;
Jian Li69f66632016-01-15 12:27:42 -080025import java.util.Collection;
Simon Hunt933b1a82015-05-04 19:07:24 -070026import java.util.Collections;
27import java.util.Comparator;
Simon Hunte9828152015-05-01 17:54:25 -070028import java.util.HashMap;
29import java.util.List;
30import java.util.Map;
31import java.util.Set;
32
33import static com.google.common.base.Preconditions.checkArgument;
34import static com.google.common.base.Preconditions.checkNotNull;
35
36/**
Simon Hunt933b1a82015-05-04 19:07:24 -070037 * A simple model of table data.
38 * <p>
39 * Note that this is not a full MVC type model; the expected usage pattern
40 * is to create an empty table, add rows (by consulting the business model),
41 * sort rows (based on client request parameters), and finally produce the
42 * sorted list of rows.
43 * <p>
44 * The table also provides a mechanism for defining how cell values for a
45 * particular column should be formatted into strings, to help facilitate
46 * the encoding of the table data into a JSON structure.
Simon Hunt27bf0792015-05-07 10:50:29 -070047 * <p>
48 * Note that it is expected that all values for a particular column will
49 * be the same class.
Simon Hunte9828152015-05-01 17:54:25 -070050 */
51public class TableModel {
52
Simon Hunt3d1b0652015-05-05 17:27:24 -070053 private static final CellComparator DEF_CMP = DefaultCellComparator.INSTANCE;
54 private static final CellFormatter DEF_FMT = DefaultCellFormatter.INSTANCE;
Simon Hunt051e9fa2016-01-19 15:54:22 -080055 private static final String EMPTY = "";
Simon Hunte9828152015-05-01 17:54:25 -070056
57 private final String[] columnIds;
58 private final Set<String> idSet;
Simon Hunt933b1a82015-05-04 19:07:24 -070059 private final Map<String, CellComparator> comparators = new HashMap<>();
Simon Hunte9828152015-05-01 17:54:25 -070060 private final Map<String, CellFormatter> formatters = new HashMap<>();
61 private final List<Row> rows = new ArrayList<>();
Jian Li69f66632016-01-15 12:27:42 -080062 private final Map<String, Annot> annotations = new HashMap<>();
Simon Hunte9828152015-05-01 17:54:25 -070063
64 /**
65 * Constructs a table (devoid of data) with the given column IDs.
66 *
67 * @param columnIds column identifiers
68 */
69 public TableModel(String... columnIds) {
70 checkNotNull(columnIds, "columnIds cannot be null");
71 checkArgument(columnIds.length > 0, "must be at least one column");
72
73 idSet = Sets.newHashSet(columnIds);
74 if (idSet.size() != columnIds.length) {
75 throw new IllegalArgumentException("duplicate column ID(s) detected");
76 }
77
78 this.columnIds = Arrays.copyOf(columnIds, columnIds.length);
79 }
80
81 private void checkId(String id) {
82 checkNotNull(id, "must provide a column ID");
83 if (!idSet.contains(id)) {
84 throw new IllegalArgumentException("unknown column id: " + id);
85 }
86 }
87
88 /**
89 * Returns the number of rows in this table model.
90 *
91 * @return number of rows
92 */
93 public int rowCount() {
94 return rows.size();
95 }
96
97 /**
98 * Returns the number of columns in this table model.
99 *
100 * @return number of columns
101 */
102 public int columnCount() {
103 return columnIds.length;
104 }
105
106 /**
Simon Hunt3d1b0652015-05-05 17:27:24 -0700107 * Returns the array of column IDs for this table model.
108 * <p>
109 * Implementation note: we are knowingly passing you a reference to
110 * our internal array to avoid copying. Don't mess with it. It's your
111 * table you'll break if you do!
Simon Hunte9828152015-05-01 17:54:25 -0700112 *
Simon Hunt3d1b0652015-05-05 17:27:24 -0700113 * @return the column identifiers
Simon Hunte9828152015-05-01 17:54:25 -0700114 */
Simon Hunt3d1b0652015-05-05 17:27:24 -0700115 public String[] getColumnIds() {
116 return columnIds;
Simon Hunte9828152015-05-01 17:54:25 -0700117 }
118
119 /**
120 * Returns the raw {@link Row} representation of the rows in this table.
121 *
122 * @return raw table rows
123 */
124 public Row[] getRows() {
125 return rows.toArray(new Row[rows.size()]);
126 }
127
128 /**
Jian Li69f66632016-01-15 12:27:42 -0800129 * Inserts a new annotation.
130 *
131 * @param key key of annotation
132 * @param value value of annotation
133 */
134 public void addAnnotation(String key, Object value) {
135 Annot annot = new Annot(key, value);
136 annotations.put(key, annot);
137 }
138
139 /**
140 * Returns the annotations in this table.
141 *
142 * @return annotations
143 */
144 public Collection<Annot> getAnnotations() {
145 Collection<Annot> annots = new ArrayList<>(annotations.size());
146 annotations.forEach((k, v) -> annots.add(v));
147 return annots;
148 }
149
150 /**
Simon Hunt933b1a82015-05-04 19:07:24 -0700151 * Sets a cell comparator for the specified column.
152 *
153 * @param columnId column identifier
154 * @param comparator comparator to use
155 */
156 public void setComparator(String columnId, CellComparator comparator) {
157 checkNotNull(comparator, "must provide a comparator");
158 checkId(columnId);
159 comparators.put(columnId, comparator);
160 }
161
162 /**
163 * Returns the cell comparator to use on values in the specified column.
164 *
165 * @param columnId column identifier
166 * @return an appropriate cell comparator
167 */
168 private CellComparator getComparator(String columnId) {
169 checkId(columnId);
170 CellComparator cmp = comparators.get(columnId);
171 return cmp == null ? DEF_CMP : cmp;
172 }
173
174 /**
Simon Hunte9828152015-05-01 17:54:25 -0700175 * Sets a cell formatter for the specified column.
176 *
177 * @param columnId column identifier
178 * @param formatter formatter to use
179 */
180 public void setFormatter(String columnId, CellFormatter formatter) {
181 checkNotNull(formatter, "must provide a formatter");
182 checkId(columnId);
183 formatters.put(columnId, formatter);
184 }
185
186 /**
187 * Returns the cell formatter to use on values in the specified column.
188 *
189 * @param columnId column identifier
190 * @return an appropriate cell formatter
191 */
192 public CellFormatter getFormatter(String columnId) {
193 checkId(columnId);
194 CellFormatter fmt = formatters.get(columnId);
195 return fmt == null ? DEF_FMT : fmt;
196 }
197
198 /**
199 * Adds a row to the table model.
200 *
201 * @return the row, for chaining
202 */
203 public Row addRow() {
204 Row r = new Row();
205 rows.add(r);
206 return r;
207 }
208
209 /**
Simon Hunt051e9fa2016-01-19 15:54:22 -0800210 * Sorts the table rows based on the specified columns, in the
211 * specified directions. The second column is optional, and can be
212 * disregarded by passing null into id2 and dir2.
Simon Hunt933b1a82015-05-04 19:07:24 -0700213 *
Simon Hunt051e9fa2016-01-19 15:54:22 -0800214 * @param id1 first column identifier
215 * @param dir1 first column sort direction
216 * @param id2 second column identifier (may be null)
217 * @param dir2 second column sort direction (may be null)
Simon Hunt933b1a82015-05-04 19:07:24 -0700218 */
Simon Hunt051e9fa2016-01-19 15:54:22 -0800219 public void sort(String id1, SortDir dir1, String id2, SortDir dir2) {
220 Collections.sort(rows, new RowComparator(id1, dir1, id2, dir2));
Simon Hunt933b1a82015-05-04 19:07:24 -0700221 }
222
223
224 /** Designates sorting direction. */
225 public enum SortDir {
226 /** Designates an ascending sort. */
227 ASC,
228 /** Designates a descending sort. */
229 DESC
230 }
231
Simon Hunt051e9fa2016-01-19 15:54:22 -0800232 private boolean nullOrEmpty(String s) {
233 return s == null || EMPTY.equals(s.trim());
234 }
235
Simon Hunt933b1a82015-05-04 19:07:24 -0700236 /**
237 * Row comparator.
238 */
239 private class RowComparator implements Comparator<Row> {
Simon Hunt051e9fa2016-01-19 15:54:22 -0800240 private final String id1;
241 private final SortDir dir1;
242 private final String id2;
243 private final SortDir dir2;
244 private final CellComparator cc1;
245 private final CellComparator cc2;
Simon Hunt933b1a82015-05-04 19:07:24 -0700246
247 /**
248 * Constructs a row comparator based on the specified
Simon Hunt051e9fa2016-01-19 15:54:22 -0800249 * column identifiers and sort directions. Note that id2 and dir2 may
250 * be null.
Simon Hunt933b1a82015-05-04 19:07:24 -0700251 *
Simon Hunt051e9fa2016-01-19 15:54:22 -0800252 * @param id1 first column identifier
253 * @param dir1 first column sort direction
254 * @param id2 second column identifier
255 * @param dir2 second column sort direction
Simon Hunt933b1a82015-05-04 19:07:24 -0700256 */
Simon Hunt051e9fa2016-01-19 15:54:22 -0800257 public RowComparator(String id1, SortDir dir1, String id2, SortDir dir2) {
258 this.id1 = id1;
259 this.dir1 = dir1;
260 this.id2 = id2;
261 this.dir2 = dir2;
262 cc1 = getComparator(id1);
263 cc2 = nullOrEmpty(id2) ? null : getComparator(id2);
Simon Hunt933b1a82015-05-04 19:07:24 -0700264 }
265
266 @Override
267 public int compare(Row a, Row b) {
Simon Hunt051e9fa2016-01-19 15:54:22 -0800268 Object cellA = a.get(id1);
269 Object cellB = b.get(id1);
270 int result = cc1.compare(cellA, cellB);
271 result = dir1 == SortDir.ASC ? result : -result;
272
273 if (result == 0 && cc2 != null) {
274 cellA = a.get(id2);
275 cellB = b.get(id2);
276 result = cc2.compare(cellA, cellB);
277 result = dir2 == SortDir.ASC ? result : -result;
278 }
279 return result;
Simon Hunt933b1a82015-05-04 19:07:24 -0700280 }
281 }
282
283 /**
Jian Li69f66632016-01-15 12:27:42 -0800284 * Model of an annotation.
285 */
286 public class Annot {
287 private final String key;
288 private final Object value;
289
290 /**
291 * Constructs an annotation with the given key and value.
292 *
293 * @param key the key
294 * @param value the value
295 */
296 public Annot(String key, Object value) {
297 this.key = key;
298 this.value = value;
299 }
300
301 /**
302 * Returns the annotation's key.
303 *
304 * @return key
305 */
306 public String key() {
307 return key;
308 }
309
310 /**
311 * Returns the annotation's value.
312 *
313 * @return value
314 */
315 public Object value() {
316 return value;
317 }
318
319 /**
320 * Returns the value as a string.
321 * This default implementation uses the value's toString() method.
322 *
323 * @return the value as a string
324 */
325 public String valueAsString() {
326 return value.toString();
327 }
328 }
329
330 /**
Simon Hunte9828152015-05-01 17:54:25 -0700331 * Model of a row.
332 */
333 public class Row {
334 private final Map<String, Object> cells = new HashMap<>();
335
336 /**
337 * Sets the cell value for the given column of this row.
338 *
339 * @param columnId column identifier
340 * @param value value to set
341 * @return self, for chaining
342 */
343 public Row cell(String columnId, Object value) {
Simon Hunte9828152015-05-01 17:54:25 -0700344 checkId(columnId);
345 cells.put(columnId, value);
346 return this;
347 }
348
349 /**
350 * Returns the value of the cell in the given column for this row.
351 *
352 * @param columnId column identifier
353 * @return cell value
354 */
355 public Object get(String columnId) {
356 return cells.get(columnId);
357 }
Simon Hunt3ee7f742015-05-05 10:18:20 -0700358
359 /**
360 * Returns the value of the cell as a string, using the
361 * formatter appropriate for the column.
362 *
363 * @param columnId column identifier
364 * @return formatted cell value
365 */
Simon Hunt3d1b0652015-05-05 17:27:24 -0700366 String getAsString(String columnId) {
Simon Hunt3ee7f742015-05-05 10:18:20 -0700367 return getFormatter(columnId).format(get(columnId));
368 }
Simon Hunt3d1b0652015-05-05 17:27:24 -0700369
370 /**
371 * Returns the row as an array of formatted strings.
372 *
373 * @return the formatted row data
374 */
375 public String[] getAsFormattedStrings() {
376 List<String> formatted = new ArrayList<>(columnCount());
377 for (String c : columnIds) {
378 formatted.add(getAsString(c));
379 }
380 return formatted.toArray(new String[formatted.size()]);
381 }
Simon Hunte9828152015-05-01 17:54:25 -0700382 }
Simon Hunt933b1a82015-05-04 19:07:24 -0700383
384 private static final String DESC = "desc";
385
386 /**
387 * Returns the appropriate sort direction for the given string.
388 * <p>
389 * The expected strings are "asc" for {@link SortDir#ASC ascending} and
390 * "desc" for {@link SortDir#DESC descending}. Any other value will
391 * default to ascending.
392 *
393 * @param s sort direction string encoding
394 * @return sort direction
395 */
396 public static SortDir sortDir(String s) {
397 return !DESC.equals(s) ? SortDir.ASC : SortDir.DESC;
398 }
Simon Hunte9828152015-05-01 17:54:25 -0700399}