/* * $Id: Filter.java,v 1.19 2009/01/02 13:26:06 rah003 Exp $ * * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle, * Santa Clara, California 95054, U.S.A. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ package org.jdesktop.swingx.decorator; /** * <p>A <b><code>Filter</code></b> is used to filter the data presented in a * data-aware component such as a {@link org.jdesktop.swingx.JXList} or a * {@link org.jdesktop.swingx.JXTable}. Filtering involves interposing one or * more filters in a {@link org.jdesktop.swingx.decorator.FilterPipeline} between * a data model and a view to change the apparent order and/or number of records * in the data model.</p> * * @author Ramesh Gupta * @see org.jdesktop.swingx.decorator.FilterPipeline * @see org.jdesktop.swingx.JXTable */ public abstract class Filter { /** the column the filter is bound to. JW: no need to make it final. */ private /** final */ int column; // in model coordinates protected FilterPipeline pipeline = null; protected ComponentAdapter adapter = null; /** TODO: make private */ protected int[] fromPrevious = new int[0]; // TODO: JW... magic number! int order = -1; // package private /** * Constructs a new filter for the first column of a data model. */ public Filter() { this(0); } /** * Constructs a new filter for the specified column of a data model in absolute * model coordinates. * * @param column column index in absolute model coordinates */ public Filter(int column) { this.column = column; init(); } //----------------- public methods meant for end-client access /** * Refreshes the internal state of the filter, performs the {@link #filter() filter} * operation and regenerates row mappings from the previous filter. If this * filter is bound to a filter pipeline (as most filters are), it also triggers a * {@link org.jdesktop.swingx.decorator.FilterPipeline#filterChanged(org.jdesktop.swingx.decorator.Filter) filterChanged} * notification. */ public void refresh() { refresh(true); } /** * Returns the model index of the column that this filter has been bound to. * * @return the column index in absolute model coordinates */ public int getColumnIndex() { return column; // model coordinates } /** * TODO: PENDING: not tested! * * @param modelColumn column index in absolute model coordinates */ public void setColumnIndex(int modelColumn) { if (getColumnIndex() == modelColumn) return; this.column = modelColumn; refresh(); } public String getColumnName() { if (adapter == null) { return "Column " + column; // in model coordinates :-( } else { return adapter.getColumnName(getColumnIndex()); } } /** * Convert row index from view coordinates to model coordinates * accounting for the presence of sorters and filters. * * PRE: 0 <= row < getSize() * * @param row the row index in this filter's output ("view") coordinates * @return row index in absolute model coordinates */ public int convertRowIndexToModel(int row) { int mappedRow = mapTowardModel(row); Filter filter = getMappingFilter(); if (filter != null) { mappedRow = filter.convertRowIndexToModel(mappedRow); } return mappedRow; } /** * Convert row index from model coordinates to view coordinates accounting * for the presence of sorters and filters. * * @param row row index in model coordinates * @return row index in this filter's "view" coordinates */ public int convertRowIndexToView(int row) { int mappedRow = row; Filter filter = getMappingFilter(); if (filter != null) { mappedRow = filter.convertRowIndexToView(mappedRow); } return mapTowardView(mappedRow); } /** * Returns the value at the specified row and column. * The column index is in absolute column coordinates. * * PRE: 0 <= row < getSize() * * @param row the row index in this filter's output ("view") coordinates * @param column column index in absolute model coordinates * @return the value at the specified row and column */ public Object getValueAt(int row, int column) { int mappedRow = mapTowardModel(row); Filter filter = getMappingFilter(); if (filter != null) { return filter.getValueAt(mappedRow, column); } return adapter.getValueAt(mappedRow, column); } /** * Returns the string representation at the specified row and column. * The column index is in absolute column coordinates. * * PRE: 0 <= row < getSize() * * @param row the row index in this filter's output ("view") coordinates * @param column column index in model coordinates * @return the string representation of the cell at the specified row and column. */ public String getStringAt(int row, int column) { int mappedRow = mapTowardModel(row); Filter filter = getMappingFilter(); if (filter != null) { return filter.getStringAt(mappedRow, column); } return adapter.getStringAt(mappedRow, column); } /** * Sets the specified value as the new value for the cell identified by the * specified row and column index. The column index is in absolute column coordinates. * * PRE: 0 <= row < getSize() * * @param aValue new value for the specified cell * @param row the row index in this filter's output ("view") coordinates * @param column the column index in absolute model coordinates */ public void setValueAt(Object aValue, int row, int column) { int mappedRow = mapTowardModel(row); Filter filter = getMappingFilter(); if (filter != null) { filter.setValueAt(aValue, mappedRow, column); return; // make sure you return from here! } adapter.setValueAt(aValue, mappedRow, column); } /** * Returns editability of the cell identified by the specified row * and column index. The column index is in absolute column coordinates. * * PRE: 0 <= row < <code>getSize()</code> * * @param row the row index in this filter's output ("view") coordinates * @param column column index in model coordinates * @return true if the cell at the specified row/col is editable */ public boolean isCellEditable(int row, int column) { int mappedRow = mapTowardModel(row); Filter filter = getMappingFilter(); if (filter != null) { return filter.isCellEditable(mappedRow, column); } return adapter.isCellEditable(mappedRow, column); } /** * Returns the number of records that remain in this filter's output ("view") * after the input records have been filtered. * * @return the number of records that remain in this filter's output ("view") * after the input records have been filtered */ public abstract int getSize(); //---------------------------------- for subclasses /** * Returns the number of records of this filter's input ("model"). * * @return the number of records of this filter's input ("model"). */ protected int getInputSize() { return pipeline == null ? adapter == null ? 0 : adapter.getRowCount() : pipeline.getInputSize(this); } /** * Returns the value of the cell at the specified row and column. * The column index is in absolute column coordinates. * * @param row in the coordinates of what is the filter's input ("model"). * @param column in model coordinates * @return the value of the cell at the specified row and column. */ protected Object getInputValue(int row, int column) { Filter filter = getMappingFilter(); if (filter != null) { return filter.getValueAt(row, column); } if (adapter != null) { return adapter.getValueAt(row, column); } return null; } /** * Returns the string representation of cell at the specified row and column. * * @param row in the coordinates of what is the filter's "model" (== input) coordinates * @param column in model coordinates * @return the string representation of the cell at the specified row and column. */ protected String getInputString(int row, int column) { Filter filter = getMappingFilter(); if (filter != null) { return filter.getStringAt(row, column); } if (adapter != null) { return adapter.getStringAt(row, column); } return null; } /** * Provides filter-specific initialization. Called from the <code>Filter</code> * constructor. */ protected abstract void init(); /** * Resets the internal row mappings from this filter to the previous filter. */ protected abstract void reset(); /** * Performs the filter operation defined by this filter. */ protected abstract void filter(); /** * PRE: 0 <= row < <code>getSize();</code> * * @param row * @return TODO: */ protected abstract int mapTowardModel(int row); /** * PRE: 0 <= row < <code>getInputSize();</code> * * @param row * @return TODO: */ protected int mapTowardView(int row) { // WARNING: Not all model indices map to view when view is filtered! // JW - TODO: cleanup and clarify preconditions in all mapping methods // in all towardView the row must be < getInputSize // in add towardModel the row must be < getSize return row < 0 || row >= fromPrevious.length ? - 1 : fromPrevious[row]; } /** * Returns the filter to use for accessing input. * That's the previous (model is first) filter if this is * part of a pipeline or null if this is standalone or the first * in the pipeline. * * @return filter to use for accessing input */ protected Filter getMappingFilter() { Filter filter = null; if (pipeline != null) { filter = pipeline.previous(this); } return filter; } /** * Refreshes the internal state of the filter, optionally resetting the * cache of existing row mappings from this filter to the previous filter. * Always performs the {@link #filter() filter} operation and regenerates * row mappings from the previous filter. If this filter is bound to a filter * pipeline (as most filters are), it also triggers a * {@link org.jdesktop.swingx.decorator.FilterPipeline#filterChanged(org.jdesktop.swingx.decorator.Filter) filterChanged} * notification. * * @param reset true if existing row mappings from this filter to the previous * filter should be reset; false, if the existing row mappings should be preserved. */ protected void refresh(boolean reset) { if (reset) { reset(); } filter(); fireFilterChanged(); } /** * Notifies interested parties that this filter has changed. * */ protected void fireFilterChanged() { // trigger direct notification; will cascade to next in pipeline, if any if (pipeline != null) { if (pipeline.contains(this)) { pipeline.filterChanged(this); return; } } if (adapter != null) { adapter.refresh(); } } /** * Binds this filter to the specified <code>ComponentAdapter</code>. * Called by {@link FilterPipeline#assign(ComponentAdapter)} * * @param adapter adapter that this filter is bound to */ protected void assign(ComponentAdapter adapter) { if (adapter == null) { throw new IllegalArgumentException("null adapter"); } if (this.adapter == null) { this.adapter = adapter; } else if (this.adapter != adapter){ throw new IllegalStateException("Already bound to another adapter"); } } /** * Binds this filter to the specified filter pipeline. * * @param pipeline the filter pipeline that this filter is bound to */ final void assign(FilterPipeline pipeline) { /** NOTE: JXTable.resetSorter may pass in null for filter pipeline! if (pipeline == null) { throw new IllegalArgumentException("null pipeline"); } */ if ((this.pipeline == null) || (pipeline == null)) { this.pipeline = pipeline; } else if (this.pipeline != pipeline) { throw new IllegalStateException("Already bound to another pipeline"); } } /** * Called by {@link FilterPipeline#assignFilters()} */ void assign(FilterPipeline pipeline, int i) { if (order >= 0) { throw new IllegalArgumentException("Element " + i + " is part of another pipeline."); } this.order = i; assign(pipeline); } protected FilterPipeline getPipeline() { return pipeline; } }