From 5447e7e05392483455f7df4bf2af5d5d74e5a2ae Mon Sep 17 00:00:00 2001 From: Leif Åstrand Date: Sun, 22 Jan 2017 21:50:46 +0200 Subject: Add ListDataProvider shorthands for filter conversion (#8279) Also updates ComboBox.setItems to use these new shorthands This is one of many steps towards #8245 --- .../com/vaadin/data/provider/ListDataProvider.java | 195 +++++++++++++++++++++ server/src/main/java/com/vaadin/ui/ComboBox.java | 26 +-- .../vaadin/data/provider/DataProviderTestBase.java | 7 + .../vaadin/data/provider/ListDataProviderTest.java | 45 +++++ 4 files changed, 260 insertions(+), 13 deletions(-) (limited to 'server/src') diff --git a/server/src/main/java/com/vaadin/data/provider/ListDataProvider.java b/server/src/main/java/com/vaadin/data/provider/ListDataProvider.java index 470339bc2a..4269ddbeff 100644 --- a/server/src/main/java/com/vaadin/data/provider/ListDataProvider.java +++ b/server/src/main/java/com/vaadin/data/provider/ListDataProvider.java @@ -17,14 +17,18 @@ package com.vaadin.data.provider; import java.util.Collection; import java.util.Comparator; +import java.util.Locale; import java.util.Objects; import java.util.Optional; import java.util.stream.Stream; import com.vaadin.data.ValueProvider; +import com.vaadin.server.SerializableBiPredicate; import com.vaadin.server.SerializableComparator; import com.vaadin.server.SerializablePredicate; +import com.vaadin.server.SerializableSupplier; import com.vaadin.shared.data.sort.SortDirection; +import com.vaadin.ui.UI; /** * {@link DataProvider} wrapper for {@link Collection}s. This class does not @@ -38,6 +42,15 @@ public class ListDataProvider implements AppendableFilterDataProvider>, ConfigurableFilterDataProvider, SerializablePredicate> { + private static final SerializableSupplier CURRENT_LOCALE_SUPPLIER = () -> { + UI currentUi = UI.getCurrent(); + if (currentUi != null) { + return currentUi.getLocale(); + } else { + return Locale.getDefault(); + } + }; + private SerializableComparator sortOrder = null; private SerializablePredicate filter; @@ -376,4 +389,186 @@ public class ListDataProvider SerializablePredicate filter2) { return t -> filter1.test(t) && filter2.test(t); } + + /** + * Wraps this data provider to create a new data provider that is filtered + * by comparing an item to the filter value provided in the query. + *

+ * The predicate receives the item as the first parameter and the query + * filter value as the second parameter, and should return true + * if the corresponding item should be included. The query filter value is + * never null – all items are included without running the + * predicate if the query doesn't define any filter. + * + * @param predicate + * a predicate to use for comparing the item to the query filter, + * not null + * + * @return a data provider that filters accordingly, not null + */ + public DataProvider filteringBy( + SerializableBiPredicate predicate) { + Objects.requireNonNull(predicate, "Predicate cannot be null"); + + return convertFilter( + filterValue -> item -> predicate.test(item, filterValue)); + } + + /** + * Wraps this data provider to create a new data provider that is filtered + * by comparing an item property value to the filter value provided in the + * query. + *

+ * The predicate receives the property value as the first parameter and the + * query filter value as the second parameter, and should return + * true if the corresponding item should be included. The query + * filter value is never null – all items are included without + * running either callback if the query doesn't define any filter. + * + * @param valueProvider + * a value provider that gets the property value, not + * null + * @param predicate + * a predicate to use for comparing the property value to the + * query filter, not null + * + * @return a data provider that filters accordingly, not null + */ + public DataProvider filteringBy( + ValueProvider valueProvider, + SerializableBiPredicate predicate) { + Objects.requireNonNull(valueProvider, "Value provider cannot be null"); + Objects.requireNonNull(predicate, "Predicate cannot be null"); + + return filteringBy((item, filterValue) -> predicate + .test(valueProvider.apply(item), filterValue)); + } + + /** + * Wraps this data provider to create a new data provider that is filtered + * by testing whether the value of a property is equals to the filter value + * provided in the query. Equality is tested using + * {@link Objects#equals(Object, Object)}. + * + * @param valueProvider + * a value provider that gets the property value, not + * null + * + * @return a data provider that filters accordingly, not null + */ + public DataProvider filteringByEquals( + ValueProvider valueProvider) { + return filteringBy(valueProvider, Objects::equals); + } + + private DataProvider filteringByIgnoreNull( + ValueProvider valueProvider, + SerializableBiPredicate predicate) { + Objects.requireNonNull(predicate, "Predicate cannot be null"); + + return filteringBy(valueProvider, + (itemValue, queryFilter) -> itemValue != null + && predicate.test(itemValue, queryFilter)); + } + + /** + * Wraps this data provider to create a new data provider that is filtered + * by a string by checking whether the lower case representation of the + * filter value provided in the query is a substring of the lower case + * representation of an item property value. The filter never passes if the + * item property value is null. + * + * @param valueProvider + * a value provider that gets the string property value, not + * null + * @param locale + * the locale to use for converting the strings to lower case, + * not null + * @return a data provider that filters accordingly, not null + */ + public DataProvider filteringBySubstring( + ValueProvider valueProvider, Locale locale) { + Objects.requireNonNull(locale, "Locale cannot be null"); + return filteringByCaseInsensitiveString(valueProvider, String::contains, + () -> locale); + } + + /** + * Wraps this data provider to create a new data provider that is filtered + * by a string by checking whether the lower case representation of the + * filter value provided in the query is a substring of the lower case + * representation of an item property value. Conversion to lower case is + * done using the locale of the {@link UI#getCurrent() current UI} if + * available, or otherwise {@link Locale#getDefault() the default locale}. + * The filter never passes if the item property value is null. + * + * @param valueProvider + * a value provider that gets the string property value, not + * null + * @return a data provider that filters accordingly, not null + */ + public DataProvider filteringBySubstring( + ValueProvider valueProvider) { + return filteringByCaseInsensitiveString(valueProvider, String::contains, + CURRENT_LOCALE_SUPPLIER); + } + + /** + * Wraps this data provider to create a new data provider that is filtered + * by a string by checking whether the lower case representation of an item + * property value starts with the lower case representation of the filter + * value provided in the query. The filter never passes if the item property + * value is null. + * + * @param valueProvider + * a value provider that gets the string property value, not + * null + * @param locale + * the locale to use for converting the strings to lower case, + * not null + * @return a data provider that filters accordingly, not null + */ + public DataProvider filteringByPrefix( + ValueProvider valueProvider, Locale locale) { + return filteringByCaseInsensitiveString(valueProvider, + String::startsWith, () -> locale); + } + + /** + * Wraps this data provider to create a new data provider that is filtered + * by a string by checking whether the lower case representation of an item + * property value starts with the lower case representation of the filter + * value provided in the query. Conversion to lower case is done using the + * locale of the {@link UI#getCurrent() current UI} if available, or + * otherwise {@link Locale#getDefault() the default locale}. The filter + * never passes if the item property value is null. + * + * @param valueProvider + * a value provider that gets the string property value, not + * null + * @return a data provider that filters accordingly, not null + */ + public DataProvider filteringByPrefix( + ValueProvider valueProvider) { + return filteringByCaseInsensitiveString(valueProvider, + String::startsWith, CURRENT_LOCALE_SUPPLIER); + } + + private DataProvider filteringByCaseInsensitiveString( + ValueProvider valueProvider, + SerializableBiPredicate predicate, + SerializableSupplier localeSupplier) { + // Only assert since these are only passed from our own code + assert predicate != null; + assert localeSupplier != null; + + return filteringByIgnoreNull(valueProvider, + (itemString, filterString) -> { + Locale locale = localeSupplier.get(); + assert locale != null; + + return predicate.test(itemString.toLowerCase(locale), + filterString.toLowerCase(locale)); + }); + } } diff --git a/server/src/main/java/com/vaadin/ui/ComboBox.java b/server/src/main/java/com/vaadin/ui/ComboBox.java index a789e2990c..20cc14b2da 100644 --- a/server/src/main/java/com/vaadin/ui/ComboBox.java +++ b/server/src/main/java/com/vaadin/ui/ComboBox.java @@ -134,11 +134,6 @@ public class ComboBox extends AbstractSingleSelect private StyleGenerator itemStyleGenerator = item -> null; - private final SerializableBiPredicate defaultFilterMethod = ( - text, item) -> getItemCaptionGenerator().apply(item) - .toLowerCase(getLocale()) - .contains(text.toLowerCase(getLocale())); - /** * Constructs an empty combo box without a caption. The content of the combo * box can be set with {@link #setDataProvider(DataProvider)} or @@ -216,10 +211,13 @@ public class ComboBox extends AbstractSingleSelect @Override public void setItems(Collection items) { - DataProvider provider = DataProvider.create(items) - .convertFilter(filterText -> item -> defaultFilterMethod - .test(filterText, item)); - setDataProvider(provider); + // Cannot use the case insensitive contains shorthand from + // ListDataProvider since it wouldn't react to locale changes + CaptionFilter defaultCaptionFilter = (itemText, filterText) -> itemText + .toLowerCase(getLocale()) + .contains(filterText.toLowerCase(getLocale())); + + setItems(defaultCaptionFilter, items); } /** @@ -236,9 +234,11 @@ public class ComboBox extends AbstractSingleSelect * the data items to display */ public void setItems(CaptionFilter captionFilter, Collection items) { + // Must do getItemCaptionGenerator() for each operation since it might + // not be the same as when this method was invoked DataProvider provider = DataProvider.create(items) - .convertFilter(filterText -> item -> captionFilter.test( - getItemCaptionGenerator().apply(item), filterText)); + .filteringBy(item -> getItemCaptionGenerator().apply(item), + captionFilter); setDataProvider(provider); } @@ -300,8 +300,8 @@ public class ComboBox extends AbstractSingleSelect /** * Returns true if the user can enter text into the field to either filter - * the selections or enter a new value if new item handler is set - * (see {@link #setNewItemHandler(NewItemHandler)}. If text input is disabled, + * the selections or enter a new value if new item handler is set (see + * {@link #setNewItemHandler(NewItemHandler)}. If text input is disabled, * the comboBox will work in the same way as a {@link NativeSelect} * * @return true if text input is allowed diff --git a/server/src/test/java/com/vaadin/data/provider/DataProviderTestBase.java b/server/src/test/java/com/vaadin/data/provider/DataProviderTestBase.java index 9d1115d2af..79426f8c6c 100644 --- a/server/src/test/java/com/vaadin/data/provider/DataProviderTestBase.java +++ b/server/src/test/java/com/vaadin/data/provider/DataProviderTestBase.java @@ -190,4 +190,11 @@ public abstract class DataProviderTestBase()).count(); } + + protected static void assertSizeWithFilter(int expectedSize, + DataProvider dataProvider, F filterValue) { + Assert.assertEquals(expectedSize, + dataProvider.size(new Query<>(filterValue))); + } + } diff --git a/server/src/test/java/com/vaadin/data/provider/ListDataProviderTest.java b/server/src/test/java/com/vaadin/data/provider/ListDataProviderTest.java index e10c071c41..6f98100e19 100644 --- a/server/src/test/java/com/vaadin/data/provider/ListDataProviderTest.java +++ b/server/src/test/java/com/vaadin/data/provider/ListDataProviderTest.java @@ -2,6 +2,7 @@ package com.vaadin.data.provider; import java.util.Comparator; import java.util.List; +import java.util.Locale; import java.util.stream.Collectors; import org.junit.Assert; @@ -232,6 +233,50 @@ public class ListDataProviderTest Assert.assertEquals(0, size); } + @Test + public void filteringBy_itemPredicate() { + DataProvider filteringBy = dataProvider.filteringBy( + (item, filterValue) -> item.getValue().equals(filterValue)); + + assertSizeWithFilter(36, filteringBy, "Foo"); + } + + @Test + public void filteringBy_equals() { + DataProvider filteringBy = dataProvider + .filteringByEquals(StrBean::getValue); + + assertSizeWithFilter(36, filteringBy, "Foo"); + } + + @Test + public void filteringBy_propertyValuePredicate() { + DataProvider filteringBy = dataProvider.filteringBy( + StrBean::getId, + (propertyValue, filterValue) -> propertyValue >= filterValue); + + assertSizeWithFilter(90, filteringBy, 10); + } + + @Test + public void filteringBy_caseInsensitiveSubstring() { + DataProvider filteringBy = dataProvider + .filteringBySubstring(StrBean::getValue, Locale.ENGLISH); + + assertSizeWithFilter(36, filteringBy, "oo"); + assertSizeWithFilter(36, filteringBy, "Oo"); + } + + @Test + public void filterBy_caseInsensitivePrefix() { + DataProvider filteringBy = dataProvider + .filteringByPrefix(StrBean::getValue, Locale.ENGLISH); + + assertSizeWithFilter(36, filteringBy, "Fo"); + assertSizeWithFilter(36, filteringBy, "fo"); + assertSizeWithFilter(0, filteringBy, "oo"); + } + @Override protected void setSortOrder(List> sortOrder, Comparator comp) { -- cgit v1.2.3