package com.onaro.util.jfc.tables.filter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableModel;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import com.onaro.util.jfc.tables.FilterTable;
public class FilterTableModel extends AbstractTableModel implements IFilterModel {
private static final long serialVersionUID = 1L;
private TableModel encapsulatedModel;
/**
* Maps number of visible (accepted) rows to row numbers in the encapsulated model.
* The array is indexed by numbers visible in the jtable and the value is the number in the encapsulated model.
* In order to improve performance, the array is kept empty if no filter is set.
*/
private int acceptedRows[];
private int acceptedRowCount;
private int activeFilterColumns[];
private int activeFilterColumnsCount;
private Filter filters[];
private String filterTooltips[];
private boolean serverFilterActive = false;
private List patternEventListeners = new ArrayList();
private TableModelListener encapsulatedModelListener = new TableModelListener() {
public void tableChanged(TableModelEvent e) {
if (e.getFirstRow() == 0 && e.getLastRow() == Integer.MAX_VALUE) {
invalidateAcceptedRows();
}
}
};
public FilterTableModel() {
// Do nothing
}
public FilterTableModel(TableModel encapsulatedModel) {
this();
setEncapsulatedTableModel(encapsulatedModel);
}
/**
* Install column filters according to their type as returned from {@link TableModel#getColumnClass(int)}.
*/
public void installDefaultFilters() {
installDefaultFilters(FilterTable.DEFAULT_FILTER_FACTORY);
}
/**
* Install column filters according to their type as returned from {@link TableModel#getColumnClass(int)}.
* Given a filterFactory
*/
public void installDefaultFilters(FilterFactory filterFactory) {
int columnCount = getColumnCount();
for (int column = 0; column < columnCount; ++column) {
Class> columnClass = getColumnClass(column);
setFilter(column, filterFactory.create(columnClass));
}
}
public void addFilterPatternEventListener(FilterPatternEventListener listener) {
patternEventListeners.add(listener);
}
public void removeFilterPatternEventListener(FilterPatternEventListener listener) {
patternEventListeners.remove(listener);
}
protected void fireFilterPatternEvent(int columnIndex, Object oldPattern, Object newPattern){
if (!patternEventListeners.isEmpty()) {
FilterPatternEvent event = new FilterPatternEvent(this, columnIndex, oldPattern, newPattern);
for (FilterPatternEventListener patternEventListener : patternEventListeners) {
if(patternEventListener != null){
patternEventListener.filterPatternChanged(event);
}
}
}
}
public void setEncapsulatedTableModel(TableModel encapsulatedModel) {
if (this.encapsulatedModel != null) {
this.encapsulatedModel.removeTableModelListener(encapsulatedModelListener);
}
this.encapsulatedModel = encapsulatedModel;
filters = new Filter[encapsulatedModel.getColumnCount()];
filterTooltips = new String[filters.length];
activeFilterColumns = new int[filters.length];
encapsulatedModel.addTableModelListener(encapsulatedModelListener);
invalidateAcceptedRows();
}
private void invalidateAcceptedRows() {
synchronized(needsFiltering) {
acceptedRows = null;
acceptedRowCount = -1;
activeFilterColumnsCount = -1;
needsFiltering.set(true);
}
fireTableDataChanged();
}
public int getRowCount() {
initAcceptedRows();
return acceptedRowCount;
}
public int getFilteredRowCount() {
return getRowCount();
}
public int getUnfilteredRowCount() {
return encapsulatedModel.getRowCount();
}
public int getColumnCount() {
return encapsulatedModel.getColumnCount();
}
public Object getValueAt(int rowIndex, int columnIndex) {
return encapsulatedModel.getValueAt(getRowInEncapsulatedModel(rowIndex), columnIndex);
}
public String getColumnName(int column) {
return encapsulatedModel.getColumnName(column);
}
public Class> getColumnClass(int columnIndex) {
return encapsulatedModel.getColumnClass(columnIndex);
}
public boolean isCellEditable(int rowIndex, int columnIndex) {
return encapsulatedModel.isCellEditable(getRowInEncapsulatedModel(rowIndex), columnIndex);
}
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
encapsulatedModel.setValueAt(aValue, getRowInEncapsulatedModel(rowIndex), columnIndex);
}
public void setFilter(int column, Filter filter) {
filters[column] = filter;
}
/**
* Clears all the filters so all the rows are visible.
*/
public void resetAllFilters() {
for (int column = 0; column < filters.length; column++) {
Filter filter = filters[column];
if (filter != null) {
setFilterPattern(column, null);
}
}
setServerFilterActive(false);
}
/**
* checks if any of the columns has an active filter
*/
public boolean isAnyFilterActive() {
if (isServerFilterActive()) return true;
for (int column = 0; column < filters.length; column++) {
if(isFilterActive(column)) {
return true;
}
}
return false;
}
public boolean isFilterActive(int column) {
return column >= 0 && column < filters.length && filters[column] != null && filters[column].isActive();
}
public boolean isFilterValid(int column) {
return filters[column] != null && filters[column].isValid();
}
public String getFilterTooltip(int column) {
if (column >= 0 && column < filters.length && filters[column] != null) {
if (filters[column].isActive()) {
return filterTooltips[column];
}
}
return null;
}
public Object getFilterPattern(int column) {
return filters[column] != null ? filters[column].getPattern() : null;
}
public Filter getFilter(int column) {
return filters[column];
}
public void setFilterPattern(int column, Object pattern) {
Filter filter = filters[column];
assert filter != null : "Cannot set pattern to column " + column + " as no filter is assigned for it"; //$NON-NLS-1$ //$NON-NLS-2$
Object oldPattern = filter.getPattern();
if (!ObjectUtils.equals(oldPattern,pattern)) {
filter.setPattern(pattern);
createTooltip(column, pattern);
invalidateAcceptedRows();
fireFilterPatternEvent(column,oldPattern,pattern);
}
}
private void createTooltip(int column, Object pattern) {
filterTooltips[column] = null;
if (pattern != null) {
boolean valid = filters[column].isValid();
String patternStr = filters[column].formatPatternForTooltip(pattern);
if (patternStr == null) {
patternStr = String.valueOf(pattern);
}
// Strip out existing html elements
patternStr = patternStr.replaceAll("(<.+?>)", StringUtils.EMPTY); //$NON-NLS-1$
patternStr = patternStr.replaceAll("&", "&qout;").replaceAll("<", "<").replaceAll(">", ">"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$
StringBuilder tooltip = new StringBuilder(""); //$NON-NLS-1$
tooltip.append(Messages.INSTANCE.getFilter()).append(" "); //$NON-NLS-1$
if (!valid)
tooltip.append(""); //$NON-NLS-1$
else
tooltip.append(""); //$NON-NLS-1$
tooltip.append(patternStr);
tooltip.append(""); //$NON-NLS-1$
filterTooltips[column] = tooltip.toString();
}
}
public boolean isFilterable(int column) {
return column >= 0 && column < filters.length && filters[column] != null;
}
public int getRowInEncapsulatedModel(int row) {
initAcceptedRows();
if (activeFilterColumnsCount <= 0) {
return row;
} else {
assert row < acceptedRowCount : "row=" + row + " < acceptedRowCount=" + acceptedRowCount; //$NON-NLS-1$ //$NON-NLS-2$
return acceptedRows[row];
}
}
public TableModel getEncapsulatedTableModel() {
return encapsulatedModel;
}
private final AtomicBoolean needsFiltering = new AtomicBoolean(true);
/**
* Filter the table model if necessary. This method is thread safe.
*/
private void initAcceptedRows() {
if (needsFiltering.get()) {
// Lock to ensure this only gets calculated once, and others wait for the results
synchronized (needsFiltering) {
// Double check that the calculation hasn't already happened
if (!needsFiltering.get()) {
return;
}
try {
activeFilterColumnsCount = 0;
for (int col = 0; col < filters.length; col++) {
if (filters[col] != null && filters[col].isActive() && filters[col].isValid()) {
activeFilterColumns[activeFilterColumnsCount++] = col;
}
}
if (acceptedRows == null && activeFilterColumnsCount > 0) {
acceptedRowCount = 0;
int encRowCount = encapsulatedModel.getRowCount();
acceptedRows = new int[encRowCount];
for (int encRow = 0; encRow < encRowCount; ++encRow) {
boolean accepted = true;
for (int i = 0; i < activeFilterColumnsCount; i++) {
int activeFilterColumn = activeFilterColumns[i];
Filter filter = filters[activeFilterColumn];
assert filter != null : "Active filter cannot is null"; //$NON-NLS-1$
Object value = encapsulatedModel.getValueAt(encRow, activeFilterColumn);
accepted = filter.isAccepted(value);
// No need to continue checking if this row is filtered out
if (!accepted) {
break;
}
}
if (accepted) {
acceptedRows[acceptedRowCount++] = encRow;
}
}
} else {
acceptedRowCount = encapsulatedModel.getRowCount();
}
} finally {
needsFiltering.set(false);
}
}
}
}
public boolean isServerFilterActive() {
return serverFilterActive;
}
public void setServerFilterActive(boolean serverFilterActive) {
if (this.serverFilterActive == serverFilterActive) {
return;
}
this.serverFilterActive = serverFilterActive;
fireFilterPatternEvent(FilterPatternEvent.SERVER_FILTER_INDEX, !serverFilterActive, serverFilterActive);
}
}