package com.ibm.nmon.gui.table;
import java.util.BitSet;
import java.util.Map;
import javax.swing.table.AbstractTableModel;
import org.slf4j.Logger;
/**
* <p>
* A base TableModel for tables whose column displayability is chosen at run time.
* </p>
*
* <p>
* This class implements the standard TableModel methods by mapping the currently enabled column
* indexes to the 'absolute' index values based on the total set of all possible columns. This
* allows subclasses to maintain a constant mapping between column indexes and values. <em>All</em>
* column indexes passed as parameters to abstract methods are <em>absolute</em> column indexes;
* subclasses will never see relative (visible) column indexes.
* </p>
*
* <p>
* For example, if a table has 3 possible columns and only columns 1 and 3 are enabled, subclasses
* will still see requests for columns 1 and 3 in the <code>getEnabledXXX</code> methods. This
* class, in the standard TableModel methods will map column 1 (relative) to column 1 (absolute) and
* column 2 (visible) to column 3 (absolute). If columns 2 and 3 are enabled however, the mapping
* will be column 1 to column 2 and column 2 to column 3. In both cases, the subclass sees the
* absolute column indexes.
* </p>
*/
public abstract class ChoosableColumnTableModel extends AbstractTableModel {
private static final long serialVersionUID = 9081831820888671224L;
protected final Logger logger = org.slf4j.LoggerFactory.getLogger(getClass());
/**
* The set of currently enabled columns. Subclasses are responsible for the life cycle of this
* field. They <em>must</em> ensure that this set is the same size as the array returned by
* {@link #getAllColumns()}.
*/
// not final so that subclasses can add or remove columns as needed
protected BitSet enabledColumns;
/**
* A map of column names to <em>absolute</em> column indexes. Subclasses <em>should not</em>
* attempt to modify this map directly. Instead they should have {@link #getAllColumns()} return
* an updated list then call {@link #buildColumnNameMap()} when the columns list changes.
*/
protected Map<String, Integer> columnNamesMap;
/**
* @return an array of all possible column names
*/
public abstract String[] getAllColumns();
/**
* @return <code>true</code> if the column is enabled by default
*/
public abstract boolean getDefaultColumnState(int column);
/**
* @return <code>true</code> if the column can be disabled
*/
public abstract boolean canDisableColumn(int column);
@Override
public boolean isCellEditable(int row, int column) {
return false;
}
/**
* @return the <em>enabled</em> column count
*/
@Override
public final int getColumnCount() {
return enabledColumns.cardinality();
}
@Override
public final Class<?> getColumnClass(int columnIndex) {
int n = 0;
for (int i = enabledColumns.nextSetBit(0); i >= 0; i = enabledColumns.nextSetBit(i + 1)) {
if (n == columnIndex) {
return getEnabledColumnClass(i);
}
++n;
}
throw new ArrayIndexOutOfBoundsException(columnIndex);
}
protected abstract Class<?> getEnabledColumnClass(int columnIndex);
@Override
public final String getColumnName(int column) {
int n = 0;
for (int i = enabledColumns.nextSetBit(0); i >= 0; i = enabledColumns.nextSetBit(i + 1)) {
if (n == column) {
return getEnabledColumnName(i);
}
++n;
}
throw new ArrayIndexOutOfBoundsException(column);
}
protected abstract String getEnabledColumnName(int column);
@Override
public final Object getValueAt(int row, int column) {
int n = 0;
for (int i = enabledColumns.nextSetBit(0); i >= 0; i = enabledColumns.nextSetBit(i + 1)) {
if (n == column) {
return getEnabledValueAt(row, i);
}
++n;
}
throw new ArrayIndexOutOfBoundsException(column);
}
protected abstract Object getEnabledValueAt(int row, int column);
/**
* @return <code>true</code> if the named column is enabled; return <code>false</code> if the
* name is not valid for this table
*/
public final boolean getEnabled(String columnName) {
Integer idx = columnNamesMap.get(columnName);
if (idx != null) {
return enabledColumns.get(idx);
}
else {
return false;
}
}
/**
* Set the state of the given column. This column index is <em>absolute</em>.
*/
public final void setEnabled(String columnName, boolean enabled) {
Integer idx = columnNamesMap.get(columnName);
if ((idx != null) && (enabledColumns.get(idx) != enabled)) {
logger.trace("{} enabled={}", columnName, enabled);
enabledColumns.set(idx, enabled);
if (enabled) {
onEnable(columnName, idx);
}
else {
onDisable(columnName, idx);
}
fireTableStructureChanged();
}
}
/**
* Hook function for subclasses to perform an action after a column is enabled. This method will
* be called before a table model event is fired.
*/
protected void onEnable(String columnName, int column) {}
/**
* Hook function for subclasses to perform an action after a column is disabled. This method
* will be called before a table model event is fired.
*/
protected void onDisable(String columnName, int column) {}
/**
* Rebuilds {@link #columnNamesMap} after calling {@link #getAllColumns()}.
*/
protected void buildColumnNameMap() {
Map<String, Integer> temp = new java.util.HashMap<String, Integer>();
String[] columnNames = getAllColumns();
for (int i = 0; i < columnNames.length; i++) {
temp.put(columnNames[i], i);
}
columnNamesMap = java.util.Collections.unmodifiableMap(temp);
}
/**
* @return the <em>absolute</em> index for the named column or <code>-1</code> if there is no
* column with the given name
*/
public int getColumnIndex(String name) {
Integer idx = columnNamesMap.get(name);
if (idx == null) {
return -1;
}
else {
return idx;
}
}
/**
* @return the <em>relative</em> index for the named column or <code>-1</code> if there is no
* column with the given name
*/
public int getEnabledColumnIndex(String name) {
Integer idx = columnNamesMap.get(name);
if (idx == null) {
return -1;
}
else if (!enabledColumns.get(idx)) {
return -1;
}
else {
int n = 0;
for (int i = 0; i < idx; i++) {
if (enabledColumns.get(i)) {
++n;
}
}
return n;
}
}
}