/* * Copyright 2014 TWO SIGMA OPEN SOURCE, LLC * * 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 com.twosigma.beaker.table; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Set; import java.util.Arrays; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.TimeUnit; import com.twosigma.beaker.chart.Color; import com.twosigma.beaker.jvm.serialization.BasicObjectSerializer; import com.twosigma.beaker.jvm.serialization.BeakerObjectConverter; import com.twosigma.beaker.table.action.TableActionDetails; import com.twosigma.beaker.table.format.TableDisplayStringFormat; import com.twosigma.beaker.table.format.ValueStringFormat; import com.twosigma.beaker.table.highlight.TableDisplayCellHighlighter; import com.twosigma.beaker.table.highlight.ValueHighlighter; import com.twosigma.beaker.table.renderer.TableDisplayCellRenderer; import com.twosigma.beaker.widgets.BeakerxWidget; import com.twosigma.beaker.widgets.RunWidgetClosure; import static java.util.Arrays.asList; public class TableDisplay extends BeakerxWidget { public static final String VIEW_NAME_VALUE = "TableDisplayView"; public static final String MODEL_NAME_VALUE = "TableDisplayModel"; public static final String TABLE_DISPLAY_SUBTYPE = "TableDisplay"; public static final String LIST_OF_MAPS_SUBTYPE = "ListOfMaps"; public static final String MATRIX_SUBTYPE = "Matrix"; public static final String DICTIONARY_SUBTYPE = "Dictionary"; private final List<List<?>> values; private List<String> columns; private final List<String> classes; private String subtype; private TimeUnit stringFormatForTimes; private Map<ColumnType, TableDisplayStringFormat> stringFormatForType = new HashMap<>(); private Map<String, TableDisplayStringFormat> stringFormatForColumn = new HashMap<>(); private Map<ColumnType, TableDisplayCellRenderer> rendererForType = new HashMap<>(); private Map<String, TableDisplayCellRenderer> rendererForColumn = new HashMap<>(); private Map<ColumnType, TableDisplayAlignmentProvider> alignmentForType = new HashMap<>(); private Map<String, TableDisplayAlignmentProvider> alignmentForColumn = new HashMap<>(); private Map<String, Boolean> columnsFrozen = new HashMap<>(); private Map<String, Boolean> columnsFrozenRight = new HashMap<>(); private Map<String, Boolean> columnsVisible = new HashMap<>(); private List<String> columnOrder = new ArrayList<>(); private List<TableDisplayCellHighlighter> cellHighlighters = new ArrayList<>(); private List<List<String>> tooltips = new ArrayList<>(); private Integer dataFontSize; private Integer headerFontSize; private List<List<Color>> fontColor = new ArrayList<>(); private List<List<?>> filteredValues; private boolean headersVertical; private String hasIndex; private String timeZone; @Override public String getModelNameValue() { return MODEL_NAME_VALUE; } @Override public String getViewNameValue() { return VIEW_NAME_VALUE; } public TableDisplay(List<List<?>> v, List<String> co, List<String> cl) { super(); values = v; columns = co; classes = cl; subtype = TABLE_DISPLAY_SUBTYPE; openComm(); } public TableDisplay(Collection<Map<?, ?>> v) { this(v, new BasicObjectSerializer()); } public TableDisplay(Map<?,?>[] v) { this(new ArrayList<Map<?,?>>(Arrays.asList(v)), new BasicObjectSerializer()); } public TableDisplay(Collection<Map<?, ?>> v, BeakerObjectConverter serializer) { super(); values = new ArrayList<List<?>>(); columns = new ArrayList<String>(); classes = new ArrayList<String>(); subtype = LIST_OF_MAPS_SUBTYPE; // create columns for(Map<?,?> m : v) { Set<?> w = m.entrySet(); for (Object s : w) { Entry<?,?> e = (Entry<?, ?>) s; String c = e.getKey().toString(); if (!columns.contains(c)) { columns.add(c); String n = e.getValue()!=null ? e.getValue().getClass().getName() : "string"; classes.add(serializer.convertType(n)); } } } // now build values for(Map<?,?> m : v) { List<Object> vals = new ArrayList<Object>(); for (String cn : columns) { if (m.containsKey(cn)){ vals.add(getValueForSerializer( m.get(cn), serializer)); } else vals.add(null); } values.add(vals); } openComm(); } public TableDisplay(Map<?, ?> v) { super(); this.values = new ArrayList<List<?>>(); this.columns = Arrays.asList("Key", "Value"); this.classes = new ArrayList<String>(); this.subtype = DICTIONARY_SUBTYPE; Set<?> w = v.entrySet(); for (Object s : w) { Entry<?, ?> e = (Entry<?, ?>) s; values.add(asList(e.getKey().toString(), e.getValue())); } openComm(); } public static TableDisplay createTableDisplayForMap(Map<?, ?> v) { return new TableDisplay(v); } public TimeUnit getStringFormatForTimes() { return stringFormatForTimes; } public void setStringFormatForTimes(TimeUnit stringFormatForTimes) { this.stringFormatForTimes = stringFormatForTimes; } public Map<ColumnType, TableDisplayStringFormat> getStringFormatForType() { return stringFormatForType; } public void setStringFormatForType(ColumnType type, TableDisplayStringFormat format) { this.stringFormatForType.put(type, format); } public Map<String, TableDisplayStringFormat> getStringFormatForColumn() { return stringFormatForColumn; } public void setStringFormatForColumn(String column, TableDisplayStringFormat format) { this.stringFormatForColumn.put(column, format); } public void setStringFormatForColumn(String column, Object closure) { int colIndex = this.columns.indexOf(column); if (colIndex == -1) { throw new IllegalArgumentException("Column " + column + " doesn't exist"); } List<String> formattedValues = new ArrayList<>(); try { for (int row = 0; row < this.values.size(); row++) { Object value = this.values.get(row).get(colIndex); Object[] params = new Object[]{ value, row, colIndex, this }; formattedValues.add((String) runClosure(closure, params)); } } catch (Throwable e) { throw new IllegalArgumentException("Can not create format using closure.", e); } this.stringFormatForColumn.put(column, new ValueStringFormat(column, formattedValues)); } public Map<ColumnType, TableDisplayCellRenderer> getRendererForType() { return rendererForType; } public void setRendererForType(ColumnType type, TableDisplayCellRenderer renderer) { this.rendererForType.put(type, renderer); } public Map<String, TableDisplayCellRenderer> getRendererForColumn() { return rendererForColumn; } public void setRendererForColumn(String column, TableDisplayCellRenderer renderer) { this.rendererForColumn.put(column, renderer); } public Map<ColumnType, TableDisplayAlignmentProvider> getAlignmentForType() { return alignmentForType; } public void setAlignmentProviderForType(ColumnType type, TableDisplayAlignmentProvider alignmentProvider) { this.alignmentForType.put(type, alignmentProvider); } public Map<String, TableDisplayAlignmentProvider> getAlignmentForColumn() { return alignmentForColumn; } public void setAlignmentProviderForColumn(String column, TableDisplayAlignmentProvider alignmentProvider) { this.alignmentForColumn.put(column, alignmentProvider); } public Map<String, Boolean> getColumnsFrozen() { return columnsFrozen; } public void setColumnFrozen(String column, boolean frozen) { this.columnsFrozen.put(column, frozen); } public Map<String, Boolean> getColumnsFrozenRight() { return columnsFrozenRight; } public void setColumnFrozenRight(String column, boolean frozen) { this.columnsFrozenRight.put(column, frozen); } public Map<String, Boolean> getColumnsVisible() { return columnsVisible; } public void setColumnVisible(String column, boolean visible) { this.columnsVisible.put(column, visible); } public List<String> getColumnOrder() { return columnOrder; } public List<TableDisplayCellHighlighter> getCellHighlighters() { return cellHighlighters; } public void addCellHighlighter(TableDisplayCellHighlighter cellHighlighter) { this.cellHighlighters.add(cellHighlighter); } public void addCellHighlighter(Object closure) { Map<String, List<Color>> colors = new HashMap<>(); try { int rowSize = this.values.get(0).size(); for (int colInd = 0; colInd < rowSize; colInd++) { boolean hasHighlightedValues = false; List<Color> columnColors = new ArrayList<>(); for (int rowInd = 0; rowInd < this.values.size(); rowInd++) { Object[] params = new Object[]{rowInd, colInd, this}; Color color = (Color) runClosure(closure, params); if (color != null) { hasHighlightedValues = true; } columnColors.add(color); } if (hasHighlightedValues) { this.cellHighlighters.add(new ValueHighlighter(this.columns.get(colInd), columnColors)); } } } catch (Throwable e) { throw new IllegalArgumentException("Can not set cell highlighter using closure.", e); } } public void removeAllCellHighlighters() { this.cellHighlighters.clear(); } public void setColumnOrder(List<String> columnOrder) { this.columnOrder = columnOrder; } public void setToolTip(Object closure) { try { for (int rowInd = 0; rowInd < this.values.size(); rowInd++) { List<?> row = this.values.get(rowInd); List<String> rowToolTips = new ArrayList<>(); for (int colInd = 0; colInd < row.size(); colInd++) { Object[] params = new Object[]{rowInd, colInd, this}; rowToolTips.add((String) runClosure(closure, params)); } tooltips.add(rowToolTips); } } catch (Throwable e) { throw new IllegalArgumentException("Can not set tooltip using closure.", e); } } public List<List<String>> getTooltips() { return tooltips; } public Integer getDataFontSize() { return dataFontSize; } public void setDataFontSize(Integer dataFontSize) { this.dataFontSize = dataFontSize; } public Integer getHeaderFontSize() { return headerFontSize; } public void setHeaderFontSize(Integer headerFontSize) { this.headerFontSize = headerFontSize; } public List<List<Color>> getFontColor() { return fontColor; } public void setFontColorProvider(Object closure) { try { for (int rowInd = 0; rowInd < this.values.size(); rowInd++) { List<?> row = this.values.get(rowInd); List<Color> rowFontColors = new ArrayList<>(); for (int colInd = 0; colInd < row.size(); colInd++) { Object[] params = new Object[]{rowInd, colInd, this}; rowFontColors.add((Color) runClosure(closure, params)); } this.fontColor.add(rowFontColors); } } catch (Throwable e) { throw new IllegalArgumentException("Can not set font color using closure.", e); } } public void setRowFilter(Object closure) { List<List<?>> filteredValues = new ArrayList<>(); try { for (int rowInd = 0; rowInd < this.values.size(); rowInd++) { Object[] params = new Object[]{rowInd, this.values}; if((boolean)runClosure(closure, params)){ filteredValues.add(values.get(rowInd)); } } } catch (Throwable e) { throw new IllegalArgumentException("Can not set row filter using closure.", e); } this.filteredValues = filteredValues; } public void setHeadersVertical(boolean headersVertical){ this.headersVertical = headersVertical; } public Boolean getHeadersVertical() { return headersVertical; } public void setHasIndex(String hasIndex) { this.hasIndex = hasIndex; } public String getHasIndex() { return hasIndex; } public void setTimeZone(String timeZone) { this.timeZone = timeZone; } public String getTimeZone() { return timeZone; } public List<List<?>> getFilteredValues() { return filteredValues; } public static List<Map<String, Object>> getValuesAsRows(List<List<?>> values, List<String> columns) { List<Map<String, Object>> rows = new ArrayList<Map<String, Object>>(); if (columns != null && values != null) { for (List<?> value : values) { Map<String, Object> m = new HashMap<String, Object>(); for (int c = 0; c < columns.size(); c++) { if (value.size() > c) m.put(columns.get(c), value.get(c)); } rows.add(m); } } else { throw new IllegalArgumentException("Method 'getValuesAsRows' doesn't supported for this table"); } return rows; } public static List<List<?>> getValuesAsMatrix(List<List<?>> values) { return values; } public static Map<String, Object> getValuesAsDictionary(List<List<?>> values) { Map<String, Object> m = new HashMap<String, Object>(); for (List<?> l : values) { m.put(l.get(0).toString(), l.get(1)); } return m; } public List<Map<String, Object>> getValuesAsRows(){ return getValuesAsRows(values, columns); } public List<List<?>> getValuesAsMatrix(){ return getValuesAsMatrix( values); } public Map<String, Object> getValuesAsDictionary(){ return getValuesAsDictionary(values); } private Object getValueForSerializer(Object value, BeakerObjectConverter serializer){ if (value != null) { String clazz = serializer.convertType(value.getClass().getName()); if (BasicObjectSerializer.TYPE_LONG.equals(clazz) || BasicObjectSerializer.TYPE_BIGINT.equals(clazz)){ return value.toString(); } return value; } return null; } public List<List<?>> getValues() { return values; } public List<String> getColumnNames() { return columns; } public List<String> getTypes() { return classes; } public String getSubtype() { return subtype; } private Object doubleClickListener; private String doubleClickTag; private Map<String, Object> contextMenuListeners = new HashMap<>(); private Map<String, String> contextMenuTags = new HashMap<>(); private TableActionDetails details; public void setDoubleClickAction(Object listener) { this.doubleClickTag = null; this.doubleClickListener = listener; } public void setDoubleClickAction(String tagName) { this.doubleClickListener = null; this.doubleClickTag = tagName; } public String getDoubleClickTag() { return doubleClickTag; } public void fireDoubleClick(List<Object> params) { if (this.doubleClickListener != null) { try { params.add(this); runClosure(this.doubleClickListener, params.toArray()); } catch (Exception e) { throw new RuntimeException("Unable execute closure", e); } } } public boolean hasDoubleClickAction() { return this.doubleClickListener != null; } public void addContextMenuItem(String name, Object closure) { this.contextMenuListeners.put(name, closure); } public void addContextMenuItem(String name, String tagName) { this.contextMenuTags.put(name, tagName); } public Set<String> getContextMenuItems() { return this.contextMenuListeners.keySet(); } public Map<String, String> getContextMenuTags() { return contextMenuTags; } public void setDetails(TableActionDetails details) { this.details = details; } public TableActionDetails getDetails() { return this.details; } public void fireContextMenuClick(String name, List<Object> params) { Object contextMenuListener = this.contextMenuListeners.get(name); if (contextMenuListener != null) { try { params.add(this); runClosure(contextMenuListener, params.toArray()); } catch (Exception e) { throw new RuntimeException("Unable execute closure", e); } } } protected Object runClosure(Object closure, Object... params) throws Exception { return RunWidgetClosure.runClosure(closure, params); } @Override protected Map serializeToJsonObject() { return TableDisplayToJson.toJson(this); } }