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> {

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

/**
* 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
*/
public BackEndDataProvider(
SerializableFunction<Query<F>, Stream<T>> request,
SerializableFunction<Query<F>, Integer> sizeCallback) {
SerializableFunction<Query<T, F>, Stream<T>> request,
SerializableFunction<Query<T, F>, Integer> sizeCallback) {
Objects.requireNonNull(request, "Request function can't be null");
Objects.requireNonNull(sizeCallback, "Size callback can't be null");
this.request = request;
this.sizeCallback = sizeCallback;
}

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

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

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

Stream<T> rowsToPush;

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));
}
Stream<T> rowsToPush = getDataProvider().fetch(new Query<>(offset,
limit, backEndSorting, inMemorySorting, filter));

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
* @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}.
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
* {@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
Expand Down
Expand Up @@ -100,15 +100,15 @@ public Registration addDataProviderListener(DataProviderListener listener) {
}

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

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

/**
Expand All @@ -118,7 +118,7 @@ public Stream<T> fetch(Query<F> t) {
* the current 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.
Expand All @@ -144,7 +144,7 @@ public static <T, F> DataProvider<T, Void> filter(
return new FilteringDataProviderWrapper<T, Void, F>(dataProvider) {

@Override
protected F getFilter(Query<Void> query) {
protected F getFilter(Query<T, Void> query) {
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) {

@Override
protected M getFilter(Query<F> query) {
protected M getFilter(Query<T, F> query) {
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) {

@Override
protected F getFilter(Query<F> query) {
protected F getFilter(Query<T, F> query) {
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.Comparator;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Stream;

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

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

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

@Override
public Stream<T> fetch(Query<SerializablePredicate<T>> query) {
public Stream<T> fetch(Query<T, SerializablePredicate<T>> query) {
Stream<T> stream = backend.stream()
.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
public int size(Query<SerializablePredicate<T>> query) {
public int size(Query<T, SerializablePredicate<T>> query) {
return (int) backend.stream()
.filter(t -> query.getFilter().orElse(p -> true).test(t))
.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.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;

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

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

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

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

Expand All @@ -71,15 +77,18 @@ public Query(F filter) {
* @param limit
* fetched item count
* @param sortOrders
* sorting order for fetching
* sorting order for fetching; used for sorting backends
* @param inMemorySorting
* comparator for sorting in-memory data
* @param filter
* filtering for fetching; can be null
*/
public Query(int offset, int limit, List<SortOrder<String>> sortOrders,
F filter) {
Comparator<T> inMemorySorting, F filter) {
this.offset = offset;
this.limit = limit;
this.sortOrders = sortOrders;
this.inMemorySorting = inMemorySorting;
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
*/
Expand All @@ -121,4 +135,17 @@ public List<SortOrder<String>> getSortOrders() {
public Optional<F> getFilter() {
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
extends AbstractDataProvider<Object, Object> {
@Override
public Stream<Object> fetch(Query<Object> t) {
public Stream<Object> fetch(Query<Object, Object> t) {
return null;
}

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

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

Assert.assertEquals(
"ComboBox filtered out results with no filter applied",
totalMatches, dataProvider.size(new Query<String>()));
totalMatches, dataProvider.size(new Query<>()));
Assert.assertEquals(
"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",
matchingResults,
dataProvider.size(new Query<String>(filterText)));
matchingResults, dataProvider.size(new Query<>(filterText)));
Assert.assertEquals(
"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() {
Expand Down
Expand Up @@ -19,7 +19,8 @@ public ItemDataProvider(int 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)
.mapToObj(i -> "Item " + i);
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));
}

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

0 comments on commit 1344356

Please sign in to comment.