diff options
author | Leif Åstrand <legioth@gmail.com> | 2017-01-17 16:22:55 +0200 |
---|---|---|
committer | Denis <denis@vaadin.com> | 2017-01-17 16:22:55 +0200 |
commit | 83b16a8ee742b676bd8ac8ef48e62b8fd64326e3 (patch) | |
tree | 23321774daa70bf2faf41be546fbae169b5dcf61 | |
parent | 487cb4ea0c5e51e7a9b85d6bbb6ab9200f6772f7 (diff) | |
download | vaadin-framework-83b16a8ee742b676bd8ac8ef48e62b8fd64326e3.tar.gz vaadin-framework-83b16a8ee742b676bd8ac8ef48e62b8fd64326e3.zip |
Make data providers statefull with regards to default sort orders (#8247)
* Make data providers statefull with regards to default sort orders
This is one of many steps towards #8245
6 files changed, 263 insertions, 215 deletions
diff --git a/server/src/main/java/com/vaadin/data/provider/BackEndDataProvider.java b/server/src/main/java/com/vaadin/data/provider/BackEndDataProvider.java index 5f789e8a55..3606a99ade 100644 --- a/server/src/main/java/com/vaadin/data/provider/BackEndDataProvider.java +++ b/server/src/main/java/com/vaadin/data/provider/BackEndDataProvider.java @@ -16,13 +16,15 @@ package com.vaadin.data.provider; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; import java.util.stream.Stream; import com.vaadin.server.SerializableFunction; import com.vaadin.server.SerializableToIntFunction; -import com.vaadin.shared.Registration; /** * A {@link DataProvider} for any back end. @@ -34,6 +36,8 @@ import com.vaadin.shared.Registration; */ public class BackEndDataProvider<T, F> extends AbstractDataProvider<T, F> { + private List<SortOrder<String>> sortOrders = new ArrayList<>(); + private final SerializableFunction<Query<T, F>, Stream<T>> request; private final SerializableToIntFunction<Query<T, F>> sizeCallback; @@ -57,46 +61,71 @@ public class BackEndDataProvider<T, F> extends AbstractDataProvider<T, F> { @Override public Stream<T> fetch(Query<T, F> query) { - return request.apply(query); + return request.apply(mixInSortOrders(query)); } @Override public int size(Query<T, F> query) { - return sizeCallback.applyAsInt(query); + return sizeCallback.applyAsInt(mixInSortOrders(query)); + } + + private Query<T, F> mixInSortOrders(Query<T, F> query) { + Set<String> sortedPropertyNames = query.getSortOrders().stream() + .map(SortOrder::getSorted).collect(Collectors.toSet()); + + List<SortOrder<String>> combinedSortOrders = Stream + .concat(query.getSortOrders().stream(), + sortOrders.stream() + .filter(order -> !sortedPropertyNames + .contains(order.getSorted()))) + .collect(Collectors.toList()); + + return new Query<>(query.getOffset(), query.getLimit(), + combinedSortOrders, query.getInMemorySorting(), + query.getFilter().orElse(null)); } /** - * Sets a default sorting order to the data provider. + * Sets a list of sort orders 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(SortOrder) * * @param sortOrders - * a list of sorting information containing field ids and - * directions - * @return new data provider with modified sorting + * a list of sort orders to set, not <code>null</code> */ - @SuppressWarnings("serial") - public BackEndDataProvider<T, F> sortingBy( - List<SortOrder<String>> sortOrders) { - BackEndDataProvider<T, F> parent = this; - return new BackEndDataProvider<T, F>(query -> { - List<SortOrder<String>> queryOrder = new ArrayList<>( - query.getSortOrders()); - queryOrder.addAll(sortOrders); - return parent.fetch(new Query<>(query.getOffset(), query.getLimit(), - queryOrder, query.getInMemorySorting(), - query.getFilter().orElse(null))); - }, sizeCallback) { - - @Override - public Registration addDataProviderListener( - DataProviderListener listener) { - return parent.addDataProviderListener(listener); - } + public void setSortOrders(List<SortOrder<String>> sortOrders) { + this.sortOrders = Objects.requireNonNull(sortOrders, + "Sort orders cannot be null"); + refreshAll(); + } - @Override - public void refreshAll() { - parent.refreshAll(); - } - }; + /** + * Sets a single sort order 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 #setSortOrders(List) + * + * @param sortOrder + * a sort order to set, or <code>null</code> to clear any + * previously set sort orders + */ + public void setSortOrder(SortOrder<String> sortOrder) { + if (sortOrder == null) { + setSortOrders(Collections.emptyList()); + } else { + setSortOrders(Collections.singletonList(sortOrder)); + } } @Override 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 c6860c26cc..e1c8e8867c 100644 --- a/server/src/main/java/com/vaadin/data/provider/ListDataProvider.java +++ b/server/src/main/java/com/vaadin/data/provider/ListDataProvider.java @@ -19,11 +19,12 @@ import java.util.Collection; import java.util.Comparator; import java.util.Objects; import java.util.Optional; -import java.util.function.Function; import java.util.stream.Stream; +import com.vaadin.data.ValueProvider; +import com.vaadin.server.SerializableComparator; import com.vaadin.server.SerializablePredicate; -import com.vaadin.shared.Registration; +import com.vaadin.shared.data.sort.SortDirection; /** * {@link DataProvider} wrapper for {@link Collection}s. This class does not @@ -36,7 +37,7 @@ public class ListDataProvider<T> extends AbstractDataProvider<T, SerializablePredicate<T>> implements AppendableFilterDataProvider<T, SerializablePredicate<T>> { - private Comparator<T> sortOrder = null; + private SerializableComparator<T> sortOrder = null; private final Collection<T> backend; /** @@ -55,26 +56,6 @@ public class ListDataProvider<T> sortOrder = null; } - /** - * Chaining constructor for making modified {@link ListDataProvider}s. This - * Constructor is used internally for making sorted and filtered variants of - * a base data provider with actual data. - * <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 backend data from the original list data provider - * @param sortOrder - * a {@link Comparator} providing the needed sorting order - * - */ - protected ListDataProvider(Collection<T> items, Comparator<T> sortOrder) { - this(items); - this.sortOrder = sortOrder; - } - @Override public Stream<T> fetch(Query<T, SerializablePredicate<T>> query) { Stream<T> stream = backend.stream() @@ -92,64 +73,140 @@ public class ListDataProvider<T> 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) backend.stream() + .filter(t -> query.getFilter().orElse(p -> true).test(t)) + .count(); + } + /** - * Creates a new list data provider based on this list data provider with - * the given sort order. + * 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> - * <b>NOTE</b>: this data provider is not modified in any way. + * 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 sortOrder - * a {@link Comparator} providing the needed sorting order - * @return new data provider with modified sorting + * a comparator to use, or <code>null</code> to clear any + * previously set sort order */ - @SuppressWarnings("serial") - public ListDataProvider<T> sortingBy(Comparator<T> sortOrder) { - ListDataProvider<T> parent = this; - return new ListDataProvider<T>(backend, sortOrder) { - - @Override - public Registration addDataProviderListener( - DataProviderListener listener) { - return parent.addDataProviderListener(listener); - } - - @Override - public void refreshAll() { - parent.refreshAll(); - } - }; + public void setSortComparator(SerializableComparator<T> sortOrder) { + this.sortOrder = sortOrder; + refreshAll(); } /** - * Creates a new list data provider based on this list data provider with - * the given sort order. + * 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> - * <b>NOTE</b>: this data provider is not modified in any way. + * 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> - * This method is a short-hand for - * {@code sortingBy(Comparator.comparing(sortOrder))}. + * 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 sortOrder - * function to sort by, not {@code null} - * @param <U> - * the type of the Comparable sort key - * @return new data provider with modified sorting + * a comparator to add, not <code>null</code> */ - public <U extends Comparable<? super U>> ListDataProvider<T> sortingBy( - Function<T, U> sortOrder) { - return sortingBy(Comparator.comparing(sortOrder)); + public void addSortComparator(SerializableComparator<T> sortOrder) { + Objects.requireNonNull(sortOrder, "Sort order to add cannot be null"); + + SerializableComparator<T> originalComparator = this.sortOrder; + if (originalComparator == null) { + setSortComparator(sortOrder); + } else { + setSortComparator((a, b) -> { + int result = originalComparator.compare(a, b); + if (result == 0) { + result = sortOrder.compare(a, b); + } + return result; + }); + } } - @Override - public boolean isInMemory() { - return true; + /** + * 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)); } - @Override - public int size(Query<T, SerializablePredicate<T>> query) { - return (int) backend.stream() - .filter(t -> query.getFilter().orElse(p -> true).test(t)) - .count(); + 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; } @Override diff --git a/server/src/test/java/com/vaadin/data/provider/BackendDataProviderTest.java b/server/src/test/java/com/vaadin/data/provider/BackendDataProviderTest.java index 585abc230a..5504861165 100644 --- a/server/src/test/java/com/vaadin/data/provider/BackendDataProviderTest.java +++ b/server/src/test/java/com/vaadin/data/provider/BackendDataProviderTest.java @@ -53,8 +53,8 @@ public class BackendDataProviderTest extends } @Override - protected BackEndDataProvider<StrBean, SerializablePredicate<StrBean>> sortingBy( - List<SortOrder<String>> sortOrder, Comparator<StrBean> comp) { - return getDataProvider().sortingBy(sortOrder); + protected void setSortOrder(List<SortOrder<String>> sortOrder, + Comparator<StrBean> comp) { + getDataProvider().setSortOrders(sortOrder); } } 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 da909af29a..ccbb0b0c1a 100644 --- a/server/src/test/java/com/vaadin/data/provider/DataProviderTestBase.java +++ b/server/src/test/java/com/vaadin/data/provider/DataProviderTestBase.java @@ -49,7 +49,7 @@ public abstract class DataProviderTestBase<D extends DataProvider<StrBean, Seria return dataProvider; } - protected abstract D sortingBy(List<SortOrder<String>> sortOrder, + protected abstract void setSortOrder(List<SortOrder<String>> sortOrder, Comparator<StrBean> comp); private Query<StrBean, SerializablePredicate<StrBean>> createQuery( @@ -110,11 +110,12 @@ public abstract class DataProviderTestBase<D extends DataProvider<StrBean, Seria public void testDefaultSortWithSpecifiedPostSort() { Comparator<StrBean> comp = Comparator.comparing(StrBean::getValue) .thenComparing(Comparator.comparing(StrBean::getId).reversed()); - List<StrBean> list = sortingBy( - Sort.asc("value").thenDesc("id").build(), comp) - .fetch(createQuery(Sort.asc("randomNumber").build(), - Comparator.comparing(StrBean::getRandomNumber))) - .collect(Collectors.toList()); + setSortOrder(Sort.asc("value").thenDesc("id").build(), comp); + + List<StrBean> list = dataProvider + .fetch(createQuery(Sort.asc("randomNumber").build(), + Comparator.comparing(StrBean::getRandomNumber))) + .collect(Collectors.toList()); Assert.assertEquals("Sorted data and original data sizes don't match", data.size(), list.size()); @@ -141,9 +142,11 @@ public abstract class DataProviderTestBase<D extends DataProvider<StrBean, Seria @Test public void testDefaultSortWithFunction() { - List<StrBean> list = sortingBy(Sort.asc("value").build(), - Comparator.comparing(StrBean::getValue)).fetch(new Query<>()) - .collect(Collectors.toList()); + setSortOrder(Sort.asc("value").build(), + Comparator.comparing(StrBean::getValue)); + + List<StrBean> list = dataProvider.fetch(new Query<>()) + .collect(Collectors.toList()); Assert.assertEquals("Sorted data and original data sizes don't match", data.size(), list.size()); @@ -158,112 +161,6 @@ public abstract class DataProviderTestBase<D extends DataProvider<StrBean, Seria } @Test - public void refreshAll_changeBeanInstance() { - StrBean bean = new StrBean("foo", -1, hashCode()); - int size = dataProvider.size(new Query<>()); - - data.set(0, bean); - dataProvider.refreshAll(); - - List<StrBean> 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(new Query<>())); - } - - @Test - public void refreshAll_updateBean() { - int size = dataProvider.size(new Query<>()); - - StrBean bean = data.get(0); - bean.setValue("foo"); - dataProvider.refreshAll(); - - List<StrBean> list = dataProvider.fetch(new Query<>()) - .collect(Collectors.toList()); - StrBean first = list.get(0); - Assert.assertEquals("foo", first.getValue()); - - Assert.assertEquals(size, dataProvider.size(new Query<>())); - } - - @Test - public void refreshAll_sortingBy_changeBeanInstance() { - StrBean bean = new StrBean("foo", -1, hashCode()); - int size = dataProvider.size(new Query<>()); - - data.set(0, bean); - - D dSource = sortingBy(Sort.asc("id").build(), - Comparator.comparing(StrBean::getId)); - dSource.refreshAll(); - - List<StrBean> 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(new Query<>())); - } - - @Test - public void refreshAll_addBeanInstance() { - StrBean bean = new StrBean("foo", -1, hashCode()); - - int size = dataProvider.size(new Query<>()); - - data.add(0, bean); - dataProvider.refreshAll(); - - List<StrBean> 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(new Query<>())); - } - - @Test - public void refreshAll_removeBeanInstance() { - int size = dataProvider.size(new Query<>()); - - data.remove(0); - dataProvider.refreshAll(); - - Assert.assertEquals(size - 1, dataProvider.size(new Query<>())); - } - - @Test - public void refreshAll_fromParentToSortedBy() { - D sortedDataProvider = sortingBy(Sort.asc("randomNumber").build(), - Comparator.comparing(StrBean::getRandomNumber)); - - CountingListener listener = new CountingListener(); - sortedDataProvider.addDataProviderListener(listener); - - Assert.assertEquals("Listener was not called prematurely", 0, - listener.getCounter()); - - dataProvider.refreshAll(); - - Assert.assertEquals("Listener was not called correctly", 1, - listener.getCounter()); - - sortedDataProvider.refreshAll(); - - Assert.assertEquals("Listener was not called correctly", 2, - listener.getCounter()); - } - - @Test public void filteringListDataProvider_convertFilter() { DataProvider<StrBean, String> strFilterDataProvider = dataProvider .convertFilter( 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 148b5f98f8..ad7c3b1f46 100644 --- a/server/src/test/java/com/vaadin/data/provider/ListDataProviderTest.java +++ b/server/src/test/java/com/vaadin/data/provider/ListDataProviderTest.java @@ -2,11 +2,14 @@ package com.vaadin.data.provider; import java.util.Comparator; import java.util.List; +import java.util.stream.Collectors; import org.junit.Assert; import org.junit.Test; +import com.vaadin.server.SerializableComparator; import com.vaadin.server.SerializablePredicate; +import com.vaadin.shared.data.sort.SortDirection; public class ListDataProviderTest extends DataProviderTestBase<ListDataProvider<StrBean>> { @@ -75,10 +78,53 @@ public class ListDataProviderTest .size(new Query<>("Zyx"))); } + @Test + public void setSortByProperty_ascending() { + ListDataProvider<StrBean> dataProvider = getDataProvider(); + + dataProvider.setSortOrder(StrBean::getId, SortDirection.ASCENDING); + + int[] threeFirstIds = dataProvider.fetch(new Query<>()) + .mapToInt(StrBean::getId).limit(3).toArray(); + + Assert.assertArrayEquals(new int[] { 0, 1, 2 }, threeFirstIds); + } + + @Test + public void setSortByProperty_descending() { + ListDataProvider<StrBean> dataProvider = getDataProvider(); + + dataProvider.setSortOrder(StrBean::getId, SortDirection.DESCENDING); + + int[] threeFirstIds = dataProvider.fetch(new Query<>()) + .mapToInt(StrBean::getId).limit(3).toArray(); + + Assert.assertArrayEquals(new int[] { 98, 97, 96 }, threeFirstIds); + } + + @Test + public void testMultipleSortOrder_firstAddedWins() { + ListDataProvider<StrBean> dataProvider = getDataProvider(); + + dataProvider.addSortOrder(StrBean::getValue, SortDirection.DESCENDING); + dataProvider.addSortOrder(StrBean::getId, SortDirection.DESCENDING); + + List<StrBean> threeFirstItems = dataProvider.fetch(new Query<>()) + .limit(3).collect(Collectors.toList()); + + // First one is Xyz + Assert.assertEquals(new StrBean("Xyz", 10, 100), + threeFirstItems.get(0)); + // The following are Foos ordered by id + Assert.assertEquals(new StrBean("Foo", 93, 2), threeFirstItems.get(1)); + Assert.assertEquals(new StrBean("Foo", 91, 2), threeFirstItems.get(2)); + } + @Override - protected ListDataProvider<StrBean> sortingBy( - List<SortOrder<String>> sortOrder, Comparator<StrBean> comp) { - return getDataProvider().sortingBy(comp); + protected void setSortOrder(List<SortOrder<String>> sortOrder, + Comparator<StrBean> comp) { + SerializableComparator<StrBean> serializableComp = comp::compare; + getDataProvider().setSortComparator(serializableComp); } } diff --git a/server/src/test/java/com/vaadin/data/provider/StrBean.java b/server/src/test/java/com/vaadin/data/provider/StrBean.java index 29c188b82d..b596ccdde5 100644 --- a/server/src/test/java/com/vaadin/data/provider/StrBean.java +++ b/server/src/test/java/com/vaadin/data/provider/StrBean.java @@ -3,6 +3,7 @@ package com.vaadin.data.provider; import java.io.Serializable; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.Random; class StrBean implements Serializable { @@ -47,6 +48,24 @@ class StrBean implements Serializable { } @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else if (obj instanceof StrBean) { + StrBean that = (StrBean) obj; + return that.id == this.id && that.randomNumber == this.randomNumber + && Objects.equals(this.value, that.value); + } else { + return false; + } + } + + @Override + public int hashCode() { + return Objects.hash(id, randomNumber, value); + } + + @Override public String toString() { return "{ " + value + ", " + randomNumber + ", " + id + " }"; } |