/**
* Copyright (C) 2001-2017 by RapidMiner and the contributors
*
* Complete list of developers available at our web site:
*
* http://rapidminer.com
*
* This program is free software: you can redistribute it and/or modify it under the terms of the
* GNU Affero General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* This program 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
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License along with this program.
* If not, see http://www.gnu.org/licenses/.
*/
package com.rapidminer.datatable;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Vector;
/**
* This DataTable filters the contained rows using a stack of FilterConditions. Each time the stack
* is modified, it informs it's DataTableFilteredListener that they need to update the table.
*
* @author Sebastian Land, Marius Helf
*/
public class FilteredDataTable extends DataTableView {
public static enum ConditionCombination {
AND, // all conditions must match
OR // at least one condition must match
}
public static interface DataTableFilteredListener {
/**
* This method is called by a datatable, if its content is changed.
*/
public void informDataTableChange(DataTable dataTable);
}
private List<DataTableFilteredListener> listeners = new LinkedList<DataTableFilteredListener>();
private ArrayList<DataTableFilterCondition> conditionStack = new ArrayList<DataTableFilterCondition>();
private final ConditionCombination conditionCombination;
public FilteredDataTable(DataTable parentDataTable) {
super(parentDataTable);
this.conditionCombination = ConditionCombination.AND;
}
public FilteredDataTable(DataTable parentDataTable, ConditionCombination conditionCombination) {
super(parentDataTable);
this.conditionCombination = conditionCombination;
}
/**
* Adds a new condition. null arguments will be ignored.
*
* @param condition
*/
public void addCondition(DataTableFilterCondition condition) {
if (condition == null) {
return;
}
conditionStack.add(condition);
setSelectedIndices(updateSelection());
informDataTableFilteredListener();
}
/**
* Adds a batch of conditions. The listeners will be only informed after all of the conditions
* have been added. Null conditions will be ignored.
*
* @param conditions
* The collection of the conditions to be added. Must not be null.
*/
public void addConditions(Iterable<? extends DataTableFilterCondition> conditions) {
boolean changed = false;
for (DataTableFilterCondition condition : conditions) {
if (condition != null) {
conditionStack.add(condition);
changed = true;
}
}
if (changed) {
setSelectedIndices(updateSelection());
informDataTableFilteredListener();
}
}
public void removeCondition() {
if (conditionStack.size() > 0) {
conditionStack.remove(conditionStack.size() - 1);
setSelectedIndices(updateSelection());
informDataTableFilteredListener();
}
}
public void removeAllConditions() {
if (conditionStack.size() > 0) {
conditionStack.clear();
setSelectedIndices(updateSelection());
informDataTableFilteredListener();
}
}
/**
* Replaces all conditions. See addConditions for parameter description.
*/
public void replaceConditions(Iterable<? extends DataTableFilterCondition> newConditions) {
int oldSize = conditionStack.size();
conditionStack.clear();
if (!newConditions.iterator().hasNext() && oldSize > 0) {
setSelectedIndices(updateSelection());
informDataTableFilteredListener();
return;
}
addConditions(newConditions);
}
/**
* @return a vector which contains the indices of all rows in the parent table that match the
* condition stack.
*/
private Vector<Integer> updateSelection() {
if (conditionStack.isEmpty()) {
return null;
}
int parentRowIndex = 0;
Vector<Integer> selectedIndices = new Vector<Integer>();
for (DataTableRow row : getParentTable()) {
// heuristic: if we have accesses to the row, cache the row in a SimpleDataTableRow
if (conditionStack.size() / 2 > row.getNumberOfValues() && !(row instanceof SimpleDataTableRow)) {
row = new SimpleDataTableRow(row);
}
switch (conditionCombination) {
case AND:
boolean keep = true;
for (int conditionIndex = conditionStack.size() - 1; conditionIndex >= 0 && keep; conditionIndex--) {
keep &= conditionStack.get(conditionIndex).keepRow(row);
if (!keep) {
break;
}
}
if (keep) {
selectedIndices.add(parentRowIndex);
}
break;
case OR:
for (int conditionIndex = conditionStack.size() - 1; conditionIndex >= 0; conditionIndex--) {
if (conditionStack.get(conditionIndex).keepRow(row)) {
selectedIndices.add(parentRowIndex);
break;
}
}
break;
}
parentRowIndex++;
}
return selectedIndices;
}
/*
* Listener Methods
*/
public void addDataTableFilteredListener(DataTableFilteredListener listener) {
this.listeners.add(listener);
}
public void removeDataTableFilteredListewner(DataTableFilteredListener listener) {
this.listeners.remove(listener);
}
private void informDataTableFilteredListener() {
for (DataTableFilteredListener listener : listeners) {
listener.informDataTableChange(this);
}
}
@Override
public void dataTableUpdated(DataTable source) {
setSelectedIndices(updateSelection());
}
}