From c75f71c74a53c0ca538da4785589ffb756dc0f2e Mon Sep 17 00:00:00 2001 From: Teemu Suo-Anttila Date: Mon, 21 Nov 2016 17:35:19 +0200 Subject: [PATCH] Add DataProvider.convertFilter, fix ListDataProvider filtering Change-Id: Ic90ae83acf5d77aa9b0f485dff4e55bba5296fa7 --- .../vaadin/server/data/DataCommunicator.java | 15 ++-- .../com/vaadin/server/data/DataProvider.java | 34 +++++++++ .../data/FilteringDataProviderWrapper.java | 75 +++++++++++++++++++ .../vaadin/server/data/ListDataProvider.java | 17 ++--- .../java/com/vaadin/server/data/Query.java | 5 +- .../data/provider/ListDataProviderTest.java | 72 ++++++++++++------ .../listselect/ListSelectAddRemoveItems.java | 18 +++-- 7 files changed, 183 insertions(+), 53 deletions(-) create mode 100644 server/src/main/java/com/vaadin/server/data/FilteringDataProviderWrapper.java diff --git a/server/src/main/java/com/vaadin/server/data/DataCommunicator.java b/server/src/main/java/com/vaadin/server/data/DataCommunicator.java index fc66185aee..852c583a56 100644 --- a/server/src/main/java/com/vaadin/server/data/DataCommunicator.java +++ b/server/src/main/java/com/vaadin/server/data/DataCommunicator.java @@ -18,7 +18,6 @@ package com.vaadin.server.data; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.LinkedHashSet; @@ -223,16 +222,14 @@ public class DataCommunicator extends AbstractExtension { return; } - // FIXME: Sorting and Filtering with Backend - Set filters = Collections.emptySet(); - if (initial || reset) { int dataProviderSize; if (getDataProvider().isInMemory() && inMemoryFilter != null) { - dataProviderSize = (int) getDataProvider().fetch(new Query()) + dataProviderSize = (int) getDataProvider().fetch(new Query<>()) .filter(inMemoryFilter).count(); } else { - dataProviderSize = getDataProvider().size(new Query(filters)); + // TODO: Apply filter + dataProviderSize = getDataProvider().size(new Query<>()); } rpc.reset(dataProviderSize); } @@ -245,7 +242,7 @@ public class DataCommunicator extends AbstractExtension { if (getDataProvider().isInMemory()) { // We can safely request all the data when in memory - rowsToPush = getDataProvider().fetch(new Query()); + rowsToPush = getDataProvider().fetch(new Query<>()); if (inMemoryFilter != null) { rowsToPush = rowsToPush.filter(inMemoryFilter); } @@ -254,8 +251,8 @@ public class DataCommunicator extends AbstractExtension { } rowsToPush = rowsToPush.skip(offset).limit(limit); } else { - Query query = new Query(offset, limit, backEndSorting, filters); - rowsToPush = getDataProvider().fetch(query); + rowsToPush = getDataProvider().fetch( + new Query<>(offset, limit, backEndSorting, null)); } pushData(offset, rowsToPush); } diff --git a/server/src/main/java/com/vaadin/server/data/DataProvider.java b/server/src/main/java/com/vaadin/server/data/DataProvider.java index 441a57b559..19c657be70 100644 --- a/server/src/main/java/com/vaadin/server/data/DataProvider.java +++ b/server/src/main/java/com/vaadin/server/data/DataProvider.java @@ -20,6 +20,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.stream.Stream; +import com.vaadin.server.SerializableFunction; import com.vaadin.shared.Registration; /** @@ -88,6 +89,39 @@ public interface DataProvider extends Serializable { */ Registration addDataProviderListener(DataProviderListener listener); + /** + * Convert the data provider to use a different filter type. It is used for + * adapting this data provider to a filter type provided by a Component such + * as ComboBox. + *

+ * For example receiving a String from ComboBox and making a Predicate based + * on it: + * + *

