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.
*/
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;
@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
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
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;
/**
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()
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
}
@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);
}
}
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(
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());
@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());
}
}
- @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
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>> {
.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);
}
}
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
import java.util.Random;
class StrBean implements Serializable {
return data;
}
+ @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 + " }";