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); } }