Skip to content

Commit

Permalink
Provide in-memory sorting information in Query
Browse files Browse the repository at this point in the history
Change-Id: Iebafff6079816c08e5a4d144f6891d1379751f12
  • Loading branch information
Teemu Suo-Anttila authored and Vaadin Code Review committed Nov 29, 2016
1 parent 907e241 commit 1344356
Show file tree
Hide file tree
Showing 9 changed files with 78 additions and 54 deletions.
Expand Up @@ -32,8 +32,8 @@
*/ */
public class BackEndDataProvider<T, F> extends AbstractDataProvider<T, F> { public class BackEndDataProvider<T, F> extends AbstractDataProvider<T, F> {


private final SerializableFunction<Query<F>, Stream<T>> request; private final SerializableFunction<Query<T, F>, Stream<T>> request;
private final SerializableFunction<Query<F>, Integer> sizeCallback; private final SerializableFunction<Query<T, F>, Integer> sizeCallback;


/** /**
* Constructs a new DataProvider to request data from an arbitrary back end * Constructs a new DataProvider to request data from an arbitrary back end
Expand All @@ -45,21 +45,21 @@ public class BackEndDataProvider<T, F> extends AbstractDataProvider<T, F> {
* function that return the amount of data in back end for query * function that return the amount of data in back end for query
*/ */
public BackEndDataProvider( public BackEndDataProvider(
SerializableFunction<Query<F>, Stream<T>> request, SerializableFunction<Query<T, F>, Stream<T>> request,
SerializableFunction<Query<F>, Integer> sizeCallback) { SerializableFunction<Query<T, F>, Integer> sizeCallback) {
Objects.requireNonNull(request, "Request function can't be null"); Objects.requireNonNull(request, "Request function can't be null");
Objects.requireNonNull(sizeCallback, "Size callback can't be null"); Objects.requireNonNull(sizeCallback, "Size callback can't be null");
this.request = request; this.request = request;
this.sizeCallback = sizeCallback; this.sizeCallback = sizeCallback;
} }


@Override @Override
public Stream<T> fetch(Query<F> query) { public Stream<T> fetch(Query<T, F> query) {
return request.apply(query); return request.apply(query);
} }


@Override @Override
public int size(Query<F> query) { public int size(Query<T, F> query) {
return sizeCallback.apply(query); return sizeCallback.apply(query);
} }


Expand All @@ -77,9 +77,9 @@ public BackEndDataProvider<T, F> sortingBy(
List<SortOrder<String>> queryOrder = new ArrayList<>( List<SortOrder<String>> queryOrder = new ArrayList<>(
query.getSortOrders()); query.getSortOrders());
queryOrder.addAll(sortOrders); queryOrder.addAll(sortOrders);
return request return request.apply(new Query<>(query.getLimit(),
.apply(new Query<>(query.getLimit(), query.getOffset(), query.getOffset(), queryOrder, query.getInMemorySorting(),
queryOrder, query.getFilter().orElse(null))); query.getFilter().orElse(null)));
}, sizeCallback); }, sizeCallback);
} }


Expand Down
17 changes: 3 additions & 14 deletions server/src/main/java/com/vaadin/server/data/DataCommunicator.java
Expand Up @@ -237,20 +237,9 @@ public void beforeClientResponse(boolean initial) {
int offset = pushRows.getStart(); int offset = pushRows.getStart();
int limit = pushRows.length(); int limit = pushRows.length();


Stream<T> rowsToPush; Stream<T> rowsToPush = getDataProvider().fetch(new Query<>(offset,

limit, backEndSorting, inMemorySorting, filter));
if (getDataProvider().isInMemory()) {
// TODO: Move in-memory sorting to Query.
// We can safely request all the data when in memory
rowsToPush = getDataProvider().fetch(new Query<>(filter));
if (inMemorySorting != null) {
rowsToPush = rowsToPush.sorted(inMemorySorting);
}
rowsToPush = rowsToPush.skip(offset).limit(limit);
} else {
rowsToPush = getDataProvider().fetch(
new Query<>(offset, limit, backEndSorting, filter));
}
pushData(offset, rowsToPush); pushData(offset, rowsToPush);
} }