+     * DataProvider<Person, Predicate<Person>> dataProvider;
+     * // ComboBox uses String as the filter type
+     * DataProvider<Person, String> wrappedProvider = dataProvider
+     *         .convertFilter(filterText -> {
+     *             Predicate<Person> predicate = person -> person.getName()
+     *                     .startsWith(filterText);
+     *             return predicate;
+     *         });
+     * comboBox.setDataProvider(wrappedProvider);
+     * 
+ * + * @param mapper + * the mapper from new filter type to old filter type + * + * @param + * the filter type to map from; typically provided by a Component + * + * @return wrapped data provider + */ + default DataProvider convertFilter( + SerializableFunction mapper) { + return new FilteringDataProviderWrapper<>(this, mapper); + } + /** * This method creates a new {@link ListDataProvider} from a given * Collection. The ListDataProvider creates a protective List copy of all diff --git a/server/src/main/java/com/vaadin/server/data/FilteringDataProviderWrapper.java b/server/src/main/java/com/vaadin/server/data/FilteringDataProviderWrapper.java new file mode 100644 index 0000000000..10384a9c9c --- /dev/null +++ b/server/src/main/java/com/vaadin/server/data/FilteringDataProviderWrapper.java @@ -0,0 +1,75 @@ +/* + * Copyright 2000-2016 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.server.data; + +import java.util.stream.Stream; + +import com.vaadin.server.SerializableFunction; +import com.vaadin.shared.Registration; + +/** + * Wrapper class for modifying filters in a query. Used to create a suitable + * {@link Query} for the underlying data provider. + * + * @author Vaadin Ltd. + * + * @param + * data provider data type + * @param + * wrapper query filter type + * @param + * underlying data provider filter type + */ +public class FilteringDataProviderWrapper + implements DataProvider { + + private DataProvider dataProvider; + private SerializableFunction mapper; + + public FilteringDataProviderWrapper(DataProvider dataProvider, + SerializableFunction mapper) { + this.dataProvider = dataProvider; + this.mapper = mapper; + } + + @Override + public boolean isInMemory() { + return dataProvider.isInMemory(); + } + + @Override + public void refreshAll() { + dataProvider.refreshAll(); + } + + @Override + public Registration addDataProviderListener(DataProviderListener listener) { + return dataProvider.addDataProviderListener(listener); + } + + @Override + public int size(Query t) { + return dataProvider.size(new Query(t.getOffset(), t.getLimit(), + t.getSortOrders(), t.getFilter().map(mapper).orElse(null))); + } + + @Override + public Stream fetch(Query t) { + return dataProvider.fetch(new Query(t.getOffset(), t.getLimit(), + t.getSortOrders(), t.getFilter().map(mapper).orElse(null))); + } + +} diff --git a/server/src/main/java/com/vaadin/server/data/ListDataProvider.java b/server/src/main/java/com/vaadin/server/data/ListDataProvider.java index a67ce42eab..0184a2e37b 100644 --- a/server/src/main/java/com/vaadin/server/data/ListDataProvider.java +++ b/server/src/main/java/com/vaadin/server/data/ListDataProvider.java @@ -66,8 +66,9 @@ public class ListDataProvider } @Override - public Stream fetch(Query query) { - Stream stream = backend.stream(); + public Stream fetch(Query> query) { + Stream stream = backend.stream() + .filter(t -> query.getFilter().orElse(p -> true).test(t)); if (sortOrder != null) { stream = stream.sorted(sortOrder); } @@ -113,15 +114,11 @@ public class ListDataProvider return true; } - /** - * {@inheritDoc} - *

- * For in-memory data provider the query is not handled, and it will always - * return the full size. - */ @Override - public int size(Query query) { - return backend.size(); + public int size(Query> query) { + return (int) backend.stream() + .filter(t -> query.getFilter().orElse(p -> true).test(t)) + .count(); } } diff --git a/server/src/main/java/com/vaadin/server/data/Query.java b/server/src/main/java/com/vaadin/server/data/Query.java index 3762039a72..3aeaac6acc 100644 --- a/server/src/main/java/com/vaadin/server/data/Query.java +++ b/server/src/main/java/com/vaadin/server/data/Query.java @@ -52,7 +52,8 @@ public class Query implements Serializable { * filtering. * * @param filter - * back end filter of a suitable type for the data provider + * back end filter of a suitable type for the data provider; can + * be null */ public Query(F filter) { offset = 0; @@ -72,7 +73,7 @@ public class Query implements Serializable { * @param sortOrders * sorting order for fetching * @param filter - * filtering for fetching + * filtering for fetching; can be null */ public Query(int offset, int limit, List> sortOrders, F filter) { diff --git a/server/src/test/java/com/vaadin/server/data/provider/ListDataProviderTest.java b/server/src/test/java/com/vaadin/server/data/provider/ListDataProviderTest.java index 37b4214a45..6a53aae528 100644 --- a/server/src/test/java/com/vaadin/server/data/provider/ListDataProviderTest.java +++ b/server/src/test/java/com/vaadin/server/data/provider/ListDataProviderTest.java @@ -7,11 +7,11 @@ import java.util.LinkedList; import java.util.List; import java.util.stream.Collectors; -import com.vaadin.server.data.DataProvider; import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import com.vaadin.server.data.DataProvider; import com.vaadin.server.data.ListDataProvider; import com.vaadin.server.data.Query; @@ -29,7 +29,7 @@ public class ListDataProviderTest { @Test public void testListContainsAllData() { List list = new LinkedList<>(data); - dataProvider.fetch(new Query()) + dataProvider.fetch(new Query<>()) .forEach(str -> assertTrue( "Data provider contained values not in original data", list.remove(str))); @@ -42,7 +42,7 @@ public class ListDataProviderTest { Comparator comp = Comparator.comparing(StrBean::getValue) .thenComparing(StrBean::getRandomNumber) .thenComparing(StrBean::getId); - List list = dataProvider.sortingBy(comp).fetch(new Query()) + List list = dataProvider.sortingBy(comp).fetch(new Query<>()) .collect(Collectors.toList()); // First value in data is { Xyz, 10, 100 } which should be last in list @@ -63,7 +63,7 @@ public class ListDataProviderTest { public void testDefatulSortWithSpecifiedPostSort() { Comparator comp = Comparator.comparing(StrBean::getValue) .thenComparing(Comparator.comparing(StrBean::getId).reversed()); - List list = dataProvider.sortingBy(comp).fetch(new Query()) + List list = dataProvider.sortingBy(comp).fetch(new Query<>()) // The sort here should come e.g from a Component .sorted(Comparator.comparing(StrBean::getRandomNumber)) .collect(Collectors.toList()); @@ -91,7 +91,7 @@ public class ListDataProviderTest { @Test public void testDefatulSortWithFunction() { List list = dataProvider.sortingBy(StrBean::getValue) - .fetch(new Query()).collect(Collectors.toList()); + .fetch(new Query<>()).collect(Collectors.toList()); Assert.assertEquals("Sorted data and original data sizes don't match", data.size(), list.size()); @@ -108,44 +108,41 @@ public class ListDataProviderTest { @Test public void refreshAll_changeBeanInstance() { StrBean bean = new StrBean("foo", -1, hashCode()); - Query query = new Query(); - int size = dataProvider.size(query); + int size = dataProvider.size(new Query<>()); data.set(0, bean); dataProvider.refreshAll(); - List list = dataProvider.fetch(query) + List list = dataProvider.fetch(new Query<>()) .collect(Collectors.toList()); StrBean first = list.get(0); Assert.assertEquals(bean.getValue(), first.getValue()); Assert.assertEquals(bean.getRandomNumber(), first.getRandomNumber()); Assert.assertEquals(bean.getId(), first.getId()); - Assert.assertEquals(size, dataProvider.size(query)); + Assert.assertEquals(size, dataProvider.size(new Query<>())); } @Test public void refreshAll_updateBean() { - Query query = new Query(); - int size = dataProvider.size(query); + int size = dataProvider.size(new Query<>()); StrBean bean = data.get(0); bean.setValue("foo"); dataProvider.refreshAll(); - List list = dataProvider.fetch(query) + List list = dataProvider.fetch(new Query<>()) .collect(Collectors.toList()); StrBean first = list.get(0); Assert.assertEquals("foo", first.getValue()); - Assert.assertEquals(size, dataProvider.size(query)); + Assert.assertEquals(size, dataProvider.size(new Query<>())); } @Test public void refreshAll_sortingBy_changeBeanInstance() { StrBean bean = new StrBean("foo", -1, hashCode()); - Query query = new Query(); - int size = dataProvider.size(query); + int size = dataProvider.size(new Query<>()); data.set(0, bean); @@ -153,43 +150,70 @@ public class ListDataProviderTest { .sortingBy(Comparator.comparing(StrBean::getId)); dSource.refreshAll(); - List list = dSource.fetch(query).collect(Collectors.toList()); + List list = dSource.fetch(new Query<>()) + .collect(Collectors.toList()); StrBean first = list.get(0); Assert.assertEquals(bean.getValue(), first.getValue()); Assert.assertEquals(bean.getRandomNumber(), first.getRandomNumber()); Assert.assertEquals(bean.getId(), first.getId()); - Assert.assertEquals(size, dataProvider.size(query)); + Assert.assertEquals(size, dataProvider.size(new Query<>())); } @Test public void refreshAll_addBeanInstance() { StrBean bean = new StrBean("foo", -1, hashCode()); - Query query = new Query(); - int size = dataProvider.size(query); + int size = dataProvider.size(new Query<>()); data.add(0, bean); dataProvider.refreshAll(); - List list = dataProvider.fetch(query) + List list = dataProvider.fetch(new Query<>()) .collect(Collectors.toList()); StrBean first = list.get(0); Assert.assertEquals(bean.getValue(), first.getValue()); Assert.assertEquals(bean.getRandomNumber(), first.getRandomNumber()); Assert.assertEquals(bean.getId(), first.getId()); - Assert.assertEquals(size + 1, dataProvider.size(query)); + Assert.assertEquals(size + 1, dataProvider.size(new Query<>())); } @Test public void refreshAll_removeBeanInstance() { - Query query = new Query(); - int size = dataProvider.size(query); + int size = dataProvider.size(new Query<>()); data.remove(0); dataProvider.refreshAll(); - Assert.assertEquals(size - 1, dataProvider.size(query)); + Assert.assertEquals(size - 1, dataProvider.size(new Query<>())); + } + + @Test + public void filteringListDataProvider_convertFilter() { + DataProvider strFilterDataProvider = dataProvider + .convertFilter( + text -> strBean -> strBean.getValue().contains(text)); + Assert.assertEquals("Only one item should match 'Xyz'", 1, + strFilterDataProvider.size(new Query<>("Xyz"))); + Assert.assertEquals("No item should match 'Zyx'", 0, + strFilterDataProvider.size(new Query<>("Zyx"))); + Assert.assertEquals("Unexpected number of matches for 'Foo'", 36, + strFilterDataProvider.size(new Query<>("Foo"))); + + Assert.assertEquals("No items should've been filtered out", data.size(), + strFilterDataProvider.size(new Query<>())); + } + + @Test + public void filteringListDataProvider_defaultFilterType() { + Assert.assertEquals("Only one item should match 'Xyz'", 1, + dataProvider.size(new Query<>( + strBean -> strBean.getValue().contains("Xyz")))); + Assert.assertEquals("No item should match 'Zyx'", 0, dataProvider.size( + new Query<>(strBean -> strBean.getValue().contains("Zyx")))); + Assert.assertEquals("Unexpected number of matches for 'Foo'", 36, + dataProvider.size(new Query<>( + strBean -> strBean.getValue().contains("Foo")))); } } diff --git a/uitest/src/main/java/com/vaadin/tests/components/listselect/ListSelectAddRemoveItems.java b/uitest/src/main/java/com/vaadin/tests/components/listselect/ListSelectAddRemoveItems.java index 0ee679e891..fdcb15c1f0 100644 --- a/uitest/src/main/java/com/vaadin/tests/components/listselect/ListSelectAddRemoveItems.java +++ b/uitest/src/main/java/com/vaadin/tests/components/listselect/ListSelectAddRemoveItems.java @@ -22,6 +22,7 @@ import java.util.stream.Collectors; import com.vaadin.server.VaadinRequest; import com.vaadin.server.data.ListDataProvider; +import com.vaadin.server.data.Query; import com.vaadin.tests.components.AbstractTestUIWithLog; import com.vaadin.ui.Button; import com.vaadin.ui.ListSelect; @@ -50,7 +51,7 @@ public class ListSelectAddRemoveItems extends AbstractTestUIWithLog { })); addComponent(new Button("Add first", event -> { - List list = dataProvider.fetch(null) + List list = dataProvider.fetch(new Query<>()) .collect(Collectors.toList()); list.add(0, "first"); dataProvider = new ListDataProvider<>(list); @@ -59,7 +60,7 @@ public class ListSelectAddRemoveItems extends AbstractTestUIWithLog { })); addComponent(new Button("Add middle", event -> { - List list = dataProvider.fetch(null) + List list = dataProvider.fetch(new Query<>()) .collect(Collectors.toList()); list.add(list.size() / 2, "middle"); dataProvider = new ListDataProvider<>(list); @@ -68,7 +69,7 @@ public class ListSelectAddRemoveItems extends AbstractTestUIWithLog { })); addComponent(new Button("Add last", event -> { - List list = dataProvider.fetch(null) + List list = dataProvider.fetch(new Query<>()) .collect(Collectors.toList()); list.add("last"); dataProvider = new ListDataProvider<>(list); @@ -77,7 +78,7 @@ public class ListSelectAddRemoveItems extends AbstractTestUIWithLog { })); addComponent(new Button("Swap", event -> { - List list = dataProvider.fetch(null) + List list = dataProvider.fetch(new Query<>()) .collect(Collectors.toList()); Collections.swap(list, 0, list.size() - 1); dataProvider = new ListDataProvider<>(list); @@ -87,7 +88,7 @@ public class ListSelectAddRemoveItems extends AbstractTestUIWithLog { })); addComponent(new Button("Remove first", event -> { - List list = dataProvider.fetch(null) + List list = dataProvider.fetch(new Query<>()) .collect(Collectors.toList()); list.remove(0); @@ -98,7 +99,7 @@ public class ListSelectAddRemoveItems extends AbstractTestUIWithLog { })); addComponent(new Button("Remove middle", event -> { - List list = dataProvider.fetch(null) + List list = dataProvider.fetch(new Query<>()) .collect(Collectors.toList()); list.remove(list.size() / 2); dataProvider = new ListDataProvider<>(list); @@ -107,7 +108,7 @@ public class ListSelectAddRemoveItems extends AbstractTestUIWithLog { })); addComponent(new Button("Remove last", event -> { - List list = dataProvider.fetch(null) + List list = dataProvider.fetch(new Query<>()) .collect(Collectors.toList()); list.remove(list.size() - 1); @@ -121,7 +122,8 @@ public class ListSelectAddRemoveItems extends AbstractTestUIWithLog { private void logContainer() { StringBuilder b = new StringBuilder(); - List list = dataProvider.fetch(null).collect(Collectors.toList()); + List list = dataProvider.fetch(new Query<>()) + .collect(Collectors.toList()); for (int i = 0; i < list.size(); i++) { Object id = list.get(i); if (i != 0) { -- 2.39.5