]> source.dussan.org Git - vaadin-framework.git/commitdiff
Refactor how DataCommunicator passes filters to its DataProvider (#8309)
authorLeif Åstrand <legioth@gmail.com>
Wed, 25 Jan 2017 07:44:16 +0000 (09:44 +0200)
committerGitHub <noreply@github.com>
Wed, 25 Jan 2017 07:44:16 +0000 (09:44 +0200)
The immediate benefit of this change is that ComboBox doesn't have to do
any wrapping when a ListDataProvider is set. A secondary benefit is that
a bunch of redundant type parameters and unsafe casts can be removed.

This is one of many steps towards #8245

server/src/main/java/com/vaadin/data/HasFilterableDataProvider.java
server/src/main/java/com/vaadin/data/provider/DataCommunicator.java
server/src/main/java/com/vaadin/ui/AbstractListing.java
server/src/main/java/com/vaadin/ui/AbstractSingleSelect.java
server/src/main/java/com/vaadin/ui/ComboBox.java
server/src/main/java/com/vaadin/ui/components/grid/MultiSelectionModelImpl.java
server/src/test/java/com/vaadin/data/provider/DataCommunicatorTest.java
server/src/test/java/com/vaadin/data/provider/ReplaceDataProviderTest.java
server/src/test/java/com/vaadin/tests/server/component/combobox/ComboBoxFilteringTest.java

index 03e2e3037973b7b3f7867f5951a5c56a4f028670..4d2964b2e519da8710985ef78c2bc2c0fcc7bb2b 100644 (file)
@@ -16,6 +16,7 @@
 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
@@ -39,16 +40,32 @@ public interface HasFilterableDataProvider<T, F> extends HasItems<T> {
     /**
      * 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);
 }
index 7b909b831d9e9331f9ac0a42d07bb79a85cfa305..021623555e623603b5d27f47e677bd44e68f7987 100644 (file)
@@ -29,6 +29,7 @@ import java.util.stream.Stream;
 
 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;
@@ -48,12 +49,10 @@ import elemental.json.JsonObject;
  *
  * @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;
 
@@ -190,7 +189,7 @@ public class DataCommunicator<T, F> extends AbstractExtension {
     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;
 
@@ -199,7 +198,7 @@ public class DataCommunicator<T, F> extends AbstractExtension {
     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;
@@ -236,7 +235,8 @@ public class DataCommunicator<T, F> extends AbstractExtension {
         }
 
         if (initial || reset) {
-            int dataProviderSize = getDataProvider().size(new Query<>(filter));
+            @SuppressWarnings({ "rawtypes", "unchecked" })
+            int dataProviderSize = getDataProvider().size(new Query(filter));
             rpc.reset(dataProviderSize);
         }
 
@@ -244,7 +244,8 @@ public class DataCommunicator<T, F> extends AbstractExtension {
             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);
@@ -398,17 +399,6 @@ public class DataCommunicator<T, F> extends AbstractExtension {
         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.
      *
@@ -459,19 +449,31 @@ public class DataCommunicator<T, F> extends AbstractExtension {
      *
      * @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();
         /*
@@ -491,6 +493,18 @@ public class DataCommunicator<T, F> extends AbstractExtension {
             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();
+            }
+        };
     }
 
     /**
index c3328683a15d6c54a87c50527c523ac4c4c2b5a5..dfb8ac103e26cc8316f5b0c885b788fbedf32035 100644 (file)
@@ -29,6 +29,7 @@ import com.vaadin.data.provider.DataProvider;
 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;
@@ -132,7 +133,7 @@ public abstract class AbstractListing<T> extends AbstractComponent
         }
     }
 
-    private final DataCommunicator<T, ?> dataCommunicator;
+    private final DataCommunicator<T> dataCommunicator;
 
     /**
      * Creates a new {@code AbstractListing} with a default data communicator.
@@ -154,7 +155,7 @@ public abstract class AbstractListing<T> extends AbstractComponent
      * @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");
 
@@ -162,10 +163,14 @@ public abstract class AbstractListing<T> extends AbstractComponent
         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() {
@@ -257,7 +262,7 @@ public abstract class AbstractListing<T> extends AbstractComponent
      *
      * @return the data communicator, not null
      */
-    public DataCommunicator<T, ?> getDataCommunicator() {
+    public DataCommunicator<T> getDataCommunicator() {
         return dataCommunicator;
     }
 
index 224d6823e70fea8de53bb21e528fdeb494ec8c90..2b0e6b7d017595aa847e6994c629f569ef297ae3 100644 (file)
@@ -74,7 +74,7 @@ public abstract class AbstractSingleSelect<T> extends AbstractListing<T>
      * @param dataCommunicator
      *            the data communicator to use, not null
      */
-    protected AbstractSingleSelect(DataCommunicator<T, ?> dataCommunicator) {
+    protected AbstractSingleSelect(DataCommunicator<T> dataCommunicator) {
         super(dataCommunicator);
         init();
     }
@@ -152,9 +152,9 @@ public abstract class AbstractSingleSelect<T> extends AbstractListing<T>
     @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
index d08f269fa3518c7fa922f4a97cf2f7fb14b7f7b5..515c581510e1d7394e5e046ed8393c2a253d7f47 100644 (file)
@@ -43,6 +43,7 @@ import com.vaadin.server.Resource;
 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;
@@ -125,7 +126,8 @@ public class ComboBox<T> extends AbstractSingleSelect<T>
 
         @Override
         public void setFilter(String filterText) {
-            getDataCommunicator().setFilter(filterText);
+            currentFilterText = filterText;
+            filterSlot.accept(filterText);
         }
     };
 
@@ -136,13 +138,19 @@ public class ComboBox<T> extends AbstractSingleSelect<T>
 
     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>() {
@@ -294,11 +302,6 @@ public class ComboBox<T> extends AbstractSingleSelect<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
@@ -313,9 +316,8 @@ public class ComboBox<T> extends AbstractSingleSelect<T>
 
         // 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));
     }
 
     /**
@@ -702,22 +704,30 @@ public class ComboBox<T> extends AbstractSingleSelect<T>
     }
 
     @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
index 89e7374bc6c1c457f5f506f59aa522339f77d594..551918ef85a03fbc5324c20da9a7756372477350 100644 (file)
@@ -436,7 +436,7 @@ public class MultiSelectionModelImpl<T> extends AbstractSelectionModel<T>
             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);
index f49dbf1f88a6cc0079c038189372b7b3ea568caf..50a3ebc3372557cdd854777272e4ad2a857339b8 100644 (file)
@@ -22,7 +22,6 @@ import org.junit.Test;
 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;
@@ -81,8 +80,7 @@ public class DataCommunicatorTest {
 
     }
 
-    private static class TestDataCommunicator
-            extends DataCommunicator<Object, SerializablePredicate<Object>> {
+    private static class TestDataCommunicator extends DataCommunicator<Object> {
         protected void extend(UI ui) {
             super.extend(ui);
         }
@@ -100,7 +98,7 @@ public class DataCommunicatorTest {
         TestDataCommunicator communicator = new TestDataCommunicator();
 
         TestDataProvider dataProvider = new TestDataProvider();
-        communicator.setDataProvider(dataProvider);
+        communicator.setDataProvider(dataProvider, null);
 
         Assert.assertFalse(dataProvider.isListenerAdded());
 
@@ -118,7 +116,7 @@ public class DataCommunicatorTest {
         TestDataCommunicator communicator = new TestDataCommunicator();
 
         TestDataProvider dataProvider = new TestDataProvider();
-        communicator.setDataProvider(dataProvider);
+        communicator.setDataProvider(dataProvider, null);
 
         communicator.extend(ui);
 
index 0510022e5e9c4f8bba21b624c72a7b80d3095917..f64d6b27c5a28a294baa64234c1a61584ba2f7cc 100644 (file)
@@ -11,8 +11,6 @@ import java.util.stream.IntStream;
 
 import org.junit.Test;
 
-import com.vaadin.server.SerializablePredicate;
-
 public class ReplaceDataProviderTest {
 
     public static class BeanWithEquals extends Bean {
@@ -73,14 +71,14 @@ public class ReplaceDataProviderTest {
 
     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);
@@ -91,7 +89,7 @@ public class ReplaceDataProviderTest {
         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);
index e8c8d3dd6e872168bfb6d5968ceebb4b06d19f41..159af4ad0b3dd865cae1400b1e8c0e2c14345041 100644 (file)
@@ -15,6 +15,7 @@
  */
 package com.vaadin.tests.server.component.combobox;
 
+import java.util.Collections;
 import java.util.List;
 import java.util.Locale;
 import java.util.stream.Collectors;
@@ -24,13 +25,16 @@ import org.junit.Assert;
 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.
@@ -178,21 +182,62 @@ public class ComboBoxFilteringTest {
         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() {