aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorOlli Tietäväinen <ollit@vaadin.com>2018-12-28 09:25:11 +0200
committerSun Zhe <31067185+ZheSun88@users.noreply.github.com>2018-12-28 09:25:11 +0200
commit613f36ff23fe06c676f9a71e92c04ccabcd19c63 (patch)
tree8a7ad51680bd9889bdd7897e69a41a00f724d962
parentd9de8a9470d0e89590e8fd77e1471c5154e110cd (diff)
downloadvaadin-framework-613f36ff23fe06c676f9a71e92c04ccabcd19c63.tar.gz
vaadin-framework-613f36ff23fe06c676f9a71e92c04ccabcd19c63.zip
Grid column npe behavior (#11390)
* add possibility to configure nested null behavior to Grid.Column * added addColumn method that allows configuring the nested null handling behavior of the column * added uitest for nested null value handling, implements #11137
-rw-r--r--server/src/main/java/com/vaadin/ui/Grid.java145
-rw-r--r--uitest/src/main/java/com/vaadin/tests/components/grid/GridNullSafeNestedPropertyColumn.java65
-rw-r--r--uitest/src/test/java/com/vaadin/tests/components/grid/GridNullSafeNestedPropertyColumnTest.java46
3 files changed, 253 insertions, 3 deletions
diff --git a/server/src/main/java/com/vaadin/ui/Grid.java b/server/src/main/java/com/vaadin/ui/Grid.java
index 7c3467eaa3..81f46f8d59 100644
--- a/server/src/main/java/com/vaadin/ui/Grid.java
+++ b/server/src/main/java/com/vaadin/ui/Grid.java
@@ -845,6 +845,23 @@ public class Grid<T> extends AbstractListing<T> implements HasComponents,
*/
public static class Column<T, V> extends AbstractExtension {
+ /**
+ * behavior when parsing nested properties which may contain
+ * <code>null</code> values in the property chain
+ */
+ public enum NestedNullBehavior {
+ /**
+ * throw a NullPointerException if there is a nested
+ * <code>null</code> value
+ */
+ THROW,
+ /**
+ * silently ignore any exceptions caused by nested <code>null</code>
+ * values
+ */
+ ALLOW_NULLS
+ }
+
private final ValueProvider<T, V> valueProvider;
private ValueProvider<V, ?> presentationProvider;
@@ -856,6 +873,7 @@ public class Grid<T> extends AbstractListing<T> implements HasComponents,
return Stream.of(new QuerySortOrder(id, direction));
};
+ private NestedNullBehavior nestedNullBehavior = NestedNullBehavior.THROW;
private boolean sortable = true;
private SerializableComparator<T> comparator;
private StyleGenerator<T> styleGenerator = item -> null;
@@ -990,6 +1008,38 @@ public class Grid<T> extends AbstractListing<T> implements HasComponents,
}
}
+ /**
+ * Constructs a new Column configuration with given renderer and value
+ * provider.
+ * <p>
+ * For a more complete explanation on presentation provider, see
+ * {@link #setRenderer(ValueProvider, Renderer)}.
+ *
+ * @param valueProvider
+ * the function to get values from items, not
+ * <code>null</code>
+ * @param presentationProvider
+ * the function to get presentations from the value of this
+ * column, not <code>null</code>. For more details, see
+ * {@link #setRenderer(ValueProvider, Renderer)}
+ * @param nestedNullBehavior
+ * behavior on encountering nested <code>null</code> values
+ * when reading the value from the bean
+ * @param renderer
+ * the presentation renderer, not <code>null</code>
+ * @param <P>
+ * the presentation type
+ *
+ * @since
+ */
+ protected <P> Column(ValueProvider<T, V> valueProvider,
+ ValueProvider<V, P> presentationProvider,
+ Renderer<? super P> renderer,
+ NestedNullBehavior nestedNullBehavior) {
+ this(valueProvider, presentationProvider, renderer);
+ this.nestedNullBehavior = nestedNullBehavior;
+ }
+
private static int compareMaybeComparables(Object a, Object b) {
if (hasCommonComparableBaseType(a, b)) {
return compareComparables(a, b);
@@ -1053,8 +1103,16 @@ public class Grid<T> extends AbstractListing<T> implements HasComponents,
@SuppressWarnings("unchecked")
private <P> JsonValue generateRendererValue(T item,
ValueProvider<V, P> presentationProvider, Connector renderer) {
- P presentationValue = presentationProvider
- .apply(valueProvider.apply(item));
+ V value;
+ try {
+ value = valueProvider.apply(item);
+ } catch (NullPointerException npe) {
+ value = null;
+ if (NestedNullBehavior.THROW == nestedNullBehavior) {
+ throw npe;
+ }
+ }
+ P presentationValue = presentationProvider.apply(value);
// Make Grid track components.
if (renderer instanceof ComponentRenderer
@@ -2723,6 +2781,59 @@ public class Grid<T> extends AbstractListing<T> implements HasComponents,
}
/**
+ * Adds a new column with the given property name and renderer. The property
+ * name will be used as the {@link Column#getId() column id} and the
+ * {@link Column#getCaption() column caption} will be set based on the
+ * property definition.
+ * <p>
+ * This method can only be used for a <code>Grid</code> created using
+ * {@link Grid#Grid(Class)} or {@link #withPropertySet(PropertySet)}.
+ * <p>
+ * You can add columns for nested properties with dot notation, eg.
+ * <code>"property.nestedProperty"</code>
+ *
+ * @param propertyName
+ * the property name of the new column, not <code>null</code>
+ * @param renderer
+ * the renderer to use, not <code>null</code>
+ * @param nestedNullBehavior
+ * the behavior when
+ * @return the newly added column, not <code>null</code>
+ */
+ public Column<T, ?> addColumn(String propertyName,
+ AbstractRenderer<? super T, ?> renderer,
+ Column.NestedNullBehavior nestedNullBehavior) {
+ Objects.requireNonNull(propertyName, "Property name cannot be null");
+ Objects.requireNonNull(renderer, "Renderer cannot be null");
+
+ if (getColumn(propertyName) != null) {
+ throw new IllegalStateException(
+ "There is already a column for " + propertyName);
+ }
+
+ PropertyDefinition<T, ?> definition = propertySet
+ .getProperty(propertyName)
+ .orElseThrow(() -> new IllegalArgumentException(
+ "Could not resolve property name " + propertyName
+ + " from " + propertySet));
+
+ if (!renderer.getPresentationType()
+ .isAssignableFrom(definition.getType())) {
+ throw new IllegalArgumentException(
+ renderer + " cannot be used with a property of type "
+ + definition.getType().getName());
+ }
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ Column<T, ?> column = createColumn(definition.getGetter(),
+ ValueProvider.identity(), (AbstractRenderer) renderer,
+ nestedNullBehavior);
+ String generatedIdentifier = getGeneratedIdentifier();
+ addColumn(generatedIdentifier, column);
+ column.setId(definition.getName()).setCaption(definition.getCaption());
+ return column;
+ }
+
+ /**
* Adds a new text column to this {@link Grid} with a value provider. The
* column will use a {@link TextRenderer}. The value is converted to a
* String using {@link Object#toString()}. In-memory sorting will use the
@@ -2824,7 +2935,7 @@ public class Grid<T> extends AbstractListing<T> implements HasComponents,
/**
* Adds a column that shows components.
* <p>
- * This is a shorthand for {@link #addColum()} with a
+ * This is a shorthand for {@link #addColumn()} with a
* {@link ComponentRenderer}.
*
* @param componentProvider
@@ -2865,6 +2976,34 @@ public class Grid<T> extends AbstractListing<T> implements HasComponents,
return new Column<>(valueProvider, presentationProvider, renderer);
}
+ /**
+ * Creates a column instance from a value provider, presentation provider
+ * and a renderer.
+ *
+ * @param valueProvider
+ * the value provider
+ * @param presentationProvider
+ * the presentation provider
+ * @param renderer
+ * the renderer
+ * @param nestedNullBehavior
+ * the behavior when facing nested <code>null</code> values
+ * @return a new column instance
+ * @param <V>
+ * the column value type
+ * @param <P>
+ * the column presentation type
+ *
+ * @since
+ */
+ private <V, P> Column<T, V> createColumn(ValueProvider<T, V> valueProvider,
+ ValueProvider<V, P> presentationProvider,
+ AbstractRenderer<? super T, ? super P> renderer,
+ Column.NestedNullBehavior nestedNullBehavior) {
+ return new Column<>(valueProvider, presentationProvider, renderer,
+ nestedNullBehavior);
+ }
+
private void addColumn(String identifier, Column<T, ?> column) {
if (getColumns().contains(column)) {
return;
diff --git a/uitest/src/main/java/com/vaadin/tests/components/grid/GridNullSafeNestedPropertyColumn.java b/uitest/src/main/java/com/vaadin/tests/components/grid/GridNullSafeNestedPropertyColumn.java
new file mode 100644
index 0000000000..888296d5f8
--- /dev/null
+++ b/uitest/src/main/java/com/vaadin/tests/components/grid/GridNullSafeNestedPropertyColumn.java
@@ -0,0 +1,65 @@
+package com.vaadin.tests.components.grid;
+
+import com.vaadin.annotations.Widgetset;
+import com.vaadin.data.provider.ListDataProvider;
+import com.vaadin.server.VaadinRequest;
+import com.vaadin.tests.components.AbstractTestUI;
+import com.vaadin.tests.data.bean.Address;
+import com.vaadin.tests.data.bean.Country;
+import com.vaadin.tests.data.bean.Person;
+import com.vaadin.tests.data.bean.Sex;
+import com.vaadin.ui.Button;
+import com.vaadin.ui.Grid;
+import com.vaadin.ui.TextField;
+import com.vaadin.ui.renderers.TextRenderer;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Widgetset("com.vaadin.DefaultWidgetSet")
+public class GridNullSafeNestedPropertyColumn extends AbstractTestUI {
+
+ private List<Person> personList = new ArrayList<>();
+ private ListDataProvider<Person> listDataProvider;
+ private Grid.Column nullSafeColumn = null;
+ private Grid.Column regularColumn = null;
+
+ @Override
+ protected void setup(VaadinRequest request) {
+ Grid<Person> grid = new Grid<>(Person.class);
+ grid.setSizeFull();
+
+ personList.add(new Person("person", "with", "an address", 0,
+ Sex.UNKNOWN, new Address("street", 0, "", Country.FINLAND)));
+ listDataProvider = new ListDataProvider<>(personList);
+ grid.setDataProvider(listDataProvider);
+
+ Button addPersonButton = new Button("add person with a null address",
+ event -> {
+ Address address = null;
+ Person person = new Person("person", "without", "address",
+ 42, Sex.UNKNOWN, address);
+ personList.add(person);
+ listDataProvider.refreshAll();
+ });
+ addPersonButton.setId("add");
+
+ Button addSafeColumnButton = new Button(
+ "add 'address.streetAddress' as a null-safe column", event -> {
+ nullSafeColumn = grid.addColumn("address.streetAddress",
+ new TextRenderer(),
+ Grid.Column.NestedNullBehavior.ALLOW_NULLS);
+ });
+ addSafeColumnButton.setId("safe");
+
+ Button addUnsafeColumnButton = new Button(
+ "add 'address.streetAddress' column without nested null safety",
+ event -> {
+ regularColumn = grid.addColumn("address.streetAddress");
+ });
+ addUnsafeColumnButton.setId("unsafe");
+
+ addComponents(grid, addPersonButton, addSafeColumnButton,
+ addUnsafeColumnButton);
+ }
+}
diff --git a/uitest/src/test/java/com/vaadin/tests/components/grid/GridNullSafeNestedPropertyColumnTest.java b/uitest/src/test/java/com/vaadin/tests/components/grid/GridNullSafeNestedPropertyColumnTest.java
new file mode 100644
index 0000000000..2ac67b7fc2
--- /dev/null
+++ b/uitest/src/test/java/com/vaadin/tests/components/grid/GridNullSafeNestedPropertyColumnTest.java
@@ -0,0 +1,46 @@
+package com.vaadin.tests.components.grid;
+
+import com.vaadin.testbench.By;
+import com.vaadin.testbench.elements.ButtonElement;
+import com.vaadin.testbench.parallel.TestCategory;
+import com.vaadin.tests.tb3.MultiBrowserTest;
+import org.junit.Test;
+import org.openqa.selenium.WebElement;
+
+import java.util.List;
+
+import static junit.framework.TestCase.assertTrue;
+import static org.junit.Assert.assertFalse;
+
+/**
+ * Tests that using a nested property name with a null bean child property won't
+ * cause an exception.
+ */
+@TestCategory("grid")
+public class GridNullSafeNestedPropertyColumnTest extends MultiBrowserTest {
+
+ @Test
+ public void testNullNestedPropertyInSafeGridColumn() {
+ openTestURL();
+ waitForElementPresent(org.openqa.selenium.By.className("v-grid"));
+ $(ButtonElement.class).id("safe").click();
+ $(ButtonElement.class).id("add").click();
+ List<WebElement> errorIndicator = findElements(
+ By.className("v-errorindicator"));
+ assertTrue(errorIndicator.isEmpty());
+ }
+
+ @Test
+ public void testNullNestedPropertyInUnsafeGridColumn() {
+ openTestURL();
+ waitForElementPresent(org.openqa.selenium.By.className("v-grid"));
+ $(ButtonElement.class).id("unsafe").click();
+ $(ButtonElement.class).id("add").click();
+ List<WebElement> errorIndicator = findElements(
+ By.className("v-errorindicator"));
+ assertFalse(
+ "There should be an error indicator when adding nested null values to Grid",
+ errorIndicator.isEmpty());
+ }
+
+}