/* * 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.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. * * @param <T> * data type * @since 8.0 */ public class ListDataProvider<T> extends AbstractDataProvider<T, SerializablePredicate<T>> implements ConfigurableFilterDataProvider<T, SerializablePredicate<T>, SerializablePredicate<T>> { private static final SerializableSupplier<Locale> CURRENT_LOCALE_SUPPLIER = () -> { UI currentUi = UI.getCurrent(); if (currentUi != null) { return currentUi.getLocale(); } else { return Locale.getDefault(); } }; private SerializableComparator<T> sortOrder = null; private SerializablePredicate<T> filter; private final Collection<T> backend; /** * Constructs a new ListDataProvider. * <p> * No protective copy is made of the list, and changes in the provided * backing Collection will be visible via this data provider. The caller * should copy the list if necessary. * * @param items * the initial data, not null */ public ListDataProvider(Collection<T> items) { Objects.requireNonNull(items, "items cannot be null"); backend = items; sortOrder = null; } /** * Returns the underlying data items. * * @return the underlying data items */ public Collection<T> getItems() { return backend; } @Override public Stream<T> fetch(Query<T, SerializablePredicate<T>> query) { Stream<T> stream = getFilteredStream(query); Optional<Comparator<T>> comparing = Stream .of(query.getInMemorySorting(), sortOrder) .filter(c -> c != null) .reduce((c1, c2) -> c1.thenComparing(c2)); if (comparing.isPresent()) { stream = stream.sorted(comparing.get()); } return stream.skip(query.getOffset()).limit(query.getLimit()); } @Override public boolean isInMemory() { return true; } @Override public int size(Query<T, SerializablePredicate<T>> query) { return (int) getFilteredStream(query).count(); } private Stream<T> getFilteredStream( Query<T, SerializablePredicate<T>> query) { Stream<T> stream = backend.stream(); // Apply our own filters first so that query filters never see the items // that would already have been filtered out if (filter != null) { stream = stream.filter(filter); } stream = query.getFilter().map(stream::filter).orElse(stream); return stream; } /** * Sets the comparator to use as the default sorting for this data provider. * This overrides the sorting set by any other method that manipulates the * default sorting of this data provider. * <p> * The default sorting is used if the query defines no sorting. The default * sorting is also used to determine the ordering of items that are * considered equal by the sorting defined in the query. * * @see #setSortOrder(ValueProvider, SortDirection) * @see #addSortComparator(SerializableComparator) * * @param comparator * a comparator to use, or <code>null</code> to clear any * previously set sort order */ public void setSortComparator(SerializableComparator<T> comparator) { this.sortOrder = comparator; refreshAll(); } /** * Sets the property and direction to use as the default sorting for this * data provider. This overrides the sorting set by any other method that * manipulates the default sorting of this data provider. * <p> * The default sorting is used if the query defines no sorting. The default * sorting is also used to determine the ordering of items that are * considered equal by the sorting defined in the query. * * @see #setSortComparator(SerializableComparator) * @see #addSortOrder(ValueProvider, SortDirection) * * @param valueProvider * the value provider that defines the property do sort by, not * <code>null</code> * @param sortDirection * the sort direction to use, not <code>null</code> */ public <V extends Comparable<? super V>> void setSortOrder( ValueProvider<T, V> valueProvider, SortDirection sortDirection) { setSortComparator(propertyComparator(valueProvider, sortDirection)); } /** * Adds a comparator to the default sorting for this data provider. If no * default sorting has been defined, then the provided comparator will be * used as the default sorting. If a default sorting has been defined, then * the provided comparator will be used to determine the ordering of items * that are considered equal by the previously defined default sorting. * <p> * The default sorting is used if the query defines no sorting. The default * sorting is also used to determine the ordering of items that are * considered equal by the sorting defined in the query. * * @see #setSortComparator(SerializableComparator) * @see #addSortOrder(ValueProvider, SortDirection) * * @param comparator * a comparator to add, not <code>null</code> */ public void addSortComparator(SerializableComparator<T> comparator) { Objects.requireNonNull(comparator, "Sort order to add cannot be null"); SerializableComparator<T> originalComparator = this.sortOrder; if (originalComparator == null) { setSortComparator(comparator); } else { setSortComparator((a, b) -> { int result = originalComparator.compare(a, b); if (result == 0) { result = comparator.compare(a, b); } return result; }); } } /** * Adds a property and direction to the default sorting for this data * provider. If no default sorting has been defined, then the provided sort * order will be used as the default sorting. If a default sorting has been * defined, then the provided sort order will be used to determine the * ordering of items that are considered equal by the previously defined * default sorting. * <p> * The default sorting is used if the query defines no sorting. The default * sorting is also used to determine the ordering of items that are * considered equal by the sorting defined in the query. * * @see #setSortOrder(ValueProvider, SortDirection) * @see #addSortComparator(SerializableComparator) * * @param valueProvider * the value provider that defines the property do sort by, not * <code>null</code> * @param sortDirection * the sort direction to use, not <code>null</code> */ public <V extends Comparable<? super V>> void addSortOrder( ValueProvider<T, V> valueProvider, SortDirection sortDirection) { addSortComparator(propertyComparator(valueProvider, sortDirection)); } private static <V extends Comparable<? super V>, T> SerializableComparator<T> propertyComparator( ValueProvider<T, V> valueProvider, SortDirection sortDirection) { Objects.requireNonNull(valueProvider, "Value provider cannot be null"); Objects.requireNonNull(sortDirection, "Sort direction cannot be null"); Comparator<V> comparator = getNaturalSortComparator(sortDirection); return (a, b) -> comparator.compare(valueProvider.apply(a), valueProvider.apply(b)); } private static <V extends Comparable<? super V>> Comparator<V> getNaturalSortComparator( SortDirection sortDirection) { Comparator<V> comparator = Comparator.naturalOrder(); if (sortDirection == SortDirection.DESCENDING) { comparator = comparator.reversed(); } return comparator; } /** * Sets a filter to be applied to all queries. The filter replaces any * filter that has been set or added previously. * * @see #setFilter(ValueProvider, SerializablePredicate) * @see #setFilterByValue(ValueProvider, Object) * @see #addFilter(SerializablePredicate) * * @param filter * the filter to set, or <code>null</code> to remove any set * filters */ @Override public void setFilter(SerializablePredicate<T> filter) { this.filter = filter; refreshAll(); } /** * Adds a filter to be applied to all queries. The filter will be used in * addition to any filter that has been set or added previously. * * @see #addFilter(ValueProvider, SerializablePredicate) * @see #addFilterByValue(ValueProvider, Object) * @see #setFilter(SerializablePredicate) * * @param filter * the filter to add, not <code>null</code> */ public void addFilter(SerializablePredicate<T> filter) { Objects.requireNonNull(filter, "Filter cannot be null"); if (this.filter == null) { setFilter(filter); } else { SerializablePredicate<T> oldFilter = this.filter; setFilter(item -> oldFilter.test(item) && filter.test(item)); } } /** * Removes any filter that has been set or added previously. * * @see #setFilter(SerializablePredicate) */ public void clearFilters() { setFilter(null); } /** * Sets a filter for an item property. The filter replaces any filter that * has been set or added previously. * * @see #setFilter(SerializablePredicate) * @see #setFilterByValue(ValueProvider, Object) * @see #addFilter(ValueProvider, SerializablePredicate) * * @param valueProvider * value provider that gets the property value, not * <code>null</code> * @param valueFilter * filter for testing the property value, not <code>null</code> */ public <V> void setFilter(ValueProvider<T, V> valueProvider, SerializablePredicate<V> valueFilter) { setFilter(createValueProviderFilter(valueProvider, valueFilter)); } /** * Adds a filter for an item property. The filter will be used in addition * to any filter that has been set or added previously. * * @see #addFilter(SerializablePredicate) * @see #addFilterByValue(ValueProvider, Object) * @see #setFilter(ValueProvider, SerializablePredicate) * * @param valueProvider * value provider that gets the property value, not * <code>null</code> * @param valueFilter * filter for testing the property value, not <code>null</code> */ public <V> void addFilter(ValueProvider<T, V> valueProvider, SerializablePredicate<V> valueFilter) { Objects.requireNonNull(valueProvider, "Value provider cannot be null"); Objects.requireNonNull(valueFilter, "Value filter cannot be null"); addFilter(createValueProviderFilter(valueProvider, valueFilter)); } private static <T, V> SerializablePredicate<T> createValueProviderFilter( ValueProvider<T, V> valueProvider, SerializablePredicate<V> valueFilter) { return item -> valueFilter.test(valueProvider.apply(item)); } /** * Sets a filter that requires an item property to have a specific value. * The property value and the provided value are compared using * {@link Object#equals(Object)}. The filter replaces any filter that has * been set or added previously. * * @see #setFilter(SerializablePredicate) * @see #setFilter(ValueProvider, SerializablePredicate) * @see #addFilterByValue(ValueProvider, Object) * * @param valueProvider * value provider that gets the property value, not * <code>null</code> * @param requiredValue * the value that the property must have for the filter to pass */ public <V> void setFilterByValue(ValueProvider<T, V> valueProvider, V requiredValue) { setFilter(createEqualsFilter(valueProvider, requiredValue)); } /** * Adds a filter that requires an item property to have a specific value. * The property value and the provided value are compared using * {@link Object#equals(Object)}.The filter will be used in addition to any * filter that has been set or added previously. * * @see #setFilterByValue(ValueProvider, Object) * @see #addFilter(SerializablePredicate) * @see #addFilter(ValueProvider, SerializablePredicate) * * @param valueProvider * value provider that gets the property value, not * <code>null</code> * @param requiredValue * the value that the property must have for the filter to pass */ public <V> void addFilterByValue(ValueProvider<T, V> valueProvider, V requiredValue) { addFilter(createEqualsFilter(valueProvider, requiredValue)); } private static <T, V> SerializablePredicate<T> createEqualsFilter( ValueProvider<T, V> valueProvider, V requiredValue) { Objects.requireNonNull(valueProvider, "Value provider cannot be null"); return item -> Objects.equals(valueProvider.apply(item), requiredValue); } /** * 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. * <p> * The predicate receives the item as the first parameter and the query * filter value as the second parameter, and should return <code>true</code> * if the corresponding item should be included. The query filter value is * never <code>null</code> – 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 <code>null</code> * * @return a data provider that filters accordingly, not <code>null</code> */ public <Q> DataProvider<T, Q> filteringBy( SerializableBiPredicate<T, Q> predicate) { Objects.requireNonNull(predicate, "Predicate cannot be null"); return withConvertedFilter( 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. * <p> * The predicate receives the property value as the first parameter and the * query filter value as the second parameter, and should return * <code>true</code> if the corresponding item should be included. The query * filter value is never <code>null</code> – 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 * <code>null</code> * @param predicate * a predicate to use for comparing the property value to the * query filter, not <code>null</code> * * @return a data provider that filters accordingly, not <code>null</code> */ public <V, Q> DataProvider<T, Q> filteringBy( ValueProvider<T, V> valueProvider, SerializableBiPredicate<V, Q> 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 * <code>null</code> * * @return a data provider that filters accordingly, not <code>null</code> */ public <V> DataProvider<T, V> filteringByEquals( ValueProvider<T, V> valueProvider) { return filteringBy(valueProvider, Objects::equals); } private <V, Q> DataProvider<T, Q> filteringByIgnoreNull( ValueProvider<T, V> valueProvider, SerializableBiPredicate<V, Q> 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 <code>null</code>. * * @param valueProvider * a value provider that gets the string property value, not * <code>null</code> * @param locale * the locale to use for converting the strings to lower case, * not <code>null</code> * @return a data provider that filters accordingly, not <code>null</code> */ public DataProvider<T, String> filteringBySubstring( ValueProvider<T, String> 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 <code>null</code>. * * @param valueProvider * a value provider that gets the string property value, not * <code>null</code> * @return a data provider that filters accordingly, not <code>null</code> */ public DataProvider<T, String> filteringBySubstring( ValueProvider<T, String> 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 <code>null</code>. * * @param valueProvider * a value provider that gets the string property value, not * <code>null</code> * @param locale * the locale to use for converting the strings to lower case, * not <code>null</code> * @return a data provider that filters accordingly, not <code>null</code> */ public DataProvider<T, String> filteringByPrefix( ValueProvider<T, String> 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 <code>null</code>. * * @param valueProvider * a value provider that gets the string property value, not * <code>null</code> * @return a data provider that filters accordingly, not <code>null</code> */ public DataProvider<T, String> filteringByPrefix( ValueProvider<T, String> valueProvider) { return filteringByCaseInsensitiveString(valueProvider, String::startsWith, CURRENT_LOCALE_SUPPLIER); } private DataProvider<T, String> filteringByCaseInsensitiveString( ValueProvider<T, String> valueProvider, SerializableBiPredicate<String, String> predicate, SerializableSupplier<Locale> 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)); }); } }