blob: 9ec2e3b0f295d14111046aec244f7c0bb4bdbde7 [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 Huntb0582492016-09-20 18:26:38 -070053 private static final String DESC = "desc";
54
Simon Hunt3d1b0652015-05-05 17:27:24 -070055 private static final CellComparator DEF_CMP = DefaultCellComparator.INSTANCE;
56 private static final CellFormatter DEF_FMT = DefaultCellFormatter.INSTANCE;
Simon Hunt051e9fa2016-01-19 15:54:22 -080057 private static final String EMPTY = "";
Simon Hunte9828152015-05-01 17:54:25 -070058
59 private final String[] columnIds;
60 private final Set<String> idSet;
Simon Hunt933b1a82015-05-04 19:07:24 -070061 private final Map<String, CellComparator> comparators = new HashMap<>();
Simon Hunte9828152015-05-01 17:54:25 -070062 private final Map<String, CellFormatter> formatters = new HashMap<>();
63 private final List<Row> rows = new ArrayList<>();
Jian Li69f66632016-01-15 12:27:42 -080064 private final Map<String, Annot> annotations = new HashMap<>();
Simon Hunte9828152015-05-01 17:54:25 -070065
66 /**
67 * Constructs a table (devoid of data) with the given column IDs.
68 *
69 * @param columnIds column identifiers
70 */
71 public TableModel(String... columnIds) {
72 checkNotNull(columnIds, "columnIds cannot be null");
73 checkArgument(columnIds.length > 0, "must be at least one column");
74
75 idSet = Sets.newHashSet(columnIds);
76 if (idSet.size() != columnIds.length) {
77 throw new IllegalArgumentException("duplicate column ID(s) detected");
78 }
79
80 this.columnIds = Arrays.copyOf(columnIds, columnIds.length);
81 }
82
83 private void checkId(String id) {
84 checkNotNull(id, "must provide a column ID");
85 if (!idSet.contains(id)) {
86 throw new IllegalArgumentException("unknown column id: " + id);
87 }
88 }
89
90 /**
91 * Returns the number of rows in this table model.
92 *
93 * @return number of rows
94 */
95 public int rowCount() {
96 return rows.size();
97 }
98
99 /**
100 * Returns the number of columns in this table model.
101 *
102 * @return number of columns
103 */
104 public int columnCount() {
105 return columnIds.length;
106 }
107
108 /**
Simon Hunt3d1b0652015-05-05 17:27:24 -0700109 * Returns the array of column IDs for this table model.
110 * <p>
111 * Implementation note: we are knowingly passing you a reference to
112 * our internal array to avoid copying. Don't mess with it. It's your
113 * table you'll break if you do!
Simon Hunte9828152015-05-01 17:54:25 -0700114 *
Simon Hunt3d1b0652015-05-05 17:27:24 -0700115 * @return the column identifiers
Simon Hunte9828152015-05-01 17:54:25 -0700116 */
Simon Hunt3d1b0652015-05-05 17:27:24 -0700117 public String[] getColumnIds() {
118 return columnIds;
Simon Hunte9828152015-05-01 17:54:25 -0700119 }
120
121 /**
122 * Returns the raw {@link Row} representation of the rows in this table.
123 *
124 * @return raw table rows
125 */
126 public Row[] getRows() {
127 return rows.toArray(new Row[rows.size()]);
128 }
129
130 /**
Jian Li69f66632016-01-15 12:27:42 -0800131 * Inserts a new annotation.
132 *
133 * @param key key of annotation
134 * @param value value of annotation
135 */
136 public void addAnnotation(String key, Object value) {
137 Annot annot = new Annot(key, value);
138 annotations.put(key, annot);
139 }
140
141 /**
142 * Returns the annotations in this table.
143 *
144 * @return annotations
145 */
146 public Collection<Annot> getAnnotations() {
147 Collection<Annot> annots = new ArrayList<>(annotations.size());
148 annotations.forEach((k, v) -> annots.add(v));
149 return annots;
150 }
151
152 /**
Simon Hunt933b1a82015-05-04 19:07:24 -0700153 * Sets a cell comparator for the specified column.
154 *
155 * @param columnId column identifier
156 * @param comparator comparator to use
157 */
158 public void setComparator(String columnId, CellComparator comparator) {
159 checkNotNull(comparator, "must provide a comparator");
160 checkId(columnId);
161 comparators.put(columnId, comparator);
162 }
163
164 /**
165 * Returns the cell comparator to use on values in the specified column.
166 *
167 * @param columnId column identifier
168 * @return an appropriate cell comparator
169 */
170 private CellComparator getComparator(String columnId) {
171 checkId(columnId);
172 CellComparator cmp = comparators.get(columnId);
173 return cmp == null ? DEF_CMP : cmp;
174 }
175
176 /**
Simon Hunte9828152015-05-01 17:54:25 -0700177 * Sets a cell formatter for the specified column.
178 *
179 * @param columnId column identifier
180 * @param formatter formatter to use
181 */
182 public void setFormatter(String columnId, CellFormatter formatter) {
183 checkNotNull(formatter, "must provide a formatter");
184 checkId(columnId);
185 formatters.put(columnId, formatter);
186 }
187
188 /**
189 * Returns the cell formatter to use on values in the specified column.
190 *
191 * @param columnId column identifier
192 * @return an appropriate cell formatter
193 */
194 public CellFormatter getFormatter(String columnId) {
195 checkId(columnId);
196 CellFormatter fmt = formatters.get(columnId);
197 return fmt == null ? DEF_FMT : fmt;
198 }
199
200 /**
201 * Adds a row to the table model.
202 *
203 * @return the row, for chaining
204 */
205 public Row addRow() {
206 Row r = new Row();
207 rows.add(r);
208 return r;
209 }
210
211 /**
Simon Hunt051e9fa2016-01-19 15:54:22 -0800212 * Sorts the table rows based on the specified columns, in the
213 * specified directions. The second column is optional, and can be
214 * disregarded by passing null into id2 and dir2.
Simon Hunt933b1a82015-05-04 19:07:24 -0700215 *
Simon Hunt051e9fa2016-01-19 15:54:22 -0800216 * @param id1 first column identifier
217 * @param dir1 first column sort direction
218 * @param id2 second column identifier (may be null)
219 * @param dir2 second column sort direction (may be null)
Simon Hunt933b1a82015-05-04 19:07:24 -0700220 */
Simon Hunt051e9fa2016-01-19 15:54:22 -0800221 public void sort(String id1, SortDir dir1, String id2, SortDir dir2) {
222 Collections.sort(rows, new RowComparator(id1, dir1, id2, dir2));
Simon Hunt933b1a82015-05-04 19:07:24 -0700223 }
224
Simon Huntb0582492016-09-20 18:26:38 -0700225 private boolean nullOrEmpty(String s) {
226 return s == null || EMPTY.equals(s.trim());
227 }
Simon Hunt933b1a82015-05-04 19:07:24 -0700228
229 /** Designates sorting direction. */
230 public enum SortDir {
231 /** Designates an ascending sort. */
232 ASC,
233 /** Designates a descending sort. */
234 DESC
235 }
236
Simon Hunt051e9fa2016-01-19 15:54:22 -0800237
Simon Hunt933b1a82015-05-04 19:07:24 -0700238 /**
239 * Row comparator.
240 */
241 private class RowComparator implements Comparator<Row> {
Simon Hunt051e9fa2016-01-19 15:54:22 -0800242 private final String id1;
243 private final SortDir dir1;
244 private final String id2;
245 private final SortDir dir2;
246 private final CellComparator cc1;
247 private final CellComparator cc2;
Simon Hunt933b1a82015-05-04 19:07:24 -0700248
249 /**
250 * Constructs a row comparator based on the specified
Simon Hunt051e9fa2016-01-19 15:54:22 -0800251 * column identifiers and sort directions. Note that id2 and dir2 may
252 * be null.
Simon Hunt933b1a82015-05-04 19:07:24 -0700253 *
Simon Hunt051e9fa2016-01-19 15:54:22 -0800254 * @param id1 first column identifier
255 * @param dir1 first column sort direction
256 * @param id2 second column identifier
257 * @param dir2 second column sort direction
Simon Hunt933b1a82015-05-04 19:07:24 -0700258 */
Simon Hunt051e9fa2016-01-19 15:54:22 -0800259 public RowComparator(String id1, SortDir dir1, String id2, SortDir dir2) {
260 this.id1 = id1;
261 this.dir1 = dir1;
262 this.id2 = id2;
263 this.dir2 = dir2;
264 cc1 = getComparator(id1);
265 cc2 = nullOrEmpty(id2) ? null : getComparator(id2);
Simon Hunt933b1a82015-05-04 19:07:24 -0700266 }
267
268 @Override
269 public int compare(Row a, Row b) {
Simon Hunt051e9fa2016-01-19 15:54:22 -0800270 Object cellA = a.get(id1);
271 Object cellB = b.get(id1);
272 int result = cc1.compare(cellA, cellB);
273 result = dir1 == SortDir.ASC ? result : -result;
274
275 if (result == 0 && cc2 != null) {
276 cellA = a.get(id2);
277 cellB = b.get(id2);
278 result = cc2.compare(cellA, cellB);
279 result = dir2 == SortDir.ASC ? result : -result;
280 }
281 return result;
Simon Hunt933b1a82015-05-04 19:07:24 -0700282 }
283 }
284
285 /**
Jian Li69f66632016-01-15 12:27:42 -0800286 * Model of an annotation.
287 */
288 public class Annot {
289 private final String key;
290 private final Object value;
291
292 /**
293 * Constructs an annotation with the given key and value.
294 *
295 * @param key the key
296 * @param value the value
297 */
298 public Annot(String key, Object value) {
299 this.key = key;
300 this.value = value;
301 }
302
303 /**
304 * Returns the annotation's key.
305 *
306 * @return key
307 */
308 public String key() {
309 return key;
310 }
311
312 /**
313 * Returns the annotation's value.
314 *
315 * @return value
316 */
317 public Object value() {
318 return value;
319 }
320
321 /**
322 * Returns the value as a string.
323 * This default implementation uses the value's toString() method.
324 *
325 * @return the value as a string
326 */
327 public String valueAsString() {
328 return value.toString();
329 }
330 }
331
332 /**
Simon Hunte9828152015-05-01 17:54:25 -0700333 * Model of a row.
334 */
335 public class Row {
336 private final Map<String, Object> cells = new HashMap<>();
337
338 /**
339 * Sets the cell value for the given column of this row.
340 *
341 * @param columnId column identifier
342 * @param value value to set
343 * @return self, for chaining
344 */
345 public Row cell(String columnId, Object value) {
Simon Hunte9828152015-05-01 17:54:25 -0700346 checkId(columnId);
347 cells.put(columnId, value);
348 return this;
349 }
350
351 /**
352 * Returns the value of the cell in the given column for this row.
353 *
354 * @param columnId column identifier
355 * @return cell value
356 */
357 public Object get(String columnId) {
358 return cells.get(columnId);
359 }
Simon Hunt3ee7f742015-05-05 10:18:20 -0700360
361 /**
362 * Returns the value of the cell as a string, using the
363 * formatter appropriate for the column.
364 *
365 * @param columnId column identifier
366 * @return formatted cell value
367 */
Simon Hunt3d1b0652015-05-05 17:27:24 -0700368 String getAsString(String columnId) {
Simon Hunt3ee7f742015-05-05 10:18:20 -0700369 return getFormatter(columnId).format(get(columnId));
370 }
Simon Hunt3d1b0652015-05-05 17:27:24 -0700371
372 /**
373 * Returns the row as an array of formatted strings.
374 *
375 * @return the formatted row data
376 */
377 public String[] getAsFormattedStrings() {
378 List<String> formatted = new ArrayList<>(columnCount());
379 for (String c : columnIds) {
380 formatted.add(getAsString(c));
381 }
382 return formatted.toArray(new String[formatted.size()]);
383 }
Simon Hunte9828152015-05-01 17:54:25 -0700384 }
Simon Hunt933b1a82015-05-04 19:07:24 -0700385
Simon Hunt933b1a82015-05-04 19:07:24 -0700386 /**
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}