blob: bf5d95d65c89dbef8fa8ce223466eca94a48d7be [file] [log] [blame]
/*
* 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 org.onosproject.ui.table.cell.DefaultCellComparator;
import org.onosproject.ui.table.cell.DefaultCellFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
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 simple model of table data.
* <p>
* Note that this is not a full MVC type model; the expected usage pattern
* is to create an empty table, add rows (by consulting the business model),
* sort rows (based on client request parameters), and finally produce the
* sorted list of rows.
* <p>
* The table also provides a mechanism for defining how cell values for a
* particular column should be formatted into strings, to help facilitate
* the encoding of the table data into a JSON structure.
* <p>
* Note that it is expected that all values for a particular column will
* be the same class.
*/
public class TableModel {
private static final CellComparator DEF_CMP = DefaultCellComparator.INSTANCE;
private static final CellFormatter DEF_FMT = DefaultCellFormatter.INSTANCE;
private final String[] columnIds;
private final Set<String> idSet;
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.
*
* @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 array of column IDs for this table model.
* <p>
* Implementation note: we are knowingly passing you a reference to
* our internal array to avoid copying. Don't mess with it. It's your
* table you'll break if you do!
*
* @return the column identifiers
*/
public String[] getColumnIds() {
return columnIds;
}
/**
* 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()]);
}
/**
* 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
* @param comparator comparator to use
*/
public void setComparator(String columnId, CellComparator comparator) {
checkNotNull(comparator, "must provide a comparator");
checkId(columnId);
comparators.put(columnId, comparator);
}
/**
* Returns the cell comparator to use on values in the specified column.
*
* @param columnId column identifier
* @return an appropriate cell comparator
*/
private CellComparator getComparator(String columnId) {
checkId(columnId);
CellComparator cmp = comparators.get(columnId);
return cmp == null ? DEF_CMP : cmp;
}
/**
* 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;
}
/**
* Sorts the table rows based on the specified column, in the
* specified direction.
*
* @param columnId column identifier
* @param dir sort direction
*/
public void sort(String columnId, SortDir dir) {
Collections.sort(rows, new RowComparator(columnId, dir));
}
/** Designates sorting direction. */
public enum SortDir {
/** Designates an ascending sort. */
ASC,
/** Designates a descending sort. */
DESC
}
/**
* Row comparator.
*/
private class RowComparator implements Comparator<Row> {
private final String columnId;
private final SortDir dir;
private final CellComparator cellComparator;
/**
* Constructs a row comparator based on the specified
* column identifier and sort direction.
*
* @param columnId column identifier
* @param dir sort direction
*/
public RowComparator(String columnId, SortDir dir) {
this.columnId = columnId;
this.dir = dir;
cellComparator = getComparator(columnId);
}
@Override
public int compare(Row a, Row b) {
Object cellA = a.get(columnId);
Object cellB = b.get(columnId);
int result = cellComparator.compare(cellA, cellB);
return dir == SortDir.ASC ? result : -result;
}
}
/**
* 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 {
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) {
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);
}
/**
* Returns the value of the cell as a string, using the
* formatter appropriate for the column.
*
* @param columnId column identifier
* @return formatted cell value
*/
String getAsString(String columnId) {
return getFormatter(columnId).format(get(columnId));
}
/**
* Returns the row as an array of formatted strings.
*
* @return the formatted row data
*/
public String[] getAsFormattedStrings() {
List<String> formatted = new ArrayList<>(columnCount());
for (String c : columnIds) {
formatted.add(getAsString(c));
}
return formatted.toArray(new String[formatted.size()]);
}
}
private static final String DESC = "desc";
/**
* Returns the appropriate sort direction for the given string.
* <p>
* The expected strings are "asc" for {@link SortDir#ASC ascending} and
* "desc" for {@link SortDir#DESC descending}. Any other value will
* default to ascending.
*
* @param s sort direction string encoding
* @return sort direction
*/
public static SortDir sortDir(String s) {
return !DESC.equals(s) ? SortDir.ASC : SortDir.DESC;
}
}