Expand Down
4 changes: 2 additions & 2 deletions server/src/main/java/com/vaadin/server/data/DataProvider.java
Expand Up @@ -57,7 +57,7 @@ public interface DataProvider<T, F> extends Serializable {
* query with sorting and filtering * query with sorting and filtering
* @return the size of the data provider * @return the size of the data provider
*/ */
int size(Query<F> t); int size(Query<T, F> t);


/** /**
* Fetches data from this DataProvider using given {@code query}. * Fetches data from this DataProvider using given {@code query}.
Expand All @@ -67,7 +67,7 @@ public interface DataProvider<T, F> extends Serializable {
* @return the result of the query request: a stream of data objects, not * @return the result of the query request: a stream of data objects, not
* {@code null} * {@code null}
*/ */
Stream<T> fetch(Query<F> query); Stream<T> fetch(Query<T, F> query);


/** /**
* Refreshes all data based on currently available data in the underlying * Refreshes all data based on currently available data in the underlying
Expand Down
Expand Up @@ -100,15 +100,15 @@ public Registration addDataProviderListener(DataProviderListener listener) {
} }


@Override @Override
public int size(Query<F> t) { public int size(Query<T, F> t) {
return dataProvider.size(new Query<M>(t.getOffset(), t.getLimit(), return dataProvider.size(new Query<>(t.getOffset(), t.getLimit(),
t.getSortOrders(), getFilter(t))); t.getSortOrders(), t.getInMemorySorting(), getFilter(t)));
} }


@Override @Override
public Stream<T> fetch(Query<F> t) { public Stream<T> fetch(Query<T, F> t) {
return dataProvider.fetch(new Query<M>(t.getOffset(), t.getLimit(), return dataProvider.fetch(new Query<>(t.getOffset(), t.getLimit(),
t.getSortOrders(), getFilter(t))); t.getSortOrders(), t.getInMemorySorting(), getFilter(t)));
} }


/** /**
Expand All @@ -118,7 +118,7 @@ public Stream<T> fetch(Query<F> t) {
* the current query * the current query
* @return filter for the modified Query * @return filter for the modified Query
*/ */
protected abstract M getFilter(Query<F> query); protected abstract M getFilter(Query<T, F> query);


/** /**
* Creates a data provider wrapper with a static filter set to each Query. * Creates a data provider wrapper with a static filter set to each Query.
Expand All @@ -144,7 +144,7 @@ public static <T, F> DataProvider<T, Void> filter(
return new FilteringDataProviderWrapper<T, Void, F>(dataProvider) { return new FilteringDataProviderWrapper<T, Void, F>(dataProvider) {


@Override @Override
protected F getFilter(Query<Void> query) { protected F getFilter(Query<T, Void> query) {
return filter; return filter;
} }
}; };
Expand Down Expand Up @@ -176,7 +176,7 @@ public static <T, F, M> DataProvider<T, F> convert(
return new FilteringDataProviderWrapper<T, F, M>(dataProvider) { return new FilteringDataProviderWrapper<T, F, M>(dataProvider) {


@Override @Override
protected M getFilter(Query<F> query) { protected M getFilter(Query<T, F> query) {
return query.getFilter().map(mapper).orElse(null); return query.getFilter().map(mapper).orElse(null);
} }
}; };
Expand All @@ -203,7 +203,7 @@ public static <T, F> AppendableFilterDataProvider<T, F> chain(
return new AppendableFilterDataProviderWrapper<T, F>(dataProvider) { return new AppendableFilterDataProviderWrapper<T, F>(dataProvider) {


@Override @Override
protected F getFilter(Query<F> query) { protected F getFilter(Query<T, F> query) {
return combineFilters(filter, query.getFilter()); return combineFilters(filter, query.getFilter());
} }
}; };
Expand Down
20 changes: 14 additions & 6 deletions server/src/main/java/com/vaadin/server/data/ListDataProvider.java
Expand Up @@ -18,6 +18,7 @@
import java.util.Collection; import java.util.Collection;
import java.util.Comparator; import java.util.Comparator;
import java.util.Objects; import java.util.Objects;
import java.util.Optional;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Stream; import java.util.stream.Stream;


Expand All @@ -34,7 +35,7 @@ public class ListDataProvider<T>
extends AbstractDataProvider<T, SerializablePredicate<T>> extends AbstractDataProvider<T, SerializablePredicate<T>>
implements AppendableFilterDataProvider<T, SerializablePredicate<T>> { implements AppendableFilterDataProvider<T, SerializablePredicate<T>> {


private Comparator<T> sortOrder; private Comparator<T> sortOrder = null;
private final Collection<T> backend; private final Collection<T> backend;


/** /**
Expand Down Expand Up @@ -67,13 +68,20 @@ protected ListDataProvider(Collection<T> items, Comparator<T> sortOrder) {
} }


@Override @Override
public Stream<T> fetch(Query<SerializablePredicate<T>> query) { public Stream<T> fetch(Query<T, SerializablePredicate<T>> query) {
Stream<T> stream = backend.stream() Stream<T> stream = backend.stream()
.filter(t -> query.getFilter().orElse(p -> true).test(t)); .filter(t -> query.getFilter().orElse(p -> true).test(t));
if (sortOrder != null) {
stream = stream.sorted(sortOrder); Optional<Comparator<T>> comparing = Stream
.of(sortOrder, query.getInMemorySorting())
.filter(c -> c != null)
.reduce((c1, c2) -> c1.thenComparing(c2));

if (comparing.isPresent()) {
stream = stream.sorted(comparing.get());
} }
return stream;
return stream.skip(query.getOffset()).limit(query.getLimit());
} }


/** /**
Expand Down Expand Up @@ -116,7 +124,7 @@ public boolean isInMemory() {
} }


@Override @Override
public int size(Query<SerializablePredicate<T>> query) { public int size(Query<T, SerializablePredicate<T>> query) {
return (int) backend.stream() return (int) backend.stream()
.filter(t -> query.getFilter().orElse(p -> true).test(t)) .filter(t -> query.getFilter().orElse(p -> true).test(t))
.count(); .count();
Expand Down
35 changes: 31 additions & 4 deletions server/src/main/java/com/vaadin/server/data/Query.java
Expand Up @@ -17,23 +17,27 @@


import java.io.Serializable; import java.io.Serializable;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;


/** /**
* Immutable query object used to request data from a backend. Contains index * Immutable query object used to request data from a backend. Contains index
* limits, sorting and filtering information. * limits, sorting and filtering information.
* *
* @param <T>
* bean type
* @param <F> * @param <F>
* filter type * filter type
* *
* @since 8.0 * @since 8.0
*/ */
public class Query<F> implements Serializable { public class Query<T, F> implements Serializable {


private final int offset; private final int offset;
private final int limit; private final int limit;
private final List<SortOrder<String>> sortOrders; private final List<SortOrder<String>> sortOrders;
private final Comparator<T> inMemorySorting;
private final F filter; private final F filter;


/** /**
Expand All @@ -44,6 +48,7 @@ public Query() {
offset = 0; offset = 0;
limit = Integer.MAX_VALUE; limit = Integer.MAX_VALUE;
sortOrders = Collections.emptyList(); sortOrders = Collections.emptyList();
inMemorySorting = null;
filter = null; filter = null;
} }


Expand All @@ -59,6 +64,7 @@ public Query(F filter) {
offset = 0; offset = 0;
limit = Integer.MAX_VALUE; limit = Integer.MAX_VALUE;
sortOrders = Collections.emptyList(); sortOrders = Collections.emptyList();
inMemorySorting = null;
this.filter = filter; this.filter = filter;
} }


Expand All @@ -71,15 +77,18 @@ public Query(F filter) {
* @param limit * @param limit
* fetched item count * fetched item count
* @param sortOrders * @param sortOrders
* sorting order for fetching * sorting order for fetching; used for sorting backends
* @param inMemorySorting
* comparator for sorting in-memory data
* @param filter * @param filter
* filtering for fetching; can be null * filtering for fetching; can be null
*/ */
public Query(int offset, int limit, List<SortOrder<String>> sortOrders, public Query(int offset, int limit, List<SortOrder<String>> sortOrders,
F filter) { Comparator<T> inMemorySorting, F filter) {
this.offset = offset; this.offset = offset;
this.limit = limit; this.limit = limit;
this.sortOrders = sortOrders; this.sortOrders = sortOrders;
this.inMemorySorting = inMemorySorting;
this.filter = filter; this.filter = filter;
} }


Expand All @@ -105,7 +114,12 @@ public int getLimit() {
} }


/** /**
* Gets the sorting for items to fetch. * Gets the sorting for items to fetch. This list of sort orders is used for
* sorting backends.
* <p>
* <strong>Note: </strong> Sort orders and in-memory sorting are mutually
* exclusive. If the {@link DataProvider} handles one, it should ignore the
* other.
* *
* @return list of sort orders * @return list of sort orders
*/ */
Expand All @@ -121,4 +135,17 @@ public List<SortOrder<String>> getSortOrders() {
public Optional<F> getFilter() { public Optional<F> getFilter() {
return Optional.ofNullable(filter); return Optional.ofNullable(filter);
} }

/**
* Gets the comparator for sorting in-memory data.
* <p>
* <strong>Note: </strong> Sort orders and in-memory sorting are mutually
* exclusive. If the {@link DataProvider} handles one, it should ignore the
* other.
*
* @return sorting comparator
*/
public Comparator<T> getInMemorySorting() {
return inMemorySorting;
}
} }
Expand Up @@ -32,12 +32,12 @@ public class AbstractDataProviderTest {
private static class TestDataProvider private static class TestDataProvider
extends AbstractDataProvider<Object, Object> { extends AbstractDataProvider<Object, Object> {
@Override @Override
public Stream<Object> fetch(Query<Object> t) { public Stream<Object> fetch(Query<Object, Object> t) {
return null; return null;
} }


@Override @Override
public int size(Query<Object> t) { public int size(Query<Object, Object> t) {
return 0; return 0;
} }


Expand Down
Expand Up @@ -156,16 +156,15 @@ private void checkFiltering(String filterText, String nonMatchingFilterText,


Assert.assertEquals( Assert.assertEquals(
"ComboBox filtered out results with no filter applied", "ComboBox filtered out results with no filter applied",
totalMatches, dataProvider.size(new Query<String>())); totalMatches, dataProvider.size(new Query<>()));
Assert.assertEquals( Assert.assertEquals(
"ComboBox filtered out results with empty filter string", "ComboBox filtered out results with empty filter string",
totalMatches, dataProvider.size(new Query<String>(""))); totalMatches, dataProvider.size(new Query<>("")));
Assert.assertEquals("ComboBox filtered out wrong number of results", Assert.assertEquals("ComboBox filtered out wrong number of results",
matchingResults, matchingResults, dataProvider.size(new Query<>(filterText)));
dataProvider.size(new Query<String>(filterText)));
Assert.assertEquals( Assert.assertEquals(
"ComboBox should have no results with a non-matching filter", 0, "ComboBox should have no results with a non-matching filter", 0,
dataProvider.size(new Query<String>(nonMatchingFilterText))); dataProvider.size(new Query<>(nonMatchingFilterText)));
} }


private List<Person> getPersonCollection() { private List<Person> getPersonCollection() {
Expand Down
Expand Up @@ -19,7 +19,8 @@ public ItemDataProvider(int size) {
q -> size(q, size)); q -> size(q, size));
} }


private static Stream<String> itemStream(Query<String> q, int size) { private static Stream<String> itemStream(Query<String, String> q,
int size) {
Stream<String> stream = IntStream.range(0, size) Stream<String> stream = IntStream.range(0, size)
.mapToObj(i -> "Item " + i); .mapToObj(i -> "Item " + i);
String filterText = q.getFilter().orElse("").toLowerCase(Locale.US); String filterText = q.getFilter().orElse("").toLowerCase(Locale.US);
Expand All @@ -32,7 +33,7 @@ private static Stream<String> itemStream(Query<String> q, int size) {
text -> text.toLowerCase(Locale.US).contains(filterText)); text -> text.toLowerCase(Locale.US).contains(filterText));
} }


private static int size(Query<String> q, int size) { private static int size(Query<String, String> q, int size) {
if (!q.getFilter().orElse("").isEmpty()) { if (!q.getFilter().orElse("").isEmpty()) {
return (int) itemStream(q, size).count(); return (int) itemStream(q, size).count();
} }
Expand Down

0 comments on commit 1344356

Please sign in to comment.