package com.vaadin.data;
import com.vaadin.data.provider.DataProvider;
+import com.vaadin.server.SerializableFunction;
/**
* A generic interface for listing components that use a filterable data
/**
* Returns the source of data items used by this listing.
*
- * @return the data provider, not null
+ * @return the data provider, not <code>null</code>
*/
- public DataProvider<T, F> getDataProvider();
+ public DataProvider<T, ?> getDataProvider();
/**
* Sets the data provider for this listing. The data provider is queried for
* displayed items as needed.
*
* @param dataProvider
- * the data provider, not null
+ * the data provider, not <code>null</code>
*/
- public void setDataProvider(DataProvider<T, F> dataProvider);
+ public default void setDataProvider(DataProvider<T, F> dataProvider) {
+ setDataProvider(dataProvider, SerializableFunction.identity());
+ }
+
+ /**
+ * Sets the data provider and filter converter for this listing. The data
+ * provider is queried for displayed items as needed.
+ *
+ * @param dataProvider
+ * the data provider, not <code>null</code>
+ * @param filterConverter
+ * a function that converts filter values produced by this
+ * listing into filter values expected by the provided data
+ * provider, not <code>null</code>
+ */
+ public <C> void setDataProvider(DataProvider<T, C> dataProvider,
+ SerializableFunction<F, C> filterConverter);
}
import com.vaadin.server.AbstractExtension;
import com.vaadin.server.KeyMapper;
+import com.vaadin.server.SerializableConsumer;
import com.vaadin.shared.Range;
import com.vaadin.shared.Registration;
import com.vaadin.shared.data.DataCommunicatorClientRpc;
*
* @param <T>
* the bean type
- * @param <F>
- * the filter type
*
* @since 8.0
*/
-public class DataCommunicator<T, F> extends AbstractExtension {
+public class DataCommunicator<T> extends AbstractExtension {
private Registration dataProviderUpdateRegistration;
private final ActiveDataHandler handler = new ActiveDataHandler();
/** Empty default data provider */
- private DataProvider<T, F> dataProvider = new CallbackDataProvider<>(
+ private DataProvider<T, ?> dataProvider = new CallbackDataProvider<>(
q -> Stream.empty(), q -> 0);
private final DataKeyMapper<T> keyMapper;
private int minPushSize = 40;
private Range pushRows = Range.withLength(0, minPushSize);
- private F filter;
+ private Object filter;
private Comparator<T> inMemorySorting;
private final List<QuerySortOrder> backEndSorting = new ArrayList<>();
private final DataCommunicatorClientRpc rpc;
}
if (initial || reset) {
- int dataProviderSize = getDataProvider().size(new Query<>(filter));
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ int dataProviderSize = getDataProvider().size(new Query(filter));
rpc.reset(dataProviderSize);
}
int offset = pushRows.getStart();
int limit = pushRows.length();
- Stream<T> rowsToPush = getDataProvider().fetch(new Query<>(offset,
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ Stream<T> rowsToPush = getDataProvider().fetch(new Query(offset,
limit, backEndSorting, inMemorySorting, filter));
pushData(offset, rowsToPush);
updatedData.add(data);
}
- /**
- * Sets the filter to use.
- *
- * @param filter
- * the filter
- */
- public void setFilter(F filter) {
- this.filter = filter;
- reset();
- }
-
/**
* Sets the {@link Comparator} to use with in-memory sorting.
*
*
* @return the data provider
*/
- public DataProvider<T, F> getDataProvider() {
+ public DataProvider<T, ?> getDataProvider() {
return dataProvider;
}
/**
* Sets the current data provider for this DataCommunicator.
+ * <p>
+ * The returned consumer can be used to set some other filter value that
+ * should be included in queries sent to the data provider. It is only valid
+ * until another data provider is set.
*
* @param dataProvider
- * the data provider to set, not null
+ * the data provider to set, not <code>null</code>
+ * @param initialFilter
+ * the initial filter value to use, or <code>null</code> to not
+ * use any initial filter value
+ * @return a consumer that accepts a new filter value to use
*/
- public void setDataProvider(DataProvider<T, F> dataProvider) {
+ public <F> SerializableConsumer<F> setDataProvider(
+ DataProvider<T, F> dataProvider, F initialFilter) {
Objects.requireNonNull(dataProvider, "data provider cannot be null");
+
+ filter = initialFilter;
this.dataProvider = dataProvider;
+
detachDataProviderListener();
dropAllData();
/*
attachDataProviderListener();
}
reset();
+
+ return filter -> {
+ if (this.dataProvider != dataProvider) {
+ throw new IllegalStateException(
+ "Filter slot is no longer valid after data provider has been changed");
+ }
+
+ if (!Objects.equals(this.filter, filter)) {
+ this.filter = filter;
+ reset();
+ }
+ };
}
/**
import com.vaadin.data.provider.Query;
import com.vaadin.server.AbstractExtension;
import com.vaadin.server.Resource;
+import com.vaadin.server.SerializableConsumer;
import com.vaadin.shared.extension.abstractlisting.AbstractListingExtensionState;
import com.vaadin.shared.ui.abstractlisting.AbstractListingState;
import com.vaadin.ui.Component.Focusable;
}
}
- private final DataCommunicator<T, ?> dataCommunicator;
+ private final DataCommunicator<T> dataCommunicator;
/**
* Creates a new {@code AbstractListing} with a default data communicator.
* @param dataCommunicator
* the data communicator to use, not null
*/
- protected AbstractListing(DataCommunicator<T, ?> dataCommunicator) {
+ protected AbstractListing(DataCommunicator<T> dataCommunicator) {
Objects.requireNonNull(dataCommunicator,
"dataCommunicator cannot be null");
addExtension(dataCommunicator);
}
- @SuppressWarnings({ "rawtypes", "unchecked" })
protected void internalSetDataProvider(DataProvider<T, ?> dataProvider) {
- // cast needed by compiler
- getDataCommunicator().setDataProvider((DataProvider) dataProvider);
+ internalSetDataProvider(dataProvider, null);
+ }
+
+ protected <F> SerializableConsumer<F> internalSetDataProvider(
+ DataProvider<T, F> dataProvider, F initialFilter) {
+ return getDataCommunicator().setDataProvider(dataProvider,
+ initialFilter);
}
protected DataProvider<T, ?> internalGetDataProvider() {
*
* @return the data communicator, not null
*/
- public DataCommunicator<T, ?> getDataCommunicator() {
+ public DataCommunicator<T> getDataCommunicator() {
return dataCommunicator;
}
* @param dataCommunicator
* the data communicator to use, not null
*/
- protected AbstractSingleSelect(DataCommunicator<T, ?> dataCommunicator) {
+ protected AbstractSingleSelect(DataCommunicator<T> dataCommunicator) {
super(dataCommunicator);
init();
}
@Override
public Registration addValueChangeListener(
HasValue.ValueChangeListener<T> listener) {
- return addSelectionListener(event -> listener.valueChange(
- new ValueChangeEvent<>(this, event.getOldValue(),
- event.isUserOriginated())));
+ return addSelectionListener(
+ event -> listener.valueChange(new ValueChangeEvent<>(this,
+ event.getOldValue(), event.isUserOriginated())));
}
@Override
import com.vaadin.server.ResourceReference;
import com.vaadin.server.SerializableBiPredicate;
import com.vaadin.server.SerializableConsumer;
+import com.vaadin.server.SerializableFunction;
import com.vaadin.shared.Registration;
import com.vaadin.shared.data.DataCommunicatorConstants;
import com.vaadin.shared.ui.combobox.ComboBoxConstants;
@Override
public void setFilter(String filterText) {
- getDataCommunicator().setFilter(filterText);
+ currentFilterText = filterText;
+ filterSlot.accept(filterText);
}
};
private StyleGenerator<T> itemStyleGenerator = item -> null;
+ private String currentFilterText;
+
+ private SerializableConsumer<String> filterSlot = filter -> {
+ // Just ignore when neither setDataProvider nor setItems has been called
+ };
+
/**
* Constructs an empty combo box without a caption. The content of the combo
* box can be set with {@link #setDataProvider(DataProvider)} or
* {@link #setItems(Collection)}
*/
public ComboBox() {
- super(new DataCommunicator<T, String>() {
+ super(new DataCommunicator<T>() {
@Override
protected DataKeyMapper<T> createKeyMapper() {
return new KeyMapper<T>() {
* Sets a list data provider with an item caption filter as the data
* provider of this combo box. The caption filter is used to compare the
* displayed caption of each item to the filter text entered by the user.
- * <p>
- * Note that this is a shorthand that calls
- * {@link #setDataProvider(DataProvider)} with a wrapper of the provided
- * list data provider. This means that {@link #getDataProvider()} will
- * return the wrapper instead of the original list data provider.
*
* @param captionFilter
* filter to check if an item is shown when user typed some text
// Must do getItemCaptionGenerator() for each operation since it might
// not be the same as when this method was invoked
- DataProvider<T, String> provider = listDataProvider.filteringBy(
- item -> getItemCaptionGenerator().apply(item), captionFilter);
- setDataProvider(provider);
+ setDataProvider(listDataProvider, filterText -> item -> captionFilter
+ .test(getItemCaptionGenerator().apply(item), filterText));
}
/**
}
@Override
- @SuppressWarnings("unchecked")
- public DataProvider<T, String> getDataProvider() {
- return (DataProvider<T, String>) internalGetDataProvider();
+ public DataProvider<T, ?> getDataProvider() {
+ return internalGetDataProvider();
}
@Override
- public void setDataProvider(DataProvider<T, String> dataProvider) {
- internalSetDataProvider(dataProvider);
- }
+ public <C> void setDataProvider(DataProvider<T, C> dataProvider,
+ SerializableFunction<String, C> filterConverter) {
+ Objects.requireNonNull(dataProvider, "dataProvider cannot be null");
+ Objects.requireNonNull(filterConverter,
+ "filterConverter cannot be null");
+
+ SerializableFunction<String, C> convertOrNull = filterText -> {
+ if (filterText == null) {
+ return null;
+ }
- @Override
- @SuppressWarnings("unchecked")
- public DataCommunicator<T, String> getDataCommunicator() {
- // Not actually an unsafe cast. DataCommunicator is final and set by
- // ComboBox.
- return (DataCommunicator<T, String>) super.getDataCommunicator();
+ return filterConverter.apply(filterText);
+ };
+
+ SerializableConsumer<C> providerFilterSlot = internalSetDataProvider(
+ dataProvider, convertOrNull.apply(currentFilterText));
+
+ filterSlot = filter -> providerFilterSlot
+ .accept(convertOrNull.apply(filter));
}
@Override
set.addAll(addedItems);
// refresh method is NOOP for items that are not present client side
- DataCommunicator<T, ?> dataCommunicator = getGrid()
+ DataCommunicator<T> dataCommunicator = getGrid()
.getDataCommunicator();
removedItems.forEach(dataCommunicator::refresh);
addedItems.forEach(dataCommunicator::refresh);
import org.mockito.Mockito;
import com.vaadin.server.MockVaadinSession;
-import com.vaadin.server.SerializablePredicate;
import com.vaadin.server.VaadinRequest;
import com.vaadin.server.VaadinService;
import com.vaadin.server.VaadinSession;
}
- private static class TestDataCommunicator
- extends DataCommunicator<Object, SerializablePredicate<Object>> {
+ private static class TestDataCommunicator extends DataCommunicator<Object> {
protected void extend(UI ui) {
super.extend(ui);
}
TestDataCommunicator communicator = new TestDataCommunicator();
TestDataProvider dataProvider = new TestDataProvider();
- communicator.setDataProvider(dataProvider);
+ communicator.setDataProvider(dataProvider, null);
Assert.assertFalse(dataProvider.isListenerAdded());
TestDataCommunicator communicator = new TestDataCommunicator();
TestDataProvider dataProvider = new TestDataProvider();
- communicator.setDataProvider(dataProvider);
+ communicator.setDataProvider(dataProvider, null);
communicator.extend(ui);
import org.junit.Test;
-import com.vaadin.server.SerializablePredicate;
-
public class ReplaceDataProviderTest {
public static class BeanWithEquals extends Bean {
private <SOME_BEAN> void doTest(IntFunction<SOME_BEAN> beanConstructor) {
- DataCommunicator<SOME_BEAN, SerializablePredicate<SOME_BEAN>> dataCommunicator = new DataCommunicator<>();
+ DataCommunicator<SOME_BEAN> dataCommunicator = new DataCommunicator<>();
List<SOME_BEAN> beans1 = createCollection(beanConstructor);
ListDataProvider<SOME_BEAN> dataProvider = new ListDataProvider<>(
beans1);
- dataCommunicator.setDataProvider(dataProvider);
+ dataCommunicator.setDataProvider(dataProvider, null);
dataCommunicator.pushData(1, beans1.stream());
SOME_BEAN bean1_17 = beans1.get(17);
List<SOME_BEAN> beans2 = createCollection(beanConstructor);
dataProvider = new ListDataProvider<>(beans2);
- dataCommunicator.setDataProvider(dataProvider);
+ dataCommunicator.setDataProvider(dataProvider, null);
dataCommunicator.pushData(1, beans2.stream());
SOME_BEAN bean2_17 = beans2.get(17);
*/
package com.vaadin.tests.server.component.combobox;
+import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.stream.Collectors;
import org.junit.Before;
import org.junit.Test;
+import com.vaadin.data.provider.DataCommunicator;
import com.vaadin.data.provider.DataProvider;
import com.vaadin.data.provider.ListDataProvider;
-import com.vaadin.data.provider.Query;
+import com.vaadin.server.ClientMethodInvocation;
+import com.vaadin.shared.ui.combobox.ComboBoxServerRpc;
import com.vaadin.tests.data.bean.Address;
import com.vaadin.tests.data.bean.Person;
import com.vaadin.tests.data.bean.Sex;
import com.vaadin.ui.ComboBox;
+import com.vaadin.ui.ComponentTest;
/**
* Test for ComboBox data providers and filtering.
checkFiltering("t", "Engel", 2, 1);
}
+ @Test
+ public void filterEmptyComboBox() {
+ // Testing that filtering doesn't cause problems in the edge case where
+ // neither setDataProvider nor setItems has been called
+ checkFiltering("foo", "bar", 0, 0);
+ }
+
+ @Test
+ public void setListDataProvider_notWrapped() {
+ ListDataProvider<Person> provider = new ListDataProvider<>(
+ Collections.emptyList());
+
+ comboBox.setDataProvider(provider);
+
+ Assert.assertSame(provider, comboBox.getDataProvider());
+ }
+
+ @Test
+ public void setItems_hasListDataProvider() {
+ comboBox.setItems();
+
+ Assert.assertEquals(ListDataProvider.class,
+ comboBox.getDataProvider().getClass());
+ }
+
private void checkFiltering(String filterText, String nonMatchingFilterText,
int totalMatches, int matchingResults) {
- DataProvider<Person, String> dataProvider = comboBox.getDataProvider();
-
Assert.assertEquals(
"ComboBox filtered out results with no filter applied",
- totalMatches, dataProvider.size(new Query<>()));
+ totalMatches, comboBoxSizeWithFilter(null));
Assert.assertEquals(
"ComboBox filtered out results with empty filter string",
- totalMatches, dataProvider.size(new Query<>("")));
+ totalMatches, comboBoxSizeWithFilter(""));
Assert.assertEquals("ComboBox filtered out wrong number of results",
- matchingResults, dataProvider.size(new Query<>(filterText)));
+ matchingResults, comboBoxSizeWithFilter(filterText));
Assert.assertEquals(
"ComboBox should have no results with a non-matching filter", 0,
- dataProvider.size(new Query<>(nonMatchingFilterText)));
+ comboBoxSizeWithFilter(nonMatchingFilterText));
+ }
+
+ private int comboBoxSizeWithFilter(String filter) {
+ DataCommunicator<Person> dataCommunicator = comboBox
+ .getDataCommunicator();
+
+ // Discard any currently pending RPC calls
+ dataCommunicator.retrievePendingRpcCalls();
+
+ ComponentTest.getRpcProxy(comboBox, ComboBoxServerRpc.class)
+ .setFilter(filter);
+ dataCommunicator.beforeClientResponse(true);
+
+ ClientMethodInvocation resetInvocation = dataCommunicator
+ .retrievePendingRpcCalls().get(0);
+ assert resetInvocation.getMethodName().equals("reset");
+
+ return ((Integer) resetInvocation.getParameters()[0]).intValue();
}
private List<Person> getPersonCollection() {