/**
* 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.gui.properties;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.swing.event.TableModelEvent;
import javax.swing.table.AbstractTableModel;
import com.rapidminer.example.set.CustomFilter.CustomFilters;
import com.rapidminer.gui.properties.tablepanel.TablePanel;
import com.rapidminer.gui.properties.tablepanel.cells.interfaces.CellType;
import com.rapidminer.gui.properties.tablepanel.cells.interfaces.CellTypeComboBox;
import com.rapidminer.gui.properties.tablepanel.cells.interfaces.CellTypeDate;
import com.rapidminer.gui.properties.tablepanel.cells.interfaces.CellTypeDateTime;
import com.rapidminer.gui.properties.tablepanel.cells.interfaces.CellTypeLabel;
import com.rapidminer.gui.properties.tablepanel.cells.interfaces.CellTypeRegex;
import com.rapidminer.gui.properties.tablepanel.cells.interfaces.CellTypeTextFieldDefault;
import com.rapidminer.gui.properties.tablepanel.cells.interfaces.CellTypeTextFieldInteger;
import com.rapidminer.gui.properties.tablepanel.cells.interfaces.CellTypeTextFieldNumerical;
import com.rapidminer.gui.properties.tablepanel.cells.interfaces.CellTypeTextFieldTime;
import com.rapidminer.gui.properties.tablepanel.model.TablePanelModel;
import com.rapidminer.operator.ports.InputPort;
import com.rapidminer.operator.ports.metadata.AttributeMetaData;
import com.rapidminer.operator.ports.metadata.ExampleSetMetaData;
import com.rapidminer.tools.I18N;
import com.rapidminer.tools.Ontology;
import com.rapidminer.tools.Tools;
/**
* This is the backing model for the {@link TablePanel} used by the example set filters.
*
* @author Marco Boeck
*
*/
public class FilterTableModel extends AbstractTableModel implements TablePanelModel {
private static final long serialVersionUID = 7919277746196444952L;
private static final int COLUMN_COUNT = 3;
private static final int COLUMN_ATTRIBUTES_INDEX = 0;
private static final int COLUMN_CUSTOM_FILTER_INDEX = 1;
private static final int COLUMN_FILTER_VALUE_INDEX = 2;
private static final String[] COLUMN_NAMES = new String[] {
I18N.getMessage(I18N.getGUIBundle(), "gui.dialog.filter_table_model.column_name_attribute.title"),
I18N.getMessage(I18N.getGUIBundle(), "gui.dialog.filter_table_model.column_name_compare.title"),
I18N.getMessage(I18N.getGUIBundle(), "gui.dialog.filter_table_model.column_name_field.title") };
/** syntax help for date fields */
private static final String SYNTAX_DATE = "01/31/2014";
/** syntax help for date_time fields */
private static final String SYNTAX_DATE_TIME = "01/31/2014 9:00:00 AM";
/** syntax help for time fields */
private static final String SYNTAX_TIME = "9:00:00 AM";
/** the metadata for the example set at the input port */
private ExampleSetMetaData md;
/** flag which indicates if possible values for filter comparators should depend on meta data */
private boolean checkMetaDataForComparators;
/** the number of rows this table model currently has */
private int rowsCount;
/** the list of AttributeMetaData for this model */
private List<AttributeMetaData> listOfAttributeMetaData;
/** the list of selected values for the attribute column */
private List<String> rowListOfSelectedAttributes;
/** the list of selected values for the comparator column */
private List<String> rowListOfSelectedComparators;
/** the list of selected values for the value column */
private List<String> rowListOfSelectedValues;
/** used to prevent events while we are creating the model */
private AtomicBoolean creatingModel;
/** the format for date_time */
private final DateFormat FORMAT_DATE_TIME = new SimpleDateFormat(CustomFilters.DATE_TIME_FORMAT_STRING, Locale.ENGLISH);
/** the format for date */
private final DateFormat FORMAT_DATE = new SimpleDateFormat(CustomFilters.DATE_FORMAT_STRING, Locale.ENGLISH);
/** the format for time */
private final DateFormat FORMAT_TIME = new SimpleDateFormat(CustomFilters.TIME_FORMAT_STRING, Locale.ENGLISH);
/**
* Creates a new {@link FilterTableModel} instance.
*
* @param inputPort
* @throws IllegalArgumentException
* if the input port has no example set
*/
public FilterTableModel(InputPort inputPort) throws IllegalArgumentException {
if (inputPort == null) {
throw new IllegalArgumentException("InputPort must not be null!");
}
this.listOfAttributeMetaData = new LinkedList<>();
if (inputPort.getMetaData() instanceof ExampleSetMetaData) {
this.md = (ExampleSetMetaData) inputPort.getMetaData();
for (AttributeMetaData metadata : md.getAllAttributes()) {
listOfAttributeMetaData.add(metadata);
}
}
this.rowsCount = 0;
this.rowListOfSelectedAttributes = new LinkedList<>();
this.rowListOfSelectedComparators = new LinkedList<>();
this.rowListOfSelectedValues = new LinkedList<>();
this.creatingModel = new AtomicBoolean(false);
this.checkMetaDataForComparators = true;
}
@Override
public int getRowCount() {
return rowsCount;
}
@Override
public int getColumnCount() {
return COLUMN_COUNT;
}
@Override
public String getColumnName(int columnIndex) {
if (columnIndex < 0 || columnIndex >= getColumnCount()) {
throw new IllegalArgumentException("Invalid columnIndex: " + columnIndex);
}
return COLUMN_NAMES[columnIndex];
}
@Override
public Class<?> getColumnClass(int columnIndex) {
return getColumnClass(-1, columnIndex);
}
@Override
public Class<? extends CellType> getColumnClass(int rowIndex, int columnIndex) {
switch (columnIndex) {
case COLUMN_ATTRIBUTES_INDEX:
return CellTypeComboBox.class;
case COLUMN_CUSTOM_FILTER_INDEX:
return CellTypeComboBox.class;
case COLUMN_FILTER_VALUE_INDEX:
if (rowIndex == -1) {
return CellTypeLabel.class;
}
CustomFilters filter = CustomFilters.getByLabel(String.valueOf(getValueAt(rowIndex,
COLUMN_CUSTOM_FILTER_INDEX)));
if (filter == CustomFilters.REGEX) {
return CellTypeRegex.class;
} else if (filter == CustomFilters.MISSING || filter == CustomFilters.NOT_MISSING) {
// missing/not missing filter -> no date cell type
return CellTypeTextFieldDefault.class;
} else if (filter == null || filter.isNominalFilter()) {
return CellTypeTextFieldDefault.class;
} else {
// distinguish between numerical and date attributes
String selectedAttributeName = String.valueOf(getValueAt(rowIndex, COLUMN_ATTRIBUTES_INDEX));
int valueType = Ontology.ATTRIBUTE_VALUE;
// see if the attribute has been set to an existing one so the value type can be
// obtained
for (AttributeMetaData md : listOfAttributeMetaData) {
if (md.getName().equals(selectedAttributeName)) {
valueType = md.getValueType();
break;
}
}
// this happens when the meta data is unknown - use String.class for generic
// textfield
if (valueType == Ontology.ATTRIBUTE_VALUE) {
return CellTypeTextFieldDefault.class;
}
if (Ontology.ATTRIBUTE_VALUE_TYPE.isA(valueType, Ontology.DATE)) {
return CellTypeDate.class;
} else if (Ontology.ATTRIBUTE_VALUE_TYPE.isA(valueType, Ontology.TIME)) {
return CellTypeTextFieldTime.class;
} else if (Ontology.ATTRIBUTE_VALUE_TYPE.isA(valueType, Ontology.DATE_TIME)) {
return CellTypeDateTime.class;
}
if (valueType == Ontology.INTEGER) {
return CellTypeTextFieldInteger.class;
}
return CellTypeTextFieldNumerical.class;
}
default:
throw new IllegalArgumentException("Invalid columnIndex: " + columnIndex);
}
}
@Override
public boolean isCellEditable(int rowIndex, int columnIndex) {
// only first and last column are editable
if (columnIndex == COLUMN_ATTRIBUTES_INDEX) {
return true;
}
if (columnIndex == COLUMN_FILTER_VALUE_INDEX) {
// all filter value cells are editable; except for special filters as they do not need a
// filter value
CustomFilters filter = CustomFilters
.getByLabel(String.valueOf(getValueAt(rowIndex, COLUMN_CUSTOM_FILTER_INDEX)));
return !filter.isSpecialFilter();
}
return false;
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
if (rowIndex < 0 || rowIndex >= getRowCount()) {
throw new IllegalArgumentException("Invalid rowIndex: " + columnIndex);
}
switch (columnIndex) {
case COLUMN_ATTRIBUTES_INDEX:
return rowListOfSelectedAttributes.get(rowIndex);
case COLUMN_CUSTOM_FILTER_INDEX:
// return the filter label (I18N), internally the symbol is used (no I18N)
return CustomFilters.getBySymbol(rowListOfSelectedComparators.get(rowIndex)).getLabel();
case COLUMN_FILTER_VALUE_INDEX:
return rowListOfSelectedValues.get(rowIndex);
default:
throw new IllegalArgumentException("Invalid columnIndex: " + columnIndex);
}
}
@Override
public String getHelptextAt(int rowIndex, int columnIndex) {
if (rowIndex < 0 || rowIndex >= getRowCount()) {
throw new IllegalArgumentException("Invalid rowIndex: " + columnIndex);
}
switch (columnIndex) {
case COLUMN_ATTRIBUTES_INDEX:
return I18N.getMessage(I18N.getGUIBundle(), "gui.dialog.filter_table_model.column_name_attribute.tip");
case COLUMN_CUSTOM_FILTER_INDEX:
CustomFilters filter = CustomFilters.getByLabel(String.valueOf(getValueAt(rowIndex,
COLUMN_CUSTOM_FILTER_INDEX)));
return filter == null ? null : filter.getHelptext();
case COLUMN_FILTER_VALUE_INDEX:
String genericHelptext = I18N.getMessage(I18N.getGUIBundle(),
"gui.dialog.filter_table_model.column_name_field.tip");
CustomFilters filter1 = CustomFilters.getByLabel(String.valueOf(getValueAt(rowIndex,
COLUMN_CUSTOM_FILTER_INDEX)));
return filter1 == null ? genericHelptext : filter1.getHelptext();
default:
throw new IllegalArgumentException("Invalid columnIndex: " + columnIndex);
}
}
@Override
public String getSyntaxHelpAt(int rowIndex, int columnIndex) {
if (rowIndex < 0 || rowIndex >= getRowCount()) {
throw new IllegalArgumentException("Invalid rowIndex: " + columnIndex);
}
switch (columnIndex) {
case COLUMN_ATTRIBUTES_INDEX:
return null;
case COLUMN_CUSTOM_FILTER_INDEX:
return null;
case COLUMN_FILTER_VALUE_INDEX:
// only show syntax help for time class
if (CellTypeTextFieldTime.class.isAssignableFrom(getColumnClass(rowIndex, columnIndex))) {
return SYNTAX_TIME;
} else if (CellTypeDateTime.class.isAssignableFrom(getColumnClass(rowIndex, columnIndex))) {
return SYNTAX_DATE_TIME;
} else if (CellTypeDate.class.isAssignableFrom(getColumnClass(rowIndex, columnIndex))) {
return SYNTAX_DATE;
} else {
return null;
}
default:
throw new IllegalArgumentException("Invalid columnIndex: " + columnIndex);
}
}
@Override
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
if (rowIndex < 0 || rowIndex >= getRowCount()) {
throw new IllegalArgumentException("Invalid rowIndex: " + columnIndex);
}
switch (columnIndex) {
case COLUMN_ATTRIBUTES_INDEX:
if (!(aValue instanceof String)) {
throw new IllegalArgumentException("aValue must be of class String!");
}
rowListOfSelectedAttributes.set(rowIndex, String.valueOf(aValue));
// attribute selection has consequences for the comparator column GUI, so fire a
// change there
fireTableChanged(new TableModelEvent(this, rowIndex, rowIndex, COLUMN_CUSTOM_FILTER_INDEX,
TableModelEvent.UPDATE));
// attribute selection has consequences for the filter value column GUI, so fire a
// change there
fireTableChanged(new TableModelEvent(this, rowIndex, rowIndex, COLUMN_FILTER_VALUE_INDEX,
TableModelEvent.UPDATE));
break;
case COLUMN_CUSTOM_FILTER_INDEX:
if (!(aValue instanceof String)) {
throw new IllegalArgumentException("aValue must be of class String!");
}
// set the filter symbol (no I18N), GUI uses the label (I18N)
CustomFilters filter = CustomFilters.getByLabel(String.valueOf(aValue));
if (filter != null) {
rowListOfSelectedComparators.set(rowIndex, filter.getSymbol());
// special case switch to missing filter: clear value column as well
if (filter == CustomFilters.MISSING || filter == CustomFilters.NOT_MISSING) {
rowListOfSelectedValues.set(rowIndex, "");
}
// filter selection may have consequences for the filter value column GUI, so
// fire a change there
fireTableChanged(new TableModelEvent(this, rowIndex, rowIndex, COLUMN_FILTER_VALUE_INDEX,
TableModelEvent.UPDATE));
}
break;
case COLUMN_FILTER_VALUE_INDEX:
if (!(aValue instanceof String)) {
throw new IllegalArgumentException("aValue must be of class String!");
}
rowListOfSelectedValues.set(rowIndex, String.valueOf(aValue));
break;
default:
throw new IllegalArgumentException("Invalid columnIndex: " + columnIndex);
}
}
@Override
public void appendRow() {
rowsCount++;
rowListOfSelectedAttributes.add(listOfAttributeMetaData.size() <= 0 ? null : listOfAttributeMetaData.get(0)
.getName());
// see if we have a valueType and set comparator accordingly
int valueType = listOfAttributeMetaData.size() <= 0 ? Ontology.ATTRIBUTE_VALUE : listOfAttributeMetaData.get(0)
.getValueType();
if (Ontology.ATTRIBUTE_VALUE_TYPE.isA(valueType, Ontology.NOMINAL)) {
rowListOfSelectedComparators.add(CustomFilters.EQUALS_NOMINAL.getSymbol());
} else if (Ontology.ATTRIBUTE_VALUE_TYPE.isA(valueType, Ontology.NUMERICAL)
|| Ontology.ATTRIBUTE_VALUE_TYPE.isA(valueType, Ontology.DATE_TIME)) {
rowListOfSelectedComparators.add(CustomFilters.EQUALS_NUMERICAL.getSymbol());
} else {
rowListOfSelectedComparators.add(CustomFilters.CONTAINS.getSymbol());
}
rowListOfSelectedValues.add("");
fireTableStructureChanged();
}
@Override
public void removeRow(int rowIndex) {
if (rowIndex < 0 || rowIndex >= rowsCount) {
throw new IllegalArgumentException("Invalid rowIndex: " + rowIndex);
}
if (rowsCount <= 0) {
return;
}
rowsCount--;
rowListOfSelectedAttributes.remove(rowIndex);
rowListOfSelectedComparators.remove(rowIndex);
rowListOfSelectedValues.remove(rowIndex);
fireTableStructureChanged();
}
@Override
public List<String> getPossibleValuesForCellOrNull(int rowIndex, int columnIndex) throws IllegalArgumentException {
if (rowIndex < 0 || rowIndex >= getRowCount()) {
throw new IllegalArgumentException("Invalid rowIndex: " + rowIndex);
}
if (columnIndex < 0 || columnIndex >= getColumnCount()) {
throw new IllegalArgumentException("Invalid columnIndex: " + columnIndex);
}
List<String> possibleValues = null;
// attribute column
if (columnIndex == COLUMN_ATTRIBUTES_INDEX) {
possibleValues = new LinkedList<>();
for (AttributeMetaData md : listOfAttributeMetaData) {
possibleValues.add(md.getName());
}
}
// custom filter column
if (columnIndex == COLUMN_CUSTOM_FILTER_INDEX) {
possibleValues = new LinkedList<>();
String selectedAttributeName = String.valueOf(getValueAt(rowIndex, COLUMN_ATTRIBUTES_INDEX));
int valueType = Ontology.ATTRIBUTE_VALUE;
if (checkMetaDataForComparators) {
// only when possible values should depend on meta data
// see if the attribute has been set to an existing one so the value type can be
// obtained
for (AttributeMetaData md : listOfAttributeMetaData) {
if (md.getName().equals(selectedAttributeName)) {
valueType = md.getValueType();
break;
}
}
}
for (CustomFilters filter : CustomFilters.getFiltersForValueType(valueType)) {
possibleValues.add(filter.getLabel());
}
}
// filter value column
if (columnIndex == COLUMN_FILTER_VALUE_INDEX) {
String selectedAttributeName = String.valueOf(getValueAt(rowIndex, COLUMN_ATTRIBUTES_INDEX));
AttributeMetaData attMD = null;
// see if the attribute has been set to an existing one so the meta data can be obtained
for (AttributeMetaData md : listOfAttributeMetaData) {
if (md.getName().equals(selectedAttributeName)) {
// content assist for value column is always possible except for unknown meta
// data
attMD = md;
break;
}
}
if (attMD != null) {
possibleValues = new LinkedList<>();
if (attMD.isNominal()) {
for (String value : attMD.getValueSet()) {
possibleValues.add(value);
}
} else {
// we only have a range so we can only add lower and upper bounds for numerical
// values
double upperBound = attMD.getValueRange().getUpper();
double lowerBound = attMD.getValueRange().getLower();
if (Ontology.ATTRIBUTE_VALUE_TYPE.isA(attMD.getValueType(), Ontology.DATE)) {
possibleValues.add(FORMAT_DATE.format(new Date((long) lowerBound)));
possibleValues.add(FORMAT_DATE.format(new Date((long) upperBound)));
} else if (Ontology.ATTRIBUTE_VALUE_TYPE.isA(attMD.getValueType(), Ontology.TIME)) {
possibleValues.add(FORMAT_TIME.format(new Date((long) lowerBound)));
possibleValues.add(FORMAT_TIME.format(new Date((long) upperBound)));
} else if (Ontology.ATTRIBUTE_VALUE_TYPE.isA(attMD.getValueType(), Ontology.DATE_TIME)) {
possibleValues.add(FORMAT_DATE_TIME.format(new Date((long) lowerBound)));
possibleValues.add(FORMAT_DATE_TIME.format(new Date((long) upperBound)));
} else {
possibleValues.add(String.valueOf(lowerBound) + " "
+ I18N.getMessage(I18N.getGUIBundle(), "gui.dialog.filter_table_model.lower_bound.title"));
possibleValues.add(String.valueOf(upperBound) + " "
+ I18N.getMessage(I18N.getGUIBundle(), "gui.dialog.filter_table_model.upper_bound.title"));
}
}
}
}
return possibleValues;
}
@Override
public boolean isContentAssistPossibleForCell(int rowIndex, int columnIndex) {
if (rowIndex < 0 || rowIndex >= getRowCount()) {
throw new IllegalArgumentException("Invalid rowIndex: " + rowIndex);
}
if (columnIndex < 0 || columnIndex >= getColumnCount()) {
throw new IllegalArgumentException("Invalid columnIndex: " + columnIndex);
}
if (columnIndex == COLUMN_FILTER_VALUE_INDEX) {
String selectedAttributeName = String.valueOf(getValueAt(rowIndex, COLUMN_ATTRIBUTES_INDEX));
// see if the attribute has been set to an existing one so the meta data can be obtained
for (AttributeMetaData md : listOfAttributeMetaData) {
if (md.getName().equals(selectedAttributeName)) {
// content assist for value column is always possible except for unknown meta
// data
return true;
}
}
}
return false;
}
@Override
public boolean canCellHaveMultipleValues(int rowIndex, int columnIndex) {
if (rowIndex < 0 || rowIndex >= getRowCount()) {
throw new IllegalArgumentException("Invalid rowIndex: " + rowIndex);
}
if (columnIndex < 0 || columnIndex >= getColumnCount()) {
throw new IllegalArgumentException("Invalid columnIndex: " + columnIndex);
}
if (!isContentAssistPossibleForCell(rowIndex, columnIndex)) {
return false;
}
if (columnIndex == COLUMN_FILTER_VALUE_INDEX) {
CustomFilters filter = CustomFilters
.getByLabel(String.valueOf(getValueAt(rowIndex, COLUMN_CUSTOM_FILTER_INDEX)));
// condition with == is intended, it's an enum
if (filter == CustomFilters.IS_IN_NOMINAL || filter == CustomFilters.IS_NOT_IN_NOMINAL) {
// multiple values are only allowed for these filters
return true;
}
}
return false;
}
@Override
public List<String> convertEncodedStringValueToList(String encodedValue) {
List<String> result;
try {
result = Tools.unescape(encodedValue, CustomFilters.ESCAPE_CHAR, new char[] { CustomFilters.SEPERATOR_CHAR },
CustomFilters.SEPERATOR_CHAR);
} catch (IllegalArgumentException e) {
// happens when there is a char escaped via one backslash which is no special char,
// ignore it
return Collections.<String> emptyList();
}
return result;
}
@Override
public String encodeListOfStringsToValue(List<String> listOfStrings) {
StringBuilder builder = new StringBuilder();
for (int i = 0; i < listOfStrings.size(); i++) {
if (i > 0) {
builder.append(CustomFilters.SEPERATOR_CHAR);
}
if (listOfStrings.get(i) != null) {
builder.append(Tools.escape(listOfStrings.get(i), CustomFilters.ESCAPE_CHAR,
new char[] { CustomFilters.SEPERATOR_CHAR }));
}
}
return builder.toString();
}
@Override
public List<String[]> getRowTupels() {
List<String[]> list = new LinkedList<>();
for (int row = 0; row < getRowCount(); row++) {
// conversion because getValue() returns the filter label (used by GUI which displays
// the I18N label instead of the symbol)
// but getTupels() expects the filter symbols
CustomFilters filter = CustomFilters.getByLabel(String.valueOf(getValueAt(row, COLUMN_CUSTOM_FILTER_INDEX)));
String filterSymbol = filter != null ? filter.getSymbol() : "eq";
list.add(new String[] { String.valueOf(getValueAt(row, COLUMN_ATTRIBUTES_INDEX)), filterSymbol,
String.valueOf(getValueAt(row, COLUMN_FILTER_VALUE_INDEX)) });
}
return list;
}
@Override
public void setRowTupels(List<String[]> tupelList) {
creatingModel.set(true);
// remove existing rows (if there are any)
for (int row = getRowCount() - 1; row >= 0; row--) {
removeRow(row);
}
// add new rows
for (int row = 0; row < tupelList.size(); row++) {
appendRow();
String[] tupel = tupelList.get(row);
// conversion because setValue() takes the filter label (used by GUI which displays the
// I18N label instead of the symbol)
// but setTupels() gets called with the filter symbols
CustomFilters filter = CustomFilters.getBySymbol(tupel[1]);
String filterLabel = filter != null ? filter.getLabel() : "=";
setValueAt(tupel[0], row, COLUMN_ATTRIBUTES_INDEX);
setValueAt(filterLabel, row, COLUMN_CUSTOM_FILTER_INDEX);
setValueAt(tupel[2], row, COLUMN_FILTER_VALUE_INDEX);
}
creatingModel.set(false);
fireTableStructureChanged();
}
@Override
public void fireTableChanged(TableModelEvent e) {
// don't fire while we are creating the model
if (creatingModel.get()) {
return;
}
super.fireTableChanged(e);
}
@Override
public void fireTableStructureChanged() {
// don't fire while we are creating the model
if (creatingModel.get()) {
return;
}
super.fireTableStructureChanged();
}
/**
* Sets checkMetaDataForComparators and fires an update if it has been changed.
*
* @param checkMetaDataForComparators
* the flag which decides whether the meta data should be checked for filter
* comparator preselection
*/
public void setCheckMetaDataForComparators(boolean checkMetaDataForComparators) {
if (this.checkMetaDataForComparators != checkMetaDataForComparators) {
this.checkMetaDataForComparators = checkMetaDataForComparators;
for (int row = 0; row < rowsCount; row++) {
fireTableCellUpdated(row, FilterTableModel.COLUMN_CUSTOM_FILTER_INDEX);
}
}
}
}