diff options
| author | Dirk Fauth | 2023-01-16 13:24:08 +0000 |
|---|---|---|
| committer | Dirk Fauth | 2023-01-16 13:24:08 +0000 |
| commit | 1695769432518c10d4e0991d43c0e06e334d7d71 (patch) | |
| tree | 76a34b45bd2972a24214dae2d80ae16cdd6946ac | |
| parent | 381fb194f968e5de1b0886f432c6a95dc4960bfd (diff) | |
| download | org.eclipse.nebula.widgets.nattable-1695769432518c10d4e0991d43c0e06e334d7d71.tar.gz org.eclipse.nebula.widgets.nattable-1695769432518c10d4e0991d43c0e06e334d7d71.tar.xz org.eclipse.nebula.widgets.nattable-1695769432518c10d4e0991d43c0e06e334d7d71.zip | |
Bug 581362 - Create a filter strategy with excludes
Signed-off-by: Dirk Fauth <dirk.fauth@googlemail.com>
Change-Id: I8b0500661b352475c99fbf8da6d0fe696c0a37c7
3 files changed, 336 insertions, 121 deletions
diff --git a/org.eclipse.nebula.widgets.nattable.examples/src/org/eclipse/nebula/widgets/nattable/examples/_600_GlazedLists/_603_Filter/_6037_MixedFilterRowExample.java b/org.eclipse.nebula.widgets.nattable.examples/src/org/eclipse/nebula/widgets/nattable/examples/_600_GlazedLists/_603_Filter/_6037_MixedFilterRowExample.java index 05069903..9ae837ca 100644 --- a/org.eclipse.nebula.widgets.nattable.examples/src/org/eclipse/nebula/widgets/nattable/examples/_600_GlazedLists/_603_Filter/_6037_MixedFilterRowExample.java +++ b/org.eclipse.nebula.widgets.nattable.examples/src/org/eclipse/nebula/widgets/nattable/examples/_600_GlazedLists/_603_Filter/_6037_MixedFilterRowExample.java @@ -15,7 +15,6 @@ package org.eclipse.nebula.widgets.nattable.examples._600_GlazedLists._603_Filte import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -35,7 +34,6 @@ import org.eclipse.nebula.widgets.nattable.config.DefaultNatTableStyleConfigurat import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry; import org.eclipse.nebula.widgets.nattable.config.IEditableRule; import org.eclipse.nebula.widgets.nattable.data.ExtendedReflectiveColumnPropertyAccessor; -import org.eclipse.nebula.widgets.nattable.data.IColumnAccessor; import org.eclipse.nebula.widgets.nattable.data.IColumnPropertyAccessor; import org.eclipse.nebula.widgets.nattable.data.IDataProvider; import org.eclipse.nebula.widgets.nattable.data.IRowIdAccessor; @@ -60,7 +58,7 @@ import org.eclipse.nebula.widgets.nattable.examples.AbstractNatExample; import org.eclipse.nebula.widgets.nattable.examples.runner.StandaloneNatExampleRunner; import org.eclipse.nebula.widgets.nattable.extension.glazedlists.GlazedListsEventLayer; import org.eclipse.nebula.widgets.nattable.extension.glazedlists.filterrow.ComboBoxFilterRowHeaderComposite; -import org.eclipse.nebula.widgets.nattable.extension.glazedlists.filterrow.ComboBoxGlazedListsFilterStrategy; +import org.eclipse.nebula.widgets.nattable.extension.glazedlists.filterrow.ComboBoxGlazedListsWithExcludeFilterStrategy; import org.eclipse.nebula.widgets.nattable.extension.glazedlists.filterrow.FilterRowUtils; import org.eclipse.nebula.widgets.nattable.extension.glazedlists.filterrow.GlazedListsFilterRowComboBoxDataProvider; import org.eclipse.nebula.widgets.nattable.filterrow.FilterRowDataLayer; @@ -140,9 +138,7 @@ import ca.odell.glazedlists.FilterList; import ca.odell.glazedlists.GlazedLists; import ca.odell.glazedlists.SortedList; import ca.odell.glazedlists.TransformedList; -import ca.odell.glazedlists.matchers.CompositeMatcherEditor; import ca.odell.glazedlists.matchers.Matcher; -import ca.odell.glazedlists.matchers.MatcherEditor; /** * Example showing how to add the filter row to the layer composition of a grid @@ -614,119 +610,6 @@ public class _6037_MixedFilterRowExample extends AbstractNatExample { } } - // TODO 2.1 move this class to the GlazedLists extension - /** - * Specialized {@link ComboBoxGlazedListsFilterStrategy} that can be used to - * exclude items from filtering. This means you can register a - * {@link Matcher} that avoids that matching items get filtered by the - * filterrow. - * - * @param <T> - */ - class ComboBoxGlazedListsWithExcludeFilterStrategy<T> extends ComboBoxGlazedListsFilterStrategy<T> { - - private final CompositeMatcherEditor<T> compositeMatcherEditor; - - protected Map<Matcher<T>, MatcherEditor<T>> excludeMatcherEditor = new HashMap<>(); - - public ComboBoxGlazedListsWithExcludeFilterStrategy( - FilterRowComboBoxDataProvider<T> comboBoxDataProvider, - FilterList<T> filterList, - IColumnAccessor<T> columnAccessor, - IConfigRegistry configRegistry) { - super(comboBoxDataProvider, filterList, columnAccessor, configRegistry); - - // The default MatcherEditor is created and stored as member in the - // DefaultGlazedListsFilterStrategy. That MatcherEditor is used for - // the default filter operations. To exclude entries from filtering, - // we create another CompositeMatcherEditor with an OR mode and set - // that one on the FilterList. - this.compositeMatcherEditor = new CompositeMatcherEditor<>(); - this.compositeMatcherEditor.setMode(CompositeMatcherEditor.OR); - - this.compositeMatcherEditor.getMatcherEditors().add(getMatcherEditor()); - - this.filterList.setMatcherEditor(this.compositeMatcherEditor); - } - - /** - * Add a exclude filter to this filter strategy which will always be - * applied additionally to any other filter to exclude from filtering. - * - * @param matcher - * the exclude filter to add - */ - public void addExcludeFilter(final Matcher<T> matcher) { - // create a new MatcherEditor - MatcherEditor<T> matcherEditor = GlazedLists.fixedMatcherEditor(matcher); - addExcludeFilter(matcherEditor); - } - - /** - * Add a exclude filter to this filter strategy which will always be - * applied additionally to any other filter to exclude items from - * filtering. - * - * @param matcherEditor - * the exclude filter to add - */ - public void addExcludeFilter(final MatcherEditor<T> matcherEditor) { - // add the new MatcherEditor to the CompositeMatcherEditor - this.filterLock.writeLock().lock(); - try { - this.compositeMatcherEditor.getMatcherEditors().add(matcherEditor); - } finally { - this.filterLock.writeLock().unlock(); - } - - this.excludeMatcherEditor.put(matcherEditor.getMatcher(), matcherEditor); - } - - /** - * Remove the exclude filter from this filter strategy. - * - * @param matcher - * the filter to remove - */ - public void removeExcludeFilter(final Matcher<T> matcher) { - MatcherEditor<T> removed = this.excludeMatcherEditor.remove(matcher); - if (removed != null) { - this.filterLock.writeLock().lock(); - try { - this.compositeMatcherEditor.getMatcherEditors().remove(removed); - } finally { - this.filterLock.writeLock().unlock(); - } - } - } - - /** - * Remove the exclude filter from this filter strategy. - * - * @param matcherEditor - * the filter to remove - */ - public void removeExcludeFilter(final MatcherEditor<T> matcherEditor) { - removeExcludeFilter(matcherEditor.getMatcher()); - } - - /** - * Removes all applied exclude filters from this filter strategy. - */ - public void clearExcludeFilter() { - Collection<MatcherEditor<T>> excludeMatcher = this.excludeMatcherEditor.values(); - if (!excludeMatcher.isEmpty()) { - this.filterLock.writeLock().lock(); - try { - this.compositeMatcherEditor.getMatcherEditors().removeAll(excludeMatcher); - } finally { - this.filterLock.writeLock().unlock(); - } - this.excludeMatcherEditor.clear(); - } - } - } - /** * The configuration to enable editing of {@link PersonWithAddress} objects. */ @@ -849,9 +732,14 @@ public class _6037_MixedFilterRowExample extends AbstractNatExample { FilterRowDataLayer.FILTER_ROW_COLUMN_LABEL_PREFIX + DataModelConstants.FIRSTNAME_COLUMN_POSITION); - // register the FilterRowRegularExpressionConverter in the first - // column that converts simple expressions like wildcards to valid - // regular expressions + // register display converters for the first column to support an + // unidirectional conversion of user friendly strings to complex + // regular expressions. + + // CellConfigAttributes.DISPLAY_CONVERTER is needed for editing. + // Using the DefaultDisplayConverter will simply take the entered + // value to the data model. That means, the filter row contains + // exactly the value that was entered by the user. configRegistry.registerConfigAttribute( CellConfigAttributes.DISPLAY_CONVERTER, new DefaultDisplayConverter(), @@ -859,6 +747,10 @@ public class _6037_MixedFilterRowExample extends AbstractNatExample { FilterRowDataLayer.FILTER_ROW_COLUMN_LABEL_PREFIX + DataModelConstants.FIRSTNAME_COLUMN_POSITION); + // FilterRowConfigAttributes.FILTER_DISPLAY_CONVERTER is used to + // convert the value in the filter row to a filter string. It is + // used for the unidirectional conversion of the user value to a + // complex regular expression. configRegistry.registerConfigAttribute( FilterRowConfigAttributes.FILTER_DISPLAY_CONVERTER, new CustomFilterRowRegularExpressionConverter(), @@ -866,6 +758,12 @@ public class _6037_MixedFilterRowExample extends AbstractNatExample { FilterRowDataLayer.FILTER_ROW_COLUMN_LABEL_PREFIX + DataModelConstants.FIRSTNAME_COLUMN_POSITION); + // FilterRowConfigAttributes.FILTER_CONTENT_DISPLAY_CONVERTER is + // needed to convert the body data. This is necessary as the filter + // row does not know about the display converter in the body. If it + // is not set it would use the FILTER_DISPLAY_CONVERTER, which would + // cause issues in the further processing for the regular expression + // conversion. configRegistry.registerConfigAttribute( FilterRowConfigAttributes.FILTER_CONTENT_DISPLAY_CONVERTER, new DefaultDisplayConverter(), diff --git a/org.eclipse.nebula.widgets.nattable.extension.glazedlists.test/src/org/eclipse/nebula/widgets/nattable/extension/glazedlists/filterrow/ComboBoxGlazedListsWithExcludeFilterStrategyTest.java b/org.eclipse.nebula.widgets.nattable.extension.glazedlists.test/src/org/eclipse/nebula/widgets/nattable/extension/glazedlists/filterrow/ComboBoxGlazedListsWithExcludeFilterStrategyTest.java new file mode 100644 index 00000000..ab0572af --- /dev/null +++ b/org.eclipse.nebula.widgets.nattable.extension.glazedlists.test/src/org/eclipse/nebula/widgets/nattable/extension/glazedlists/filterrow/ComboBoxGlazedListsWithExcludeFilterStrategyTest.java @@ -0,0 +1,156 @@ +/******************************************************************************* + * Copyright (c) 2023 Dirk Fauth. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Dirk Fauth <dirk.fauth@googlemail.com> - initial API and implementation + ******************************************************************************/ +package org.eclipse.nebula.widgets.nattable.extension.glazedlists.filterrow; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Arrays; + +import org.eclipse.nebula.widgets.nattable.config.ConfigRegistry; +import org.eclipse.nebula.widgets.nattable.config.DefaultNatTableStyleConfiguration; +import org.eclipse.nebula.widgets.nattable.data.IColumnAccessor; +import org.eclipse.nebula.widgets.nattable.data.ListDataProvider; +import org.eclipse.nebula.widgets.nattable.data.ReflectiveColumnPropertyAccessor; +import org.eclipse.nebula.widgets.nattable.dataset.person.Person; +import org.eclipse.nebula.widgets.nattable.dataset.person.PersonService; +import org.eclipse.nebula.widgets.nattable.edit.EditConstants; +import org.eclipse.nebula.widgets.nattable.extension.glazedlists.fixture.DataLayerFixture; +import org.eclipse.nebula.widgets.nattable.filterrow.FilterRowDataProvider; +import org.eclipse.nebula.widgets.nattable.filterrow.combobox.ComboBoxFilterRowConfiguration; +import org.eclipse.nebula.widgets.nattable.filterrow.combobox.FilterRowComboBoxDataProvider; +import org.eclipse.nebula.widgets.nattable.filterrow.config.DefaultFilterRowConfiguration; +import org.eclipse.nebula.widgets.nattable.layer.DataLayer; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import ca.odell.glazedlists.EventList; +import ca.odell.glazedlists.FilterList; +import ca.odell.glazedlists.GlazedLists; +import ca.odell.glazedlists.matchers.Matcher; + +public class ComboBoxGlazedListsWithExcludeFilterStrategyTest { + + private FilterList<Person> filterList; + + private ConfigRegistry configRegistry; + private DataLayerFixture columnHeaderLayer; + private FilterRowComboBoxDataProvider<Person> comboBoxDataProvider; + private ComboBoxGlazedListsWithExcludeFilterStrategy<Person> filterStrategy; + private FilterRowDataProvider<Person> dataProvider; + + private static String[] personPropertyNames = { + "firstName", + "lastName", + "gender", + "married", + "birthday" }; + + // Homer should never be filtered + private static Matcher<Person> homerFilter = item -> "Homer".equals(item.getFirstName()); + + @BeforeEach + public void setup() { + // initialize the collection with a big amount of values + EventList<Person> baseCollection = GlazedLists.eventList(PersonService.getFixedPersons()); + for (int i = 1; i < 1000; i++) { + baseCollection.addAll(PersonService.getFixedPersons()); + } + this.filterList = new FilterList<>(GlazedLists.eventList(baseCollection)); + + this.configRegistry = new ConfigRegistry(); + + new DefaultNatTableStyleConfiguration().configureRegistry(this.configRegistry); + new DefaultFilterRowConfiguration().configureRegistry(this.configRegistry); + new ComboBoxFilterRowConfiguration().configureRegistry(this.configRegistry); + + this.columnHeaderLayer = new DataLayerFixture(5, 2, 100, 50); + + IColumnAccessor<Person> bodyDataColumnAccessor = new ReflectiveColumnPropertyAccessor<>(personPropertyNames); + this.comboBoxDataProvider = new GlazedListsFilterRowComboBoxDataProvider<>( + new DataLayer(new ListDataProvider<>(this.filterList, bodyDataColumnAccessor)), + baseCollection, + bodyDataColumnAccessor); + + this.filterStrategy = new ComboBoxGlazedListsWithExcludeFilterStrategy<>( + this.comboBoxDataProvider, + this.filterList, + bodyDataColumnAccessor, + this.configRegistry); + this.dataProvider = new FilterRowDataProvider<>( + this.filterStrategy, + this.columnHeaderLayer, + this.columnHeaderLayer.getDataProvider(), this.configRegistry); + for (int i = 0; i < this.dataProvider.getColumnCount(); i++) { + this.dataProvider.getFilterIndexToObjectMap().put(i, EditConstants.SELECT_ALL_ITEMS_VALUE); + } + this.filterStrategy.applyFilter(this.dataProvider.getFilterIndexToObjectMap()); + } + + @Test + public void shouldFilterForFlanders() { + assertEquals(18000, this.filterList.size()); + + this.dataProvider.setDataValue(1, 1, Arrays.asList("Flanders")); + + assertEquals(8000, this.filterList.size()); + } + + // with exclude filter + + @Test + public void shouldFilterForFlandersWithHomerExcludeFilter() { + this.filterStrategy.addExcludeFilter(homerFilter); + assertEquals(18000, this.filterList.size()); + + this.dataProvider.setDataValue(1, 1, Arrays.asList("Flanders")); + // we now expect 8000 Flanders and the 3000 Homers that are excluded + // from filtering + assertEquals(11000, this.filterList.size()); + + this.filterStrategy.removeExcludeFilter(homerFilter); + // if Homer is not excluded anymore, only Flanders should be there + assertEquals(8000, this.filterList.size()); + } + + @Test + public void shouldFilterForFlandersWithHomerExcludeFilterAndClear() { + this.filterStrategy.addExcludeFilter(homerFilter); + assertEquals(18000, this.filterList.size()); + + this.dataProvider.setDataValue(1, 1, Arrays.asList("Flanders")); + // we now expect 8000 Flanders and the 3000 Homers that are excluded + // from filtering + assertEquals(11000, this.filterList.size()); + + this.filterStrategy.clearExcludeFilter(); + // if Homer is not excluded anymore, only Flanders should be there + assertEquals(8000, this.filterList.size()); + } + + @Test + public void shouldFilterForNedFlandersWithHomerExcludeFilter() { + this.filterStrategy.addExcludeFilter(homerFilter); + assertEquals(18000, this.filterList.size()); + + this.dataProvider.setDataValue(0, 1, Arrays.asList("Ned")); + this.dataProvider.setDataValue(1, 1, Arrays.asList("Flanders")); + // we now expect 2000 Ned Flanders and the 3000 Homers that are excluded + // from filtering + assertEquals(5000, this.filterList.size()); + + this.filterStrategy.clearExcludeFilter(); + // if Homer is not excluded anymore, only Ned Flanders should be there + assertEquals(2000, this.filterList.size()); + } + +} diff --git a/org.eclipse.nebula.widgets.nattable.extension.glazedlists/src/org/eclipse/nebula/widgets/nattable/extension/glazedlists/filterrow/ComboBoxGlazedListsWithExcludeFilterStrategy.java b/org.eclipse.nebula.widgets.nattable.extension.glazedlists/src/org/eclipse/nebula/widgets/nattable/extension/glazedlists/filterrow/ComboBoxGlazedListsWithExcludeFilterStrategy.java new file mode 100644 index 00000000..fcd89433 --- /dev/null +++ b/org.eclipse.nebula.widgets.nattable.extension.glazedlists/src/org/eclipse/nebula/widgets/nattable/extension/glazedlists/filterrow/ComboBoxGlazedListsWithExcludeFilterStrategy.java @@ -0,0 +1,161 @@ +/******************************************************************************* + * Copyright (c) 2023 Dirk Fauth and others. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Dirk Fauth <dirk.fauth@googlemail.com> - initial API and implementation + *******************************************************************************/ +package org.eclipse.nebula.widgets.nattable.extension.glazedlists.filterrow; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry; +import org.eclipse.nebula.widgets.nattable.data.IColumnAccessor; +import org.eclipse.nebula.widgets.nattable.filterrow.combobox.FilterRowComboBoxDataProvider; + +import ca.odell.glazedlists.FilterList; +import ca.odell.glazedlists.GlazedLists; +import ca.odell.glazedlists.matchers.CompositeMatcherEditor; +import ca.odell.glazedlists.matchers.Matcher; +import ca.odell.glazedlists.matchers.MatcherEditor; + +/** + * Specialized {@link ComboBoxGlazedListsFilterStrategy} that can be used to + * exclude items from filtering. This means you can register a {@link Matcher} + * that avoids that matching items get filtered by the filter row. + * + * @param <T> + * + * @since 2.1 + */ +public class ComboBoxGlazedListsWithExcludeFilterStrategy<T> extends ComboBoxGlazedListsFilterStrategy<T> { + + private final CompositeMatcherEditor<T> compositeMatcherEditor; + + protected Map<Matcher<T>, MatcherEditor<T>> excludeMatcherEditor = new HashMap<>(); + + /** + * + * @param comboBoxDataProvider + * The FilterRowComboBoxDataProvider needed to determine whether + * filters should applied or not. If there are no values + * specified for filtering of a column then everything should be + * filtered, if all possible values are given as filter then no + * filter needs to be applied. + * @param filterList + * The CompositeMatcherEditor that is used for GlazedLists + * filtering + * @param columnAccessor + * The IColumnAccessor needed to access the row data to perform + * filtering + * @param configRegistry + * The IConfigRegistry to retrieve several configurations from + */ + public ComboBoxGlazedListsWithExcludeFilterStrategy( + FilterRowComboBoxDataProvider<T> comboBoxDataProvider, + FilterList<T> filterList, + IColumnAccessor<T> columnAccessor, + IConfigRegistry configRegistry) { + super(comboBoxDataProvider, filterList, columnAccessor, configRegistry); + + // The default MatcherEditor is created and stored as member in the + // DefaultGlazedListsFilterStrategy. That MatcherEditor is used for + // the default filter operations. To exclude entries from filtering, + // we create another CompositeMatcherEditor with an OR mode and set + // that one on the FilterList. + // Note that we need both, the default MatcherEditor from the + // DefaultGlazedListsFilterStrategy for the user filters that are + // combined with AND mode, and the MatcherEditor with OR mode that + // combines the user filters with the exclude MatcherEditors registered + // via this strategy. + this.compositeMatcherEditor = new CompositeMatcherEditor<>(); + this.compositeMatcherEditor.setMode(CompositeMatcherEditor.OR); + + this.compositeMatcherEditor.getMatcherEditors().add(getMatcherEditor()); + + this.filterList.setMatcherEditor(this.compositeMatcherEditor); + } + + /** + * Add a exclude filter to this filter strategy which will always be applied + * additionally to any other filter to exclude from filtering. + * + * @param matcher + * the exclude filter to add + */ + public void addExcludeFilter(final Matcher<T> matcher) { + // create a new MatcherEditor + MatcherEditor<T> matcherEditor = GlazedLists.fixedMatcherEditor(matcher); + addExcludeFilter(matcherEditor); + } + + /** + * Add a exclude filter to this filter strategy which will always be applied + * additionally to any other filter to exclude items from filtering. + * + * @param matcherEditor + * the exclude filter to add + */ + public void addExcludeFilter(final MatcherEditor<T> matcherEditor) { + // add the new MatcherEditor to the CompositeMatcherEditor + this.filterLock.writeLock().lock(); + try { + this.compositeMatcherEditor.getMatcherEditors().add(matcherEditor); + } finally { + this.filterLock.writeLock().unlock(); + } + + this.excludeMatcherEditor.put(matcherEditor.getMatcher(), matcherEditor); + } + + /** + * Remove the exclude filter from this filter strategy. + * + * @param matcher + * the filter to remove + */ + public void removeExcludeFilter(final Matcher<T> matcher) { + MatcherEditor<T> removed = this.excludeMatcherEditor.remove(matcher); + if (removed != null) { + this.filterLock.writeLock().lock(); + try { + this.compositeMatcherEditor.getMatcherEditors().remove(removed); + } finally { + this.filterLock.writeLock().unlock(); + } + } + } + + /** + * Remove the exclude filter from this filter strategy. + * + * @param matcherEditor + * the filter to remove + */ + public void removeExcludeFilter(final MatcherEditor<T> matcherEditor) { + removeExcludeFilter(matcherEditor.getMatcher()); + } + + /** + * Removes all applied exclude filters from this filter strategy. + */ + public void clearExcludeFilter() { + Collection<MatcherEditor<T>> excludeMatcher = this.excludeMatcherEditor.values(); + if (!excludeMatcher.isEmpty()) { + this.filterLock.writeLock().lock(); + try { + this.compositeMatcherEditor.getMatcherEditors().removeAll(excludeMatcher); + } finally { + this.filterLock.writeLock().unlock(); + } + this.excludeMatcherEditor.clear(); + } + } +}
\ No newline at end of file |
