com.vaadin.ui.Grid.java Source code

Java tutorial

Introduction

Here is the source code for com.vaadin.ui.Grid.java

Source

/*
 * Copyright 2000-2018 Vaadin Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.vaadin.ui;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Attributes;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import com.vaadin.data.BeanPropertySet;
import com.vaadin.data.Binder;
import com.vaadin.data.Binder.Binding;
import com.vaadin.data.HasDataProvider;
import com.vaadin.data.HasValue;
import com.vaadin.data.PropertyDefinition;
import com.vaadin.data.PropertySet;
import com.vaadin.data.ValueProvider;
import com.vaadin.data.provider.CallbackDataProvider;
import com.vaadin.data.provider.DataCommunicator;
import com.vaadin.data.provider.DataGenerator;
import com.vaadin.data.provider.DataProvider;
import com.vaadin.data.provider.GridSortOrder;
import com.vaadin.data.provider.GridSortOrderBuilder;
import com.vaadin.data.provider.InMemoryDataProvider;
import com.vaadin.data.provider.Query;
import com.vaadin.data.provider.QuerySortOrder;
import com.vaadin.event.ConnectorEvent;
import com.vaadin.event.ContextClickEvent;
import com.vaadin.event.HasUserOriginated;
import com.vaadin.event.SortEvent;
import com.vaadin.event.SortEvent.SortListener;
import com.vaadin.event.SortEvent.SortNotifier;
import com.vaadin.event.selection.MultiSelectionListener;
import com.vaadin.event.selection.SelectionListener;
import com.vaadin.event.selection.SingleSelectionListener;
import com.vaadin.server.AbstractExtension;
import com.vaadin.server.EncodeResult;
import com.vaadin.server.Extension;
import com.vaadin.server.JsonCodec;
import com.vaadin.server.SerializableComparator;
import com.vaadin.server.SerializableSupplier;
import com.vaadin.server.Setter;
import com.vaadin.server.VaadinServiceClassLoaderUtil;
import com.vaadin.shared.Connector;
import com.vaadin.shared.MouseEventDetails;
import com.vaadin.shared.Registration;
import com.vaadin.shared.data.DataCommunicatorConstants;
import com.vaadin.shared.data.sort.SortDirection;
import com.vaadin.shared.ui.ContentMode;
import com.vaadin.shared.ui.grid.AbstractGridExtensionState;
import com.vaadin.shared.ui.grid.ColumnResizeMode;
import com.vaadin.shared.ui.grid.ColumnState;
import com.vaadin.shared.ui.grid.DetailsManagerState;
import com.vaadin.shared.ui.grid.GridClientRpc;
import com.vaadin.shared.ui.grid.GridConstants;
import com.vaadin.shared.ui.grid.GridConstants.Section;
import com.vaadin.shared.ui.grid.GridServerRpc;
import com.vaadin.shared.ui.grid.GridState;
import com.vaadin.shared.ui.grid.GridStaticCellType;
import com.vaadin.shared.ui.grid.HeightMode;
import com.vaadin.shared.ui.grid.ScrollDestination;
import com.vaadin.shared.ui.grid.SectionState;
import com.vaadin.ui.components.grid.ColumnReorderListener;
import com.vaadin.ui.components.grid.ColumnResizeListener;
import com.vaadin.ui.components.grid.ColumnVisibilityChangeListener;
import com.vaadin.ui.components.grid.DetailsGenerator;
import com.vaadin.ui.components.grid.Editor;
import com.vaadin.ui.components.grid.EditorImpl;
import com.vaadin.ui.components.grid.Footer;
import com.vaadin.ui.components.grid.FooterRow;
import com.vaadin.ui.components.grid.GridMultiSelect;
import com.vaadin.ui.components.grid.GridSelectionModel;
import com.vaadin.ui.components.grid.GridSingleSelect;
import com.vaadin.ui.components.grid.Header;
import com.vaadin.ui.components.grid.Header.Row;
import com.vaadin.ui.components.grid.HeaderCell;
import com.vaadin.ui.components.grid.HeaderRow;
import com.vaadin.ui.components.grid.ItemClickListener;
import com.vaadin.ui.components.grid.MultiSelectionModel;
import com.vaadin.ui.components.grid.MultiSelectionModelImpl;
import com.vaadin.ui.components.grid.NoSelectionModel;
import com.vaadin.ui.components.grid.SingleSelectionModel;
import com.vaadin.ui.components.grid.SingleSelectionModelImpl;
import com.vaadin.ui.components.grid.SortOrderProvider;
import com.vaadin.ui.declarative.DesignAttributeHandler;
import com.vaadin.ui.declarative.DesignContext;
import com.vaadin.ui.declarative.DesignException;
import com.vaadin.ui.declarative.DesignFormatter;
import com.vaadin.ui.renderers.AbstractRenderer;
import com.vaadin.ui.renderers.ComponentRenderer;
import com.vaadin.ui.renderers.HtmlRenderer;
import com.vaadin.ui.renderers.Renderer;
import com.vaadin.ui.renderers.TextRenderer;
import com.vaadin.util.ReflectTools;

import elemental.json.Json;
import elemental.json.JsonObject;
import elemental.json.JsonValue;

/**
 * A grid component for displaying tabular data.
 *
 * @author Vaadin Ltd
 * @since 8.0
 *
 * @param <T>
 *            the grid bean type
 */
public class Grid<T> extends AbstractListing<T>
        implements HasComponents, HasDataProvider<T>, SortNotifier<GridSortOrder<T>> {

    private static final String DECLARATIVE_DATA_ITEM_TYPE = "data-item-type";

    /**
     * A callback method for fetching items. The callback is provided with a
     * list of sort orders, offset index and limit.
     *
     * @param <T>
     *            the grid bean type
     */
    @FunctionalInterface
    public interface FetchItemsCallback<T> extends Serializable {

        /**
         * Returns a stream of items ordered by given sort orders, limiting the
         * results with given offset and limit.
         * <p>
         * This method is called after the size of the data set is asked from a
         * related size callback. The offset and limit are promised to be within
         * the size of the data set.
         *
         * @param sortOrder
         *            a list of sort orders
         * @param offset
         *            the first index to fetch
         * @param limit
         *            the fetched item count
         * @return stream of items
         */
        public Stream<T> fetchItems(List<QuerySortOrder> sortOrder, int offset, int limit);
    }

    @Deprecated
    private static final Method COLUMN_REORDER_METHOD = ReflectTools.findMethod(ColumnReorderListener.class,
            "columnReorder", ColumnReorderEvent.class);

    private static final Method SORT_ORDER_CHANGE_METHOD = ReflectTools.findMethod(SortListener.class, "sort",
            SortEvent.class);

    @Deprecated
    private static final Method COLUMN_RESIZE_METHOD = ReflectTools.findMethod(ColumnResizeListener.class,
            "columnResize", ColumnResizeEvent.class);

    @Deprecated
    private static final Method ITEM_CLICK_METHOD = ReflectTools.findMethod(ItemClickListener.class, "itemClick",
            ItemClick.class);

    @Deprecated
    private static final Method COLUMN_VISIBILITY_METHOD = ReflectTools.findMethod(
            ColumnVisibilityChangeListener.class, "columnVisibilityChanged", ColumnVisibilityChangeEvent.class);

    /**
     * Selection mode representing the built-in selection models in grid.
     * <p>
     * These enums can be used in {@link Grid#setSelectionMode(SelectionMode)}
     * to easily switch between the build-in selection models.
     *
     * @see Grid#setSelectionMode(SelectionMode)
     * @see Grid#setSelectionModel(GridSelectionModel)
     */
    public enum SelectionMode {

        /**
         * Single selection mode that maps to build-in
         * {@link SingleSelectionModel}.
         *
         * @see SingleSelectionModelImpl
         */
        SINGLE {
            @Override
            protected <T> GridSelectionModel<T> createModel() {
                return new SingleSelectionModelImpl<>();
            }
        },

        /**
         * Multiselection mode that maps to build-in {@link MultiSelectionModel}
         * .
         *
         * @see MultiSelectionModelImpl
         */
        MULTI {
            @Override
            protected <T> GridSelectionModel<T> createModel() {
                return new MultiSelectionModelImpl<>();
            }
        },

        /**
         * Selection model that doesn't allow selection.
         *
         * @see NoSelectionModel
         */
        NONE {
            @Override
            protected <T> GridSelectionModel<T> createModel() {
                return new NoSelectionModel<>();
            }
        };

        /**
         * Creates the selection model to use with this enum.
         *
         * @param <T>
         *            the type of items in the grid
         * @return the selection model
         */
        protected abstract <T> GridSelectionModel<T> createModel();
    }

    /**
     * An event that is fired when the columns are reordered.
     */
    public static class ColumnReorderEvent extends Component.Event implements HasUserOriginated {

        private final boolean userOriginated;

        /**
         *
         * @param source
         *            the grid where the event originated from
         * @param userOriginated
         *            <code>true</code> if event is a result of user
         *            interaction, <code>false</code> if from API call
         */
        public ColumnReorderEvent(Grid source, boolean userOriginated) {
            super(source);
            this.userOriginated = userOriginated;
        }

        /**
         * Returns <code>true</code> if the column reorder was done by the user,
         * <code>false</code> if not and it was triggered by server side code.
         *
         * @return <code>true</code> if event is a result of user interaction
         */
        @Override
        public boolean isUserOriginated() {
            return userOriginated;
        }
    }

    /**
     * An event that is fired when a column is resized, either programmatically
     * or by the user.
     */
    public static class ColumnResizeEvent extends Component.Event implements HasUserOriginated {

        private final Column<?, ?> column;
        private final boolean userOriginated;

        /**
         *
         * @param source
         *            the grid where the event originated from
         * @param userOriginated
         *            <code>true</code> if event is a result of user
         *            interaction, <code>false</code> if from API call
         */
        public ColumnResizeEvent(Grid<?> source, Column<?, ?> column, boolean userOriginated) {
            super(source);
            this.column = column;
            this.userOriginated = userOriginated;
        }

        /**
         * Returns the column that was resized.
         *
         * @return the resized column.
         */
        public Column<?, ?> getColumn() {
            return column;
        }

        /**
         * Returns <code>true</code> if the column resize was done by the user,
         * <code>false</code> if not and it was triggered by server side code.
         *
         * @return <code>true</code> if event is a result of user interaction
         */
        @Override
        public boolean isUserOriginated() {
            return userOriginated;
        }

    }

    /**
     * An event fired when an item in the Grid has been clicked.
     *
     * @param <T>
     *            the grid bean type
     */
    public static class ItemClick<T> extends ConnectorEvent {

        private final T item;
        private final Column<T, ?> column;
        private final MouseEventDetails mouseEventDetails;
        private final int rowIndex;

        /**
         * Creates a new {@code ItemClick} event containing the given item and
         * Column originating from the given Grid.
         *
         */
        public ItemClick(Grid<T> source, Column<T, ?> column, T item, MouseEventDetails mouseEventDetails,
                int rowIndex) {
            super(source);
            this.column = column;
            this.item = item;
            this.mouseEventDetails = mouseEventDetails;
            this.rowIndex = rowIndex;
        }

        /**
         * Returns the clicked item.
         *
         * @return the clicked item
         */
        public T getItem() {
            return item;
        }

        /**
         * Returns the clicked column.
         *
         * @return the clicked column
         */
        public Column<T, ?> getColumn() {
            return column;
        }

        /**
         * Returns the source Grid.
         *
         * @return the grid
         */
        @Override
        public Grid<T> getSource() {
            return (Grid<T>) super.getSource();
        }

        /**
         * Returns the mouse event details.
         *
         * @return the mouse event details
         */
        public MouseEventDetails getMouseEventDetails() {
            return mouseEventDetails;
        }

        /**
         * Returns the clicked rowIndex.
         *
         * @return the clicked rowIndex
         * @since 8.4
         */
        public int getRowIndex() {
            return rowIndex;
        }
    }

    /**
     * ContextClickEvent for the Grid Component.
     *
     * <p>
     * Usage:
     *
     * <pre>
     * grid.addContextClickListener(event -&gt; Notification.show(
     *         ((GridContextClickEvent&lt;Person&gt;) event).getItem() + " Clicked"));
     * </pre>
     *
     * @param <T>
     *            the grid bean type
     */
    public static class GridContextClickEvent<T> extends ContextClickEvent {

        private final T item;
        private final int rowIndex;
        private final Column<T, ?> column;
        private final Section section;

        /**
         * Creates a new context click event.
         *
         * @param source
         *            the grid where the context click occurred
         * @param mouseEventDetails
         *            details about mouse position
         * @param section
         *            the section of the grid which was clicked
         * @param rowIndex
         *            the index of the row which was clicked
         * @param item
         *            the item which was clicked
         * @param column
         *            the column which was clicked
         */
        public GridContextClickEvent(Grid<T> source, MouseEventDetails mouseEventDetails, Section section,
                int rowIndex, T item, Column<T, ?> column) {
            super(source, mouseEventDetails);
            this.item = item;
            this.section = section;
            this.column = column;
            this.rowIndex = rowIndex;
        }

        /**
         * Returns the item of context clicked row.
         *
         * @return item of clicked row; <code>null</code> if header or footer
         */
        public T getItem() {
            return item;
        }

        /**
         * Returns the clicked column.
         *
         * @return the clicked column
         */
        public Column<T, ?> getColumn() {
            return column;
        }

        /**
         * Return the clicked section of Grid.
         *
         * @return section of grid
         */
        public Section getSection() {
            return section;
        }

        /**
         * Returns the clicked row index.
         * <p>
         * Header and Footer rows for index can be fetched with
         * {@link Grid#getHeaderRow(int)} and {@link Grid#getFooterRow(int)}.
         *
         * @return row index in section
         */
        public int getRowIndex() {
            return rowIndex;
        }

        @Override
        public Grid<T> getComponent() {
            return (Grid<T>) super.getComponent();
        }
    }

    /**
     * An event that is fired when a column's visibility changes.
     *
     * @since 7.5.0
     */
    public static class ColumnVisibilityChangeEvent extends Component.Event implements HasUserOriginated {

        private final Column<?, ?> column;
        private final boolean userOriginated;
        private final boolean hidden;

        /**
         * Constructor for a column visibility change event.
         *
         * @param source
         *            the grid from which this event originates
         * @param column
         *            the column that changed its visibility
         * @param hidden
         *            <code>true</code> if the column was hidden,
         *            <code>false</code> if it became visible
         * @param isUserOriginated
         *            <code>true</code> if the event was triggered by an UI
         *            interaction
         */
        public ColumnVisibilityChangeEvent(Grid<?> source, Column<?, ?> column, boolean hidden,
                boolean isUserOriginated) {
            super(source);
            this.column = column;
            this.hidden = hidden;
            userOriginated = isUserOriginated;
        }

        /**
         * Gets the column that became hidden or visible.
         *
         * @return the column that became hidden or visible.
         * @see Column#isHidden()
         */
        public Column<?, ?> getColumn() {
            return column;
        }

        /**
         * Was the column set hidden or visible.
         *
         * @return <code>true</code> if the column was hidden <code>false</code>
         *         if it was set visible
         */
        public boolean isHidden() {
            return hidden;
        }

        @Override
        public boolean isUserOriginated() {
            return userOriginated;
        }
    }

    /**
     * A helper base class for creating extensions for the Grid component.
     *
     * @param <T>
     */
    public abstract static class AbstractGridExtension<T> extends AbstractListingExtension<T> {

        @Override
        public void extend(AbstractListing<T> grid) {
            if (!(grid instanceof Grid)) {
                throw new IllegalArgumentException(getClass().getSimpleName() + " can only extend Grid");
            }
            super.extend(grid);
        }

        /**
         * Adds given component to the connector hierarchy of Grid.
         *
         * @param c
         *            the component to add
         */
        protected void addComponentToGrid(Component c) {
            getParent().addExtensionComponent(c);
        }

        /**
         * Removes given component from the connector hierarchy of Grid.
         *
         * @param c
         *            the component to remove
         */
        protected void removeComponentFromGrid(Component c) {
            getParent().removeExtensionComponent(c);
        }

        @Override
        public Grid<T> getParent() {
            return (Grid<T>) super.getParent();
        }

        @Override
        protected AbstractGridExtensionState getState() {
            return (AbstractGridExtensionState) super.getState();
        }

        @Override
        protected AbstractGridExtensionState getState(boolean markAsDirty) {
            return (AbstractGridExtensionState) super.getState(markAsDirty);
        }

        protected String getInternalIdForColumn(Column<T, ?> column) {
            return getParent().getInternalIdForColumn(column);
        }
    }

    private final class GridServerRpcImpl implements GridServerRpc {
        @Override
        public void sort(String[] columnInternalIds, SortDirection[] directions, boolean isUserOriginated) {

            assert columnInternalIds.length == directions.length : "Column and sort direction counts don't match.";

            List<GridSortOrder<T>> list = new ArrayList<>(directions.length);
            for (int i = 0; i < columnInternalIds.length; ++i) {
                Column<T, ?> column = columnKeys.get(columnInternalIds[i]);
                list.add(new GridSortOrder<>(column, directions[i]));
            }
            setSortOrder(list, isUserOriginated);
        }

        @Override
        public void itemClick(String rowKey, String columnInternalId, MouseEventDetails details, int rowIndex) {
            Column<T, ?> column = getColumnByInternalId(columnInternalId);
            T item = getDataCommunicator().getKeyMapper().get(rowKey);
            fireEvent(new ItemClick<>(Grid.this, column, item, details, rowIndex));
        }

        @Override
        public void contextClick(int rowIndex, String rowKey, String columnInternalId, Section section,
                MouseEventDetails details) {
            T item = null;
            if (rowKey != null) {
                item = getDataCommunicator().getKeyMapper().get(rowKey);
            }
            fireEvent(new GridContextClickEvent<>(Grid.this, details, section, rowIndex, item,
                    getColumnByInternalId(columnInternalId)));
        }

        @Override
        public void columnsReordered(List<String> newColumnOrder, List<String> oldColumnOrder) {
            final String diffStateKey = "columnOrder";
            ConnectorTracker connectorTracker = getUI().getConnectorTracker();
            JsonObject diffState = connectorTracker.getDiffState(Grid.this);
            // discard the change if the columns have been reordered from
            // the server side, as the server side is always right
            if (getState(false).columnOrder.equals(oldColumnOrder)) {
                // Don't mark as dirty since client has the state already
                getState(false).columnOrder = newColumnOrder;
                // write changes to diffState so that possible reverting the
                // column order is sent to client
                assert diffState.hasKey(diffStateKey) : "Field name has changed";
                Type type = null;
                try {
                    type = getState(false).getClass().getField(diffStateKey).getGenericType();
                } catch (NoSuchFieldException | SecurityException e) {
                    e.printStackTrace();
                }
                EncodeResult encodeResult = JsonCodec.encode(getState(false).columnOrder, diffState, type,
                        connectorTracker);

                diffState.put(diffStateKey, encodeResult.getEncodedValue());
                fireColumnReorderEvent(true);
            } else {
                // make sure the client is reverted to the order that the
                // server thinks it is
                diffState.remove(diffStateKey);
                markAsDirty();
            }
        }

        @Override
        public void columnVisibilityChanged(String internalId, boolean hidden) {
            Column<T, ?> column = getColumnByInternalId(internalId);
            column.checkColumnIsAttached();
            if (column.isHidden() != hidden) {
                column.getState().hidden = hidden;
                fireColumnVisibilityChangeEvent(column, hidden, true);
            }
        }

        @Override
        public void columnResized(String internalId, double pixels) {
            final Column<T, ?> column = getColumnByInternalId(internalId);
            if (column != null && column.isResizable()) {
                column.getState().width = pixels;
                fireColumnResizeEvent(column, true);
            }
        }
    }

    /**
     * Class for managing visible details rows.
     *
     * @param <T>
     *            the grid bean type
     */
    public static class DetailsManager<T> extends AbstractGridExtension<T> {

        private final Set<T> visibleDetails = new HashSet<>();
        private final Map<T, Component> components = new HashMap<>();
        private DetailsGenerator<T> generator;

        /**
         * Sets the details component generator.
         *
         * @param generator
         *            the generator for details components
         */
        public void setDetailsGenerator(DetailsGenerator<T> generator) {
            if (this.generator != generator) {
                removeAllComponents();
            }
            getState().hasDetailsGenerator = generator != null;
            this.generator = generator;
            visibleDetails.forEach(this::refresh);
        }

        @Override
        public void remove() {
            removeAllComponents();

            super.remove();
        }

        private void removeAllComponents() {
            // Clean up old components
            components.values().forEach(this::removeComponentFromGrid);
            components.clear();
        }

        @Override
        public void generateData(T item, JsonObject jsonObject) {
            if (generator == null || !visibleDetails.contains(item)) {
                return;
            }

            if (!components.containsKey(item)) {
                Component detailsComponent = generator.apply(item);
                Objects.requireNonNull(detailsComponent, "Details generator can't create null components");
                if (detailsComponent.getParent() != null) {
                    throw new IllegalStateException("Details component was already attached");
                }
                addComponentToGrid(detailsComponent);
                components.put(item, detailsComponent);
            }

            jsonObject.put(GridState.JSONKEY_DETAILS_VISIBLE, components.get(item).getConnectorId());
        }

        @Override
        public void destroyData(T item) {
            // No clean up needed. Components are removed when hiding details
            // and/or changing details generator
        }

        /**
         * Sets the visibility of details component for given item.
         *
         * @param item
         *            the item to show details for
         * @param visible
         *            {@code true} if details component should be visible;
         *            {@code false} if it should be hidden
         */
        public void setDetailsVisible(T item, boolean visible) {
            boolean refresh = false;
            if (!visible) {
                refresh = visibleDetails.remove(item);
                if (components.containsKey(item)) {
                    removeComponentFromGrid(components.remove(item));
                }
            } else {
                refresh = visibleDetails.add(item);
            }

            if (refresh) {
                refresh(item);
            }
        }

        /**
         * Returns the visibility of details component for given item.
         *
         * @param item
         *            the item to show details for
         *
         * @return {@code true} if details component should be visible;
         *         {@code false} if it should be hidden
         */
        public boolean isDetailsVisible(T item) {
            return visibleDetails.contains(item);
        }

        @Override
        public Grid<T> getParent() {
            return super.getParent();
        }

        @Override
        protected DetailsManagerState getState() {
            return (DetailsManagerState) super.getState();
        }

        @Override
        protected DetailsManagerState getState(boolean markAsDirty) {
            return (DetailsManagerState) super.getState(markAsDirty);
        }
    }

    /**
     * This extension manages the configuration and data communication for a
     * Column inside of a Grid component.
     *
     * @param <T>
     *            the grid bean type
     * @param <V>
     *            the column value type
     */
    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;

        private SortOrderProvider sortOrderProvider = direction -> {
            String id = getId();
            if (id == null) {
                return Stream.empty();
            }
            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;
        private DescriptionGenerator<T> descriptionGenerator;
        private DataGenerator<T> dataGenerator = new DataGenerator<T>() {

            @Override
            public void generateData(T item, JsonObject jsonObject) {
                ColumnState state = getState(false);

                String communicationId = getConnectorId();

                assert communicationId != null : "No communication ID set for column " + state.caption;

                JsonObject obj = getDataObject(jsonObject, DataCommunicatorConstants.DATA);

                obj.put(communicationId, generateRendererValue(item, presentationProvider, state.renderer));

                String style = styleGenerator.apply(item);
                if (style != null && !style.isEmpty()) {
                    JsonObject styleObj = getDataObject(jsonObject, GridState.JSONKEY_CELLSTYLES);
                    styleObj.put(communicationId, style);
                }
                if (descriptionGenerator != null) {
                    String description = descriptionGenerator.apply(item);
                    if (description != null && !description.isEmpty()) {
                        JsonObject descriptionObj = getDataObject(jsonObject, GridState.JSONKEY_CELLDESCRIPTION);
                        descriptionObj.put(communicationId, description);
                    }
                }
            }

            @Override
            public void destroyData(T item) {
                removeComponent(getGrid().getDataProvider().getId(item));
            }

            @Override
            public void destroyAllData() {
                // Make a defensive copy of keys, as the map gets cleared when
                // removing components.
                new HashSet<>(activeComponents.keySet()).forEach(component -> removeComponent(component));
            }
        };

        private Binding<T, ?> editorBinding;
        private Map<Object, Component> activeComponents = new HashMap<>();

        private String userId;

        /**
         * Constructs a new Column configuration with given renderer and value
         * provider.
         *
         * @param valueProvider
         *            the function to get values from items, not
         *            <code>null</code>
         * @param renderer
         *            the value renderer, not <code>null</code>
         */
        protected Column(ValueProvider<T, V> valueProvider, Renderer<? super V> renderer) {
            this(valueProvider, ValueProvider.identity(), renderer);
        }

        /**
         * 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 renderer
         *            the presentation renderer, not <code>null</code>
         * @param <P>
         *            the presentation type
         *
         * @since 8.1
         */
        protected <P> Column(ValueProvider<T, V> valueProvider, ValueProvider<V, P> presentationProvider,
                Renderer<? super P> renderer) {
            Objects.requireNonNull(valueProvider, "Value provider can't be null");
            Objects.requireNonNull(presentationProvider, "Presentation provider can't be null");
            Objects.requireNonNull(renderer, "Renderer can't be null");

            ColumnState state = getState();

            this.valueProvider = valueProvider;
            this.presentationProvider = presentationProvider;

            state.renderer = renderer;
            state.caption = "";

            // Add the renderer as a child extension of this extension, thus
            // ensuring the renderer will be unregistered when this column is
            // removed
            addExtension(renderer);

            Class<? super P> valueType = renderer.getPresentationType();

            if (Comparable.class.isAssignableFrom(valueType)) {
                comparator = (a, b) -> compareComparables(valueProvider.apply(a), valueProvider.apply(b));
            } else if (Number.class.isAssignableFrom(valueType)) {
                /*
                 * Value type will be Number whenever using NumberRenderer.
                 * Provide explicit comparison support in this case even though
                 * Number itself isn't Comparable.
                 */
                comparator = (a, b) -> compareNumbers((Number) valueProvider.apply(a),
                        (Number) valueProvider.apply(b));
            } else {
                comparator = (a, b) -> compareMaybeComparables(valueProvider.apply(a), valueProvider.apply(b));
            }
        }

        /**
         * 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 8.8
         */
        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);
            }
            return compareComparables(Objects.toString(a, ""), Objects.toString(b, ""));
        }

        private static boolean hasCommonComparableBaseType(Object a, Object b) {
            if (a instanceof Comparable<?> && b instanceof Comparable<?>) {
                Class<?> aClass = a.getClass();
                Class<?> bClass = b.getClass();

                if (aClass == bClass) {
                    return true;
                }

                Class<?> baseType = ReflectTools.findCommonBaseType(aClass, bClass);
                if (Comparable.class.isAssignableFrom(baseType)) {
                    return true;
                }
            }
            if ((a == null && b instanceof Comparable<?>) || (b == null && a instanceof Comparable<?>)) {
                return true;
            }

            return false;
        }

        @SuppressWarnings({ "unchecked", "rawtypes" })
        private static int compareComparables(Object a, Object b) {
            return ((Comparator) Comparator.nullsLast(Comparator.naturalOrder())).compare(a, b);
        }

        @SuppressWarnings("unchecked")
        private static int compareNumbers(Number a, Number b) {
            Number valueA = a != null ? a : Double.POSITIVE_INFINITY;
            Number valueB = b != null ? b : Double.POSITIVE_INFINITY;
            // Most Number implementations are Comparable
            if (valueA instanceof Comparable && valueA.getClass().isInstance(valueB)) {
                return ((Comparable<Number>) valueA).compareTo(valueB);
            }
            if (valueA.equals(valueB)) {
                return 0;
            }
            // Fall back to comparing based on potentially truncated values
            int compare = Long.compare(valueA.longValue(), valueB.longValue());
            if (compare == 0) {
                // This might still produce 0 even though the values are not
                // equals, but there's nothing more we can do about that
                compare = Double.compare(valueA.doubleValue(), valueB.doubleValue());
            }
            return compare;
        }

        @SuppressWarnings("unchecked")
        private <P> JsonValue generateRendererValue(T item, ValueProvider<V, P> presentationProvider,
                Connector renderer) {
            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 && presentationValue instanceof Component) {
                addComponent(getGrid().getDataProvider().getId(item), (Component) presentationValue);
            }
            return ((Renderer<P>) renderer).encode(presentationValue);
        }

        private void addComponent(Object item, Component component) {
            if (activeComponents.containsKey(item)) {
                if (activeComponents.get(item).equals(component)) {
                    // Reusing old component
                    return;
                }
                removeComponent(item);
            }
            activeComponents.put(item, component);
            getGrid().addExtensionComponent(component);
        }

        private void removeComponent(Object item) {
            Component component = activeComponents.remove(item);
            if (component != null) {
                getGrid().removeExtensionComponent(component);
            }
        }

        /**
         * Gets a data object with the given key from the given JsonObject. If
         * there is no object with the key, this method creates a new
         * JsonObject.
         *
         * @param jsonObject
         *            the json object
         * @param key
         *            the key where the desired data object is stored
         * @return data object for the given key
         */
        private JsonObject getDataObject(JsonObject jsonObject, String key) {
            if (!jsonObject.hasKey(key)) {
                jsonObject.put(key, Json.createObject());
            }
            return jsonObject.getObject(key);
        }

        @Override
        protected ColumnState getState() {
            return getState(true);
        }

        @Override
        protected ColumnState getState(boolean markAsDirty) {
            return (ColumnState) super.getState(markAsDirty);
        }

        /**
         * This method extends the given Grid with this Column.
         *
         * @param grid
         *            the grid to extend
         */
        private void extend(Grid<T> grid) {
            super.extend(grid);
        }

        /**
         * Returns the identifier used with this Column in communication.
         *
         * @return the identifier string
         */
        private String getInternalId() {
            return getState(false).internalId;
        }

        /**
         * Sets the identifier to use with this Column in communication.
         *
         * @param id
         *            the identifier string
         */
        private void setInternalId(String id) {
            Objects.requireNonNull(id, "Communication ID can't be null");
            getState().internalId = id;
        }

        /**
         * Returns the user-defined identifier for this column.
         *
         * @return the identifier string
         */
        public String getId() {
            return userId;
        }

        /**
         * Sets the user-defined identifier to map this column. The identifier
         * can be used for example in {@link Grid#getColumn(String)}.
         * <p>
         * The id is also used as the {@link #setSortProperty(String...) backend
         * sort property} for this column if no sort property or sort order
         * provider has been set for this column.
         *
         * @see #setSortProperty(String...)
         * @see #setSortOrderProvider(SortOrderProvider)
         *
         * @param id
         *            the identifier string
         * @return this column
         */
        public Column<T, V> setId(String id) {
            Objects.requireNonNull(id, "Column identifier cannot be null");
            if (userId != null) {
                throw new IllegalStateException("Column identifier cannot be changed");
            }

            userId = id;
            getGrid().setColumnId(id, this);
            updateSortable();

            return this;
        }

        private void updateSortable() {
            boolean inMemory = getGrid().getDataProvider().isInMemory();
            boolean hasSortOrder = getSortOrder(SortDirection.ASCENDING).count() != 0;

            getState().sortable = this.sortable && (inMemory || hasSortOrder);
        }

        /**
         * Gets the function used to produce the value for data in this column
         * based on the row item.
         *
         * @return the value provider function
         *
         * @since 8.0.3
         */
        public ValueProvider<T, V> getValueProvider() {
            return valueProvider;
        }

        /**
         * Sets whether the user can sort this column or not. Whether the column
         * is actually sortable after {@code setSortable(true)} depends on the
         * {@link DataProvider} and the defined sort order for this column. When
         * using an {@link InMemoryDataProvider} sorting can be automatic.
         *
         * @param sortable
         *            {@code true} to enable sorting for this column;
         *            {@code false} to disable it
         * @return this column
         */
        public Column<T, V> setSortable(boolean sortable) {
            if (this.sortable != sortable) {
                this.sortable = sortable;
                updateSortable();
            }
            return this;
        }

        /**
         * Gets whether sorting is enabled for this column.
         *
         * @return {@code true} if the sorting is enabled for this column;
         *         {@code false} if not
         */
        public boolean isSortable() {
            return sortable;
        }

        /**
         * Gets whether the user can actually sort this column.
         *
         * @return {@code true} if the column can be sorted by the user;
         *         {@code false} if not
         *
         * @since 8.3.2
         */
        public boolean isSortableByUser() {
            return getState(false).sortable;
        }

        /**
         * Sets the header aria-label for this column.
         *
         * @param caption
         *            the header aria-label, null removes the aria-label from
         *            this column
         *
         * @return this column
         *
         * @since 8.2
         */
        public Column<T, V> setAssistiveCaption(String caption) {
            if (Objects.equals(caption, getAssistiveCaption())) {
                return this;
            }
            getState().assistiveCaption = caption;
            return this;
        }

        /**
         * Gets the header caption for this column.
         *
         * @return header caption
         *
         * @since 8.2
         */
        public String getAssistiveCaption() {
            return getState(false).assistiveCaption;
        }

        /**
         * Sets the header caption for this column.
         *
         * @param caption
         *            the header caption, not null
         *
         * @return this column
         */
        public Column<T, V> setCaption(String caption) {
            Objects.requireNonNull(caption, "Header caption can't be null");
            caption = Jsoup.parse(caption).text();
            if (caption.equals(getState(false).caption)) {
                return this;
            }
            getState().caption = caption;

            HeaderRow row = getGrid().getDefaultHeaderRow();
            if (row != null) {
                row.getCell(this).setText(caption);
            }

            return this;
        }

        /**
         * Gets the header caption for this column.
         *
         * @return header caption
         */
        public String getCaption() {
            return getState(false).caption;
        }

        /**
         * Sets a comparator to use with in-memory sorting with this column.
         * Sorting with a back-end is done using
         * {@link Column#setSortProperty(String...)}.
         *
         * @param comparator
         *            the comparator to use when sorting data in this column
         * @return this column
         */
        public Column<T, V> setComparator(SerializableComparator<T> comparator) {
            Objects.requireNonNull(comparator, "Comparator can't be null");
            this.comparator = comparator;
            return this;
        }

        /**
         * Gets the comparator to use with in-memory sorting for this column
         * when sorting in the given direction.
         *
         * @param sortDirection
         *            the direction this column is sorted by
         * @return comparator for this column
         */
        public SerializableComparator<T> getComparator(SortDirection sortDirection) {
            Objects.requireNonNull(comparator, "No comparator defined for sorted column.");
            boolean reverse = sortDirection != SortDirection.ASCENDING;
            return reverse ? (t1, t2) -> comparator.reversed().compare(t1, t2) : comparator;
        }

        /**
         * Sets strings describing back end properties to be used when sorting
         * this column.
         * <p>
         * By default, the {@link #setId(String) column id} will be used as the
         * sort property.
         *
         * @param properties
         *            the array of strings describing backend properties
         * @return this column
         */
        public Column<T, V> setSortProperty(String... properties) {
            Objects.requireNonNull(properties, "Sort properties can't be null");
            return setSortOrderProvider(dir -> Arrays.stream(properties).map(s -> new QuerySortOrder(s, dir)));
        }

        /**
         * Sets the sort orders when sorting this column. The sort order
         * provider is a function which provides {@link QuerySortOrder} objects
         * to describe how to sort by this column.
         * <p>
         * By default, the {@link #setId(String) column id} will be used as the
         * sort property.
         *
         * @param provider
         *            the function to use when generating sort orders with the
         *            given direction
         * @return this column
         */
        public Column<T, V> setSortOrderProvider(SortOrderProvider provider) {
            Objects.requireNonNull(provider, "Sort order provider can't be null");
            sortOrderProvider = provider;

            // Update state
            updateSortable();

            return this;
        }

        /**
         * Gets the sort orders to use with back-end sorting for this column
         * when sorting in the given direction.
         *
         * @see #setSortProperty(String...)
         * @see #setId(String)
         * @see #setSortOrderProvider(SortOrderProvider)
         *
         * @param direction
         *            the sorting direction
         * @return stream of sort orders
         */
        public Stream<QuerySortOrder> getSortOrder(SortDirection direction) {
            return sortOrderProvider.apply(direction);
        }

        /**
         * Sets the style generator that is used for generating class names for
         * cells in this column. Returning null from the generator results in no
         * custom style name being set.
         *
         * Note: The style generator is applied only to the body cells, not to
         * the Editor.
         *
         * @param cellStyleGenerator
         *            the cell style generator to set, not null
         * @return this column
         * @throws NullPointerException
         *             if {@code cellStyleGenerator} is {@code null}
         */
        public Column<T, V> setStyleGenerator(StyleGenerator<T> cellStyleGenerator) {
            Objects.requireNonNull(cellStyleGenerator, "Cell style generator must not be null");
            this.styleGenerator = cellStyleGenerator;
            getGrid().getDataCommunicator().reset();
            return this;
        }

        /**
         * Gets the style generator that is used for generating styles for
         * cells.
         *
         * @return the cell style generator
         */
        public StyleGenerator<T> getStyleGenerator() {
            return styleGenerator;
        }

        /**
         * Sets the description generator that is used for generating
         * descriptions for cells in this column. This method uses the
         * {@link ContentMode#PREFORMATTED} content mode.
         *
         * @see #setDescriptionGenerator(DescriptionGenerator, ContentMode)
         *
         * @param cellDescriptionGenerator
         *            the cell description generator to set, or {@code null} to
         *            remove a previously set generator
         * @return this column
         */
        public Column<T, V> setDescriptionGenerator(DescriptionGenerator<T> cellDescriptionGenerator) {
            return setDescriptionGenerator(cellDescriptionGenerator, ContentMode.PREFORMATTED);
        }

        /**
         * Sets the description generator that is used for generating
         * descriptions for cells in this column. This method uses the given
         * content mode.
         *
         * @see #setDescriptionGenerator(DescriptionGenerator)
         *
         * @param cellDescriptionGenerator
         *            the cell description generator to set, or {@code null} to
         *            remove a previously set generator
         * @param tooltipContentMode
         *            the content mode for tooltips
         * @return this column
         *
         * @since 8.2
         */
        public Column<T, V> setDescriptionGenerator(DescriptionGenerator<T> cellDescriptionGenerator,
                ContentMode tooltipContentMode) {
            this.descriptionGenerator = cellDescriptionGenerator;
            getState().tooltipContentMode = tooltipContentMode;
            getGrid().getDataCommunicator().reset();
            return this;
        }

        /**
         * Gets the description generator that is used for generating
         * descriptions for cells.
         *
         * @return the cell description generator, or <code>null</code> if no
         *         generator is set
         */
        public DescriptionGenerator<T> getDescriptionGenerator() {
            return descriptionGenerator;
        }

        /**
         * Sets the ratio with which the column expands.
         * <p>
         * By default, all columns expand equally (treated as if all of them had
         * an expand ratio of 1). Once at least one column gets a defined expand
         * ratio, the implicit expand ratio is removed, and only the defined
         * expand ratios are taken into account.
         * <p>
         * If a column has a defined width ({@link #setWidth(double)}), it
         * overrides this method's effects.
         * <p>
         * <em>Example:</em> A grid with three columns, with expand ratios 0, 1
         * and 2, respectively. The column with a <strong>ratio of 0 is exactly
         * as wide as its contents requires</strong>. The column with a ratio of
         * 1 is as wide as it needs, <strong>plus a third of any excess
         * space</strong>, because we have 3 parts total, and this column
         * reserves only one of those. The column with a ratio of 2, is as wide
         * as it needs to be, <strong>plus two thirds</strong> of the excess
         * width.
         *
         * @param expandRatio
         *            the expand ratio of this column. {@code 0} to not have it
         *            expand at all. A negative number to clear the expand
         *            value.
         * @throws IllegalStateException
         *             if the column is no longer attached to any grid
         * @see #setWidth(double)
         */
        public Column<T, V> setExpandRatio(int expandRatio) throws IllegalStateException {
            checkColumnIsAttached();
            if (expandRatio != getExpandRatio()) {
                getState().expandRatio = expandRatio;
                getGrid().markAsDirty();
            }
            return this;
        }

        /**
         * Returns the column's expand ratio.
         *
         * @return the column's expand ratio
         * @see #setExpandRatio(int)
         */
        public int getExpandRatio() {
            return getState(false).expandRatio;
        }

        /**
         * Clears the expand ratio for this column.
         * <p>
         * Equal to calling {@link #setExpandRatio(int) setExpandRatio(-1)}
         *
         * @throws IllegalStateException
         *             if the column is no longer attached to any grid
         */
        public Column<T, V> clearExpandRatio() throws IllegalStateException {
            return setExpandRatio(-1);
        }

        /**
         * Returns the width (in pixels). By default a column width is
         * {@value com.vaadin.shared.ui.grid.GridConstants#DEFAULT_COLUMN_WIDTH_PX}
         * (undefined).
         *
         * @return the width in pixels of the column
         * @throws IllegalStateException
         *             if the column is no longer attached to any grid
         */
        public double getWidth() throws IllegalStateException {
            checkColumnIsAttached();
            return getState(false).width;
        }

        /**
         * Sets the width (in pixels).
         * <p>
         * This overrides any configuration set by any of
         * {@link #setExpandRatio(int)}, {@link #setMinimumWidth(double)} or
         * {@link #setMaximumWidth(double)}.
         *
         * @param pixelWidth
         *            the new pixel width of the column
         * @return the column itself
         *
         * @throws IllegalStateException
         *             if the column is no longer attached to any grid
         * @throws IllegalArgumentException
         *             thrown if pixel width is less than zero
         */
        public Column<T, V> setWidth(double pixelWidth) throws IllegalStateException, IllegalArgumentException {
            checkColumnIsAttached();
            if (pixelWidth < 0) {
                throw new IllegalArgumentException("Pixel width should be greated than 0 (in " + toString() + ")");
            }
            if (pixelWidth != getWidth()) {
                getState().width = pixelWidth;
                getGrid().markAsDirty();
                getGrid().fireColumnResizeEvent(this, false);
            }
            return this;
        }

        /**
         * Returns whether this column has an undefined width.
         *
         * @since 7.6
         * @return whether the width is undefined
         * @throws IllegalStateException
         *             if the column is no longer attached to any grid
         */
        public boolean isWidthUndefined() {
            checkColumnIsAttached();
            return getState(false).width < 0;
        }

        /**
         * Marks the column width as undefined. An undefined width means the
         * grid is free to resize the column based on the cell contents and
         * available space in the grid.
         *
         * @return the column itself
         */
        public Column<T, V> setWidthUndefined() {
            checkColumnIsAttached();
            if (!isWidthUndefined()) {
                getState().width = -1;
                getGrid().markAsDirty();
                getGrid().fireColumnResizeEvent(this, false);
            }
            return this;
        }

        /**
         * Sets the minimum width for this column.
         * <p>
         * This defines the minimum guaranteed pixel width of the column
         * <em>when it is set to expand</em>.
         *
         * Note: Value -1 is not accepted, use {@link #setWidthUndefined()}
         * instead.
         *
         * @param pixels
         *            the minimum width for the column
         * @throws IllegalStateException
         *             if the column is no longer attached to any grid
         * @see #setExpandRatio(int)
         * @return the column itself
         */
        public Column<T, V> setMinimumWidth(double pixels) throws IllegalStateException {
            checkColumnIsAttached();

            final double maxwidth = getMaximumWidth();
            if (pixels >= 0 && pixels > maxwidth && maxwidth >= 0) {
                throw new IllegalArgumentException(
                        "New minimum width (" + pixels + ") was greater than maximum width (" + maxwidth + ")");
            }
            getState().minWidth = pixels;
            getGrid().markAsDirty();
            return this;
        }

        /**
         * Return the minimum width for this column.
         *
         * @return the minimum width for this column
         * @see #setMinimumWidth(double)
         */
        public double getMinimumWidth() {
            return getState(false).minWidth;
        }

        /**
         * Sets whether the width of the contents in the column should be
         * considered minimum width for this column.
         * <p>
         * If this is set to <code>true</code> (default for backwards
         * compatibility), then a column will not shrink to smaller than the
         * width required to show the contents available when calculating the
         * widths (only the widths of the initially rendered rows are
         * considered).
         * <p>
         * If this is set to <code>false</code> and the column has been set to
         * expand using #setExpandRatio(int), then the contents of the column
         * will be ignored when calculating the width, and the column will thus
         * shrink down to the minimum width defined by #setMinimumWidth(double)
         * if necessary.
         *
         * @param minimumWidthFromContent
         *            <code>true</code> to reserve space for all contents,
         *            <code>false</code> to allow the column to shrink smaller
         *            than the contents
         * @return the column itself
         * @throws IllegalStateException
         *             if the column is no longer attached to any grid
         * @see #setMinimumWidth(double)
         * @since 8.1
         */
        public Column<T, V> setMinimumWidthFromContent(boolean minimumWidthFromContent)
                throws IllegalStateException {
            checkColumnIsAttached();

            if (isMinimumWidthFromContent() != minimumWidthFromContent) {
                getState().minimumWidthFromContent = minimumWidthFromContent;
                getGrid().markAsDirty();
            }
            return this;
        }

        /**
         * Gets whether the width of the contents in the column should be
         * considered minimum width for this column.
         *
         * @return <code>true</code> to reserve space for all contents,
         *         <code>false</code> to allow the column to shrink smaller than
         *         the contents
         * @see #setMinimumWidthFromContent(boolean)
         * @since 8.1
         */
        public boolean isMinimumWidthFromContent() {
            return getState(false).minimumWidthFromContent;
        }

        /**
         * Sets the maximum width for this column.
         * <p>
         * This defines the maximum allowed pixel width of the column <em>when
         * it is set to expand</em>.
         *
         * @param pixels
         *            the maximum width
         * @throws IllegalStateException
         *             if the column is no longer attached to any grid
         * @see #setExpandRatio(int)
         */
        public Column<T, V> setMaximumWidth(double pixels) {
            checkColumnIsAttached();

            final double minwidth = getMinimumWidth();
            if (pixels >= 0 && pixels < minwidth && minwidth >= 0) {
                throw new IllegalArgumentException(
                        "New maximum width (" + pixels + ") was less than minimum width (" + minwidth + ")");
            }

            getState().maxWidth = pixels;
            getGrid().markAsDirty();
            return this;
        }

        /**
         * Returns the maximum width for this column.
         *
         * @return the maximum width for this column
         * @see #setMaximumWidth(double)
         */
        public double getMaximumWidth() {
            return getState(false).maxWidth;
        }

        /**
         * Sets whether this column can be resized by the user.
         *
         * @since 7.6
         * @param resizable
         *            {@code true} if this column should be resizable,
         *            {@code false} otherwise
         * @throws IllegalStateException
         *             if the column is no longer attached to any grid
         */
        public Column<T, V> setResizable(boolean resizable) {
            checkColumnIsAttached();
            if (resizable != isResizable()) {
                getState().resizable = resizable;
                getGrid().markAsDirty();
            }
            return this;
        }

        /**
         * Gets the caption of the hiding toggle for this column.
         *
         * @since 7.5.0
         * @see #setHidingToggleCaption(String)
         * @return the caption for the hiding toggle for this column
         */
        public String getHidingToggleCaption() {
            return getState(false).hidingToggleCaption;
        }

        /**
         * Sets the caption of the hiding toggle for this column. Shown in the
         * toggle for this column in the grid's sidebar when the column is
         * {@link #isHidable() hidable}.
         * <p>
         * The default value is <code>null</code>, and in that case the column's
         * {@link #getCaption() header caption} is used.
         * <p>
         * <em>NOTE:</em> setting this to empty string might cause the hiding
         * toggle to not render correctly.
         *
         * @since 7.5.0
         * @param hidingToggleCaption
         *            the text to show in the column hiding toggle
         * @return the column itself
         */
        public Column<T, V> setHidingToggleCaption(String hidingToggleCaption) {
            if (hidingToggleCaption != getHidingToggleCaption()) {
                getState().hidingToggleCaption = hidingToggleCaption;
            }
            return this;
        }

        /**
         * Hides or shows the column. By default columns are visible before
         * explicitly hiding them.
         *
         * @since 7.5.0
         * @param hidden
         *            <code>true</code> to hide the column, <code>false</code>
         *            to show
         * @return this column
         * @throws IllegalStateException
         *             if the column is no longer attached to any grid
         */
        public Column<T, V> setHidden(boolean hidden) {
            checkColumnIsAttached();
            if (hidden != isHidden()) {
                getState().hidden = hidden;
                getGrid().fireColumnVisibilityChangeEvent(this, hidden, false);
            }
            return this;
        }

        /**
         * Returns whether this column is hidden. Default is {@code false}.
         *
         * @since 7.5.0
         * @return <code>true</code> if the column is currently hidden,
         *         <code>false</code> otherwise
         */
        public boolean isHidden() {
            return getState(false).hidden;
        }

        /**
         * Sets whether this column can be hidden by the user. Hidable columns
         * can be hidden and shown via the sidebar menu.
         *
         * @since 7.5.0
         * @param hidable
         *            <code>true</code> if the column may be hidable by the user
         *            via UI interaction
         * @return this column
         */
        public Column<T, V> setHidable(boolean hidable) {
            if (hidable != isHidable()) {
                getState().hidable = hidable;
            }
            return this;
        }

        /**
         * Returns whether this column can be hidden by the user. Default is
         * {@code false}.
         * <p>
         * <em>Note:</em> the column can be programmatically hidden using
         * {@link #setHidden(boolean)} regardless of the returned value.
         *
         * @since 7.5.0
         * @return <code>true</code> if the user can hide the column,
         *         <code>false</code> if not
         */
        public boolean isHidable() {
            return getState(false).hidable;
        }

        /**
         * Returns whether this column can be resized by the user. Default is
         * {@code true}.
         * <p>
         * <em>Note:</em> the column can be programmatically resized using
         * {@link #setWidth(double)} and {@link #setWidthUndefined()} regardless
         * of the returned value.
         *
         * @since 7.6
         * @return {@code true} if this column is resizable, {@code false}
         *         otherwise
         */
        public boolean isResizable() {
            return getState(false).resizable;
        }

        /**
         * Sets whether this Column has a component displayed in Editor or not.
         * A column can only be editable if an editor component or binding has
         * been set.
         *
         * @param editable
         *            {@code true} if column is editable; {@code false} if not
         * @return this column
         *
         * @see #setEditorComponent(HasValue, Setter)
         * @see #setEditorBinding(Binding)
         */
        public Column<T, V> setEditable(boolean editable) {
            Objects.requireNonNull(editorBinding, "Column has no editor binding or component defined");
            getState().editable = editable;
            return this;
        }

        /**
         * Gets whether this Column has a component displayed in Editor or not.
         *
         * @return {@code true} if the column displays an editor component;
         *         {@code false} if not
         */
        public boolean isEditable() {
            return getState(false).editable;
        }

        /**
         * Sets an editor binding for this column. The {@link Binding} is used
         * when a row is in editor mode to define how to populate an editor
         * component based on the edited row and how to update an item based on
         * the value in the editor component.
         * <p>
         * To create a binding to use with a column, define a binding for the
         * editor binder (<code>grid.getEditor().getBinder()</code>) using e.g.
         * {@link Binder#forField(HasValue)}. You can also use
         * {@link #setEditorComponent(HasValue, Setter)} if no validator or
         * converter is needed for the binding.
         * <p>
         * The {@link HasValue} that the binding is defined to use must be a
         * {@link Component}.
         *
         * @param binding
         *            the binding to use for this column
         * @return this column
         *
         * @see #setEditorComponent(HasValue, Setter)
         * @see Binding
         * @see Grid#getEditor()
         * @see Editor#getBinder()
         */
        public Column<T, V> setEditorBinding(Binding<T, ?> binding) {
            Objects.requireNonNull(binding, "null is not a valid editor field");

            if (!(binding.getField() instanceof Component)) {
                throw new IllegalArgumentException("Binding target must be a component.");
            }

            this.editorBinding = binding;

            return setEditable(true);
        }

        /**
         * Gets the binder binding that is currently used for this column.
         *
         * @return the used binder binding, or <code>null</code> if no binding
         *         is configured
         *
         * @see #setEditorBinding(Binding)
         */
        public Binding<T, ?> getEditorBinding() {
            return editorBinding;
        }

        /**
         * Sets a component and setter to use for editing values of this column
         * in the editor row. This is a shorthand for use in simple cases where
         * no validator or converter is needed. Use
         * {@link #setEditorBinding(Binding)} to support more complex cases.
         * <p>
         * <strong>Note:</strong> The same component cannot be used for multiple
         * columns.
         *
         * @param editorComponent
         *            the editor component
         * @param setter
         *            a setter that stores the component value in the row item
         * @return this column
         *
         * @see #setEditorBinding(Binding)
         * @see Grid#getEditor()
         * @see Binder#bind(HasValue, ValueProvider, Setter)
         */
        public <C extends HasValue<V> & Component> Column<T, V> setEditorComponent(C editorComponent,
                Setter<T, V> setter) {
            Objects.requireNonNull(editorComponent, "Editor component cannot be null");
            Objects.requireNonNull(setter, "Setter cannot be null");

            Binding<T, V> binding = getGrid().getEditor().getBinder().bind(editorComponent, valueProvider::apply,
                    setter);

            return setEditorBinding(binding);
        }

        /**
         * Sets a component to use for editing values of this columns in the
         * editor row. This method can only be used if the column has an
         * {@link #setId(String) id} and the {@link Grid} has been created using
         * {@link Grid#Grid(Class)} or some other way that allows finding
         * properties based on property names.
         * <p>
         * This is a shorthand for use in simple cases where no validator or
         * converter is needed. Use {@link #setEditorBinding(Binding)} to
         * support more complex cases.
         * <p>
         * <strong>Note:</strong> The same component cannot be used for multiple
         * columns.
         *
         * @param editorComponent
         *            the editor component
         * @return this column
         *
         * @see #setEditorBinding(Binding)
         * @see Grid#getEditor()
         * @see Binder#bind(HasValue, String)
         * @see Grid#Grid(Class)
         */
        public <F, C extends HasValue<F> & Component> Column<T, V> setEditorComponent(C editorComponent) {
            Objects.requireNonNull(editorComponent, "Editor component cannot be null");

            String propertyName = getId();
            if (propertyName == null) {
                throw new IllegalStateException(
                        "setEditorComponent without a setter can only be used if the column has an id. "
                                + "Use another setEditorComponent(Component, Setter) or setEditorBinding(Binding) instead.");
            }

            Binding<T, F> binding = getGrid().getEditor().getBinder().bind(editorComponent, propertyName);

            return setEditorBinding(binding);
        }

        /**
         * Sets the Renderer for this Column. Setting the renderer will cause
         * all currently available row data to be recreated and sent to the
         * client.
         *
         * Note: Setting a new renderer will reset presentation provider if
         * it exists.
         *
         * @param renderer
         *            the new renderer
         * @return this column
         *
         * @since 8.0.3
         */
        public Column<T, V> setRenderer(Renderer<? super V> renderer) {
            return setRenderer(ValueProvider.identity(), renderer);
        }

        /**
         * Sets the Renderer for this Column. Setting the renderer will cause
         * all currently available row data to be recreated and sent to the
         * client.
         * <p>
         * The presentation provider is a method that takes the value of this
         * column on a single row, and maps that to a value that the renderer
         * accepts. This feature can be used for storing a complex value in a
         * column for editing, but providing a simplified presentation for the
         * user when not editing.
         *
         * @param presentationProvider
         *            the function to get presentations from the value of this
         *            column, not {@code null}
         * @param renderer
         *            the new renderer, not {@code null}
         *
         * @param <P>
         *            the presentation type
         *
         * @return this column
         *
         * @since 8.1
         */
        public <P> Column<T, V> setRenderer(ValueProvider<V, P> presentationProvider,
                Renderer<? super P> renderer) {
            Objects.requireNonNull(renderer, "Renderer can not be null");
            Objects.requireNonNull(presentationProvider, "Presentation provider can not be null");

            // Remove old renderer
            Connector oldRenderer = getState().renderer;
            if (oldRenderer instanceof Extension) {
                removeExtension((Extension) oldRenderer);
            }

            // Set new renderer
            getState().renderer = renderer;
            addExtension(renderer);
            this.presentationProvider = presentationProvider;

            // Trigger redraw
            getGrid().getDataCommunicator().reset();

            return this;
        }

        /**
         * Gets the Renderer for this Column.
         *
         * @return the renderer
         * @since 8.1
         */
        public Renderer<?> getRenderer() {
            return (Renderer<?>) getState().renderer;
        }

        /**
         * Sets whether Grid should handle events in this Column from Components
         * and Widgets rendered by certain Renderers. By default the events are
         * not handled.
         * <p>
         * <strong>Note:</strong> Enabling this feature will for example select
         * a row when a component is clicked. For example in the case of a
         * {@link ComboBox} or {@link TextField} it might be problematic as the
         * component gets re-rendered and might lose focus.
         *
         * @param handleWidgetEvents
         *            {@code true} to handle events; {@code false} to not
         * @return this column
         * @since 8.3
         */
        public Column<T, V> setHandleWidgetEvents(boolean handleWidgetEvents) {
            getState().handleWidgetEvents = handleWidgetEvents;
            return this;
        }

        /**
         * Gets whether Grid is handling the events in this Column from
         * Component and Widgets.
         *
         * @see #setHandleWidgetEvents(boolean)
         *
         * @return {@code true} if handling events; {@code false} if not
         * @since 8.3
         */
        public boolean isHandleWidgetEvents() {
            return getState(false).handleWidgetEvents;
        }

        /**
         * Gets the grid that this column belongs to.
         *
         * @return the grid that this column belongs to, or <code>null</code> if
         *         this column has not yet been associated with any grid
         */
        @SuppressWarnings("unchecked")
        protected Grid<T> getGrid() {
            return (Grid<T>) getParent();
        }

        /**
         * Checks if column is attached and throws an
         * {@link IllegalStateException} if it is not.
         *
         * @throws IllegalStateException
         *             if the column is no longer attached to any grid
         */
        protected void checkColumnIsAttached() throws IllegalStateException {
            if (getGrid() == null) {
                throw new IllegalStateException("Column is no longer attached to a grid.");
            }
        }

        /**
         * Writes the design attributes for this column into given element.
         *
         * @since 7.5.0
         *
         * @param element
         *            Element to write attributes into
         *
         * @param designContext
         *            the design context
         */
        protected void writeDesign(Element element, DesignContext designContext) {
            Attributes attributes = element.attributes();

            ColumnState defaultState = new ColumnState();

            if (getId() == null) {
                setId("column" + getGrid().getColumns().indexOf(this));
            }

            DesignAttributeHandler.writeAttribute("column-id", attributes, getId(), null, String.class,
                    designContext);

            // Sortable is a special attribute that depends on the data
            // provider.
            DesignAttributeHandler.writeAttribute("sortable", attributes, isSortable(), null, boolean.class,
                    designContext);
            DesignAttributeHandler.writeAttribute("editable", attributes, isEditable(), defaultState.editable,
                    boolean.class, designContext);
            DesignAttributeHandler.writeAttribute("resizable", attributes, isResizable(), defaultState.resizable,
                    boolean.class, designContext);

            DesignAttributeHandler.writeAttribute("hidable", attributes, isHidable(), defaultState.hidable,
                    boolean.class, designContext);
            DesignAttributeHandler.writeAttribute("hidden", attributes, isHidden(), defaultState.hidden,
                    boolean.class, designContext);
            DesignAttributeHandler.writeAttribute("hiding-toggle-caption", attributes, getHidingToggleCaption(),
                    defaultState.hidingToggleCaption, String.class, designContext);

            DesignAttributeHandler.writeAttribute("width", attributes, getWidth(), defaultState.width, Double.class,
                    designContext);
            DesignAttributeHandler.writeAttribute("min-width", attributes, getMinimumWidth(), defaultState.minWidth,
                    Double.class, designContext);
            DesignAttributeHandler.writeAttribute("max-width", attributes, getMaximumWidth(), defaultState.maxWidth,
                    Double.class, designContext);
            DesignAttributeHandler.writeAttribute("expand", attributes, getExpandRatio(), defaultState.expandRatio,
                    Integer.class, designContext);
        }

        /**
         * Reads the design attributes for this column from given element.
         *
         * @since 7.5.0
         * @param design
         *            Element to read attributes from
         * @param designContext
         *            the design context
         */
        @SuppressWarnings("unchecked")
        protected void readDesign(Element design, DesignContext designContext) {
            Attributes attributes = design.attributes();

            if (design.hasAttr("sortable")) {
                setSortable(DesignAttributeHandler.readAttribute("sortable", attributes, boolean.class));
            } else {
                setSortable(false);
            }
            if (design.hasAttr("editable")) {
                /**
                 * This is a fake editor just to have something (otherwise
                 * "setEditable" throws an exception.
                 *
                 * Let's use TextField here because we support only Strings as
                 * inline data type. It will work incorrectly for other types
                 * but we don't support them anyway.
                 */
                setEditorComponent((HasValue<V> & Component) new TextField(), (item, value) -> {
                    // Ignore user value since we don't know the setter
                });
                setEditable(DesignAttributeHandler.readAttribute("editable", attributes, boolean.class));
            }
            if (design.hasAttr("resizable")) {
                setResizable(DesignAttributeHandler.readAttribute("resizable", attributes, boolean.class));
            }

            if (design.hasAttr("hidable")) {
                setHidable(DesignAttributeHandler.readAttribute("hidable", attributes, boolean.class));
            }
            if (design.hasAttr("hidden")) {
                setHidden(DesignAttributeHandler.readAttribute("hidden", attributes, boolean.class));
            }
            if (design.hasAttr("hiding-toggle-caption")) {
                setHidingToggleCaption(
                        DesignAttributeHandler.readAttribute("hiding-toggle-caption", attributes, String.class));
            }
            if (design.hasAttr("assistive-caption")) {
                setAssistiveCaption(
                        DesignAttributeHandler.readAttribute("assistive-caption", attributes, String.class));
            }

            // Read size info where necessary.
            if (design.hasAttr("width")) {
                setWidth(DesignAttributeHandler.readAttribute("width", attributes, Double.class));
            }
            if (design.hasAttr("min-width")) {
                setMinimumWidth(DesignAttributeHandler.readAttribute("min-width", attributes, Double.class));
            }
            if (design.hasAttr("max-width")) {
                setMaximumWidth(DesignAttributeHandler.readAttribute("max-width", attributes, Double.class));
            }
            if (design.hasAttr("expand")) {
                if (design.attr("expand").isEmpty()) {
                    setExpandRatio(1);
                } else {
                    setExpandRatio(DesignAttributeHandler.readAttribute("expand", attributes, Integer.class));
                }
            }
        }

        /**
         * Gets the DataGenerator for this Column.
         *
         * @return data generator
         */
        private DataGenerator<T> getDataGenerator() {
            return dataGenerator;
        }
    }

    private class HeaderImpl extends Header {

        @Override
        protected Grid<T> getGrid() {
            return Grid.this;
        }

        @Override
        protected SectionState getState(boolean markAsDirty) {
            return Grid.this.getState(markAsDirty).header;
        }

        @Override
        protected Column<?, ?> getColumnByInternalId(String internalId) {
            return getGrid().getColumnByInternalId(internalId);
        }

        @Override
        @SuppressWarnings("unchecked")
        protected String getInternalIdForColumn(Column<?, ?> column) {
            return getGrid().getInternalIdForColumn((Column<T, ?>) column);
        }
    };

    private class FooterImpl extends Footer {

        @Override
        protected Grid<T> getGrid() {
            return Grid.this;
        }

        @Override
        protected SectionState getState(boolean markAsDirty) {
            return Grid.this.getState(markAsDirty).footer;
        }

        @Override
        protected Column<?, ?> getColumnByInternalId(String internalId) {
            return getGrid().getColumnByInternalId(internalId);
        }

        @Override
        @SuppressWarnings("unchecked")
        protected String getInternalIdForColumn(Column<?, ?> column) {
            return getGrid().getInternalIdForColumn((Column<T, ?>) column);
        }
    };

    private final Set<Column<T, ?>> columnSet = new LinkedHashSet<>();
    private final Map<String, Column<T, ?>> columnKeys = new HashMap<>();
    private final Map<String, Column<T, ?>> columnIds = new HashMap<>();

    private final List<GridSortOrder<T>> sortOrder = new ArrayList<>();
    private final DetailsManager<T> detailsManager;
    private final Set<Component> extensionComponents = new HashSet<>();
    private StyleGenerator<T> styleGenerator = item -> null;
    private DescriptionGenerator<T> descriptionGenerator;

    private final Header header = new HeaderImpl();
    private final Footer footer = new FooterImpl();

    private int counter = 0;

    private GridSelectionModel<T> selectionModel;

    private Editor<T> editor;

    private PropertySet<T> propertySet;

    private Class<T> beanType = null;

    /**
     * Creates a new grid without support for creating columns based on property
     * names. Use an alternative constructor, such as {@link Grid#Grid(Class)},
     * to create a grid that automatically sets up columns based on the type of
     * presented data.
     *
     * @see #Grid(Class)
     * @see #withPropertySet(PropertySet)
     */
    public Grid() {
        this(new DataCommunicator<>());
    }

    /**
     * Creates a new grid that uses reflection based on the provided bean type
     * to automatically set up an initial set of columns. All columns will be
     * configured using the same {@link Object#toString()} renderer that is used
     * by {@link #addColumn(ValueProvider)}.
     *
     * @param beanType
     *            the bean type to use, not <code>null</code>
     * @see #Grid()
     * @see #withPropertySet(PropertySet)
     */
    public Grid(Class<T> beanType) {
        this(beanType, new DataCommunicator<>());
    }

    /**
     * Creates a new grid that uses custom data communicator and provided bean
     * type
     *
     * It uses reflection of the provided bean type to automatically set up an
     * initial set of columns. All columns will be configured using the same
     * {@link Object#toString()} renderer that is used by
     * {@link #addColumn(ValueProvider)}.
     *
     * @param beanType
     *            the bean type to use, not <code>null</code>
     * @param dataCommunicator
     *            the data communicator to use, not<code>null</code>
     * @since 8.0.7
     */
    protected Grid(Class<T> beanType, DataCommunicator<T> dataCommunicator) {
        this(BeanPropertySet.get(beanType), dataCommunicator);
        this.beanType = beanType;
    }

    /**
     * Creates a new grid with the given data communicator and without support
     * for creating columns based on property names.
     *
     * @param dataCommunicator
     *            the custom data communicator to set
     * @see #Grid()
     * @see #Grid(PropertySet, DataCommunicator)
     * @since 8.0.7
     */
    protected Grid(DataCommunicator<T> dataCommunicator) {
        this(new PropertySet<T>() {
            @Override
            public Stream<PropertyDefinition<T, ?>> getProperties() {
                // No columns configured by default
                return Stream.empty();
            }

            @Override
            public Optional<PropertyDefinition<T, ?>> getProperty(String name) {
                throw new IllegalStateException(
                        "A Grid created without a bean type class literal or a custom property set"
                                + " doesn't support finding properties by name.");
            }
        }, dataCommunicator);
    }

    /**
     * Creates a grid using a custom {@link PropertySet} implementation for
     * configuring the initial columns and resolving property names for
     * {@link #addColumn(String)} and
     * {@link Column#setEditorComponent(HasValue)}.
     *
     * @see #withPropertySet(PropertySet)
     *
     * @param propertySet
     *            the property set implementation to use, not <code>null</code>.
     */
    protected Grid(PropertySet<T> propertySet) {
        this(propertySet, new DataCommunicator<>());
    }

    /**
     * Creates a grid using a custom {@link PropertySet} implementation and
     * custom data communicator.
     * <p>
     * Property set is used for configuring the initial columns and resolving
     * property names for {@link #addColumn(String)} and
     * {@link Column#setEditorComponent(HasValue)}.
     *
     * @see #withPropertySet(PropertySet)
     *
     * @param propertySet
     *            the property set implementation to use, not <code>null</code>.
     * @param dataCommunicator
     *            the data communicator to use, not<code>null</code>
     * @since 8.0.7
     */
    protected Grid(PropertySet<T> propertySet, DataCommunicator<T> dataCommunicator) {
        super(dataCommunicator);
        registerRpc(new GridServerRpcImpl());
        setDefaultHeaderRow(appendHeaderRow());
        setSelectionModel(new SingleSelectionModelImpl<>());

        detailsManager = new DetailsManager<>();
        addExtension(detailsManager);
        addDataGenerator(detailsManager);

        addDataGenerator((item, json) -> {
            String styleName = styleGenerator.apply(item);
            if (styleName != null && !styleName.isEmpty()) {
                json.put(GridState.JSONKEY_ROWSTYLE, styleName);
            }
            if (descriptionGenerator != null) {
                String description = descriptionGenerator.apply(item);
                if (description != null && !description.isEmpty()) {
                    json.put(GridState.JSONKEY_ROWDESCRIPTION, description);
                }
            }
        });

        setPropertySet(propertySet);

        // Automatically add columns for all available properties
        propertySet.getProperties().map(PropertyDefinition::getName).forEach(this::addColumn);
    }

    @Override
    public void beforeClientResponse(boolean initial) {
        super.beforeClientResponse(initial);

        if (initial && editor.isOpen()) {
            // Re-attaching grid. Any old editor should be closed.
            editor.cancel();
        }
    }

    /**
     * Sets the property set to use for this grid. Does not create or update
     * columns in any way but will delete and re-create the editor.
     * <p>
     * This is only meant to be called from constructors and readDesign, at a
     * stage where it does not matter if you throw away the editor.
     *
     * @param propertySet
     *            the property set to use
     *
     * @since 8.0.3
     */
    protected void setPropertySet(PropertySet<T> propertySet) {
        Objects.requireNonNull(propertySet, "propertySet cannot be null");
        this.propertySet = propertySet;

        if (editor instanceof Extension) {
            removeExtension((Extension) editor);
        }
        editor = createEditor();
        if (editor instanceof Extension) {
            addExtension((Extension) editor);
        }

    }

    /**
     * Returns the property set used by this grid.
     *
     * @return propertySet the property set to return
     * @since 8.4
     */
    protected PropertySet<T> getPropertySet() {
        return propertySet;
    }

    /**
     * Creates a grid using a custom {@link PropertySet} implementation for
     * creating a default set of columns and for resolving property names with
     * {@link #addColumn(String)} and
     * {@link Column#setEditorComponent(HasValue)}.
     * <p>
     * This functionality is provided as static method instead of as a public
     * constructor in order to make it possible to use a custom property set
     * without creating a subclass while still leaving the public constructors
     * focused on the common use cases.
     *
     * @see Grid#Grid()
     * @see Grid#Grid(Class)
     *
     * @param propertySet
     *            the property set implementation to use, not <code>null</code>.
     * @return a new grid using the provided property set, not <code>null</code>
     */
    public static <BEAN> Grid<BEAN> withPropertySet(PropertySet<BEAN> propertySet) {
        return new Grid<>(propertySet);
    }

    /**
     * Creates a new {@code Grid} using the given caption.
     *
     * @param caption
     *            the caption of the grid
     */
    public Grid(String caption) {
        this();
        setCaption(caption);
    }

    /**
     * Creates a new {@code Grid} using the given caption and
     * {@code DataProvider}.
     *
     * @param caption
     *            the caption of the grid
     * @param dataProvider
     *            the data provider, not {@code null}
     */
    public Grid(String caption, DataProvider<T, ?> dataProvider) {
        this(caption);
        setDataProvider(dataProvider);
    }

    /**
     * Creates a new {@code Grid} using the given {@code DataProvider}.
     *
     * @param dataProvider
     *            the data provider, not {@code null}
     */
    public Grid(DataProvider<T, ?> dataProvider) {
        this();
        setDataProvider(dataProvider);
    }

    /**
     * Creates a new {@code Grid} using the given caption and collection of
     * items.
     *
     * @param caption
     *            the caption of the grid
     * @param items
     *            the data items to use, not {@ode null}
     */
    public Grid(String caption, Collection<T> items) {
        this(caption, DataProvider.ofCollection(items));
    }

    /**
     * Gets the bean type used by this grid.
     * <p>
     * The bean type is used to automatically set up a column added using a
     * property name.
     *
     * @return the used bean type or <code>null</code> if no bean type has been
     *         defined
     *
     * @since 8.0.3
     */
    public Class<T> getBeanType() {
        return beanType;
    }

    public <V> void fireColumnVisibilityChangeEvent(Column<T, V> column, boolean hidden, boolean userOriginated) {
        fireEvent(new ColumnVisibilityChangeEvent(this, column, hidden, userOriginated));
    }

    /**
     * Adds a new column with the given property name. The column will use a
     * {@link TextRenderer}. The value is converted to a String using
     * {@link Object#toString()}. 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>
     * @return the newly added column, not <code>null</code>
     */
    public Column<T, ?> addColumn(String propertyName) {
        return addColumn(propertyName, new TextRenderer());
    }

    /**
     * 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>
     * @return the newly added column, not <code>null</code>
     */
    public Column<T, ?> addColumn(String propertyName, AbstractRenderer<? super T, ?> renderer) {
        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 = addColumn(definition.getGetter(), (AbstractRenderer) renderer)
                .setId(definition.getName()).setCaption(definition.getCaption());

        return column;
    }

    /**
     * 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>
     *
     * @since 8.8
     */
    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
     * natural ordering of elements if they are mutually comparable and
     * otherwise fall back to comparing the string representations of the
     * values.
     *
     * @param valueProvider
     *            the value provider
     *
     * @return the new column
     */
    public <V> Column<T, V> addColumn(ValueProvider<T, V> valueProvider) {
        return addColumn(valueProvider, new TextRenderer());
    }

    /**
     * Adds a new column to this {@link Grid} with typed renderer and value
     * provider.
     *
     * @param valueProvider
     *            the value provider
     * @param renderer
     *            the column value renderer
     * @param <V>
     *            the column value type
     *
     * @return the new column
     *
     * @see AbstractRenderer
     */
    public <V> Column<T, V> addColumn(ValueProvider<T, V> valueProvider,
            AbstractRenderer<? super T, ? super V> renderer) {
        return addColumn(valueProvider, ValueProvider.identity(), renderer);
    }

    /**
     * Adds a new column to this {@link Grid} with value provider and
     * presentation provider.
     * <p>
     * <strong>Note:</strong> The presentation type for this method is set to be
     * String. To use any custom renderer with the presentation provider, use
     * {@link #addColumn(ValueProvider, ValueProvider, AbstractRenderer)}.
     *
     * @param valueProvider
     *            the value provider
     * @param presentationProvider
     *            the value presentation provider
     * @param <V>
     *            the column value type
     *
     * @see #addColumn(ValueProvider, ValueProvider, AbstractRenderer)
     *
     * @return the new column
     * @since 8.1
     */
    public <V> Column<T, V> addColumn(ValueProvider<T, V> valueProvider,
            ValueProvider<V, String> presentationProvider) {
        return addColumn(valueProvider, presentationProvider, new TextRenderer());
    }

    /**
     * Adds a new column to this {@link Grid} with value provider, presentation
     * provider and typed renderer.
     *
     * <p>
     * The presentation provider is a method that takes the value from the value
     * provider, and maps that to a value that the renderer accepts. This
     * feature can be used for storing a complex value in a column for editing,
     * but providing a simplified presentation for the user when not editing.
     *
     * @param valueProvider
     *            the value provider
     * @param presentationProvider
     *            the value presentation provider
     * @param renderer
     *            the column value renderer
     * @param <V>
     *            the column value type
     * @param <P>
     *            the column presentation type
     *
     * @return the new column
     *
     * @see AbstractRenderer
     * @since 8.1
     */
    public <V, P> Column<T, V> addColumn(ValueProvider<T, V> valueProvider,
            ValueProvider<V, P> presentationProvider, AbstractRenderer<? super T, ? super P> renderer) {
        String generatedIdentifier = getGeneratedIdentifier();
        Column<T, V> column = createColumn(valueProvider, presentationProvider, renderer);
        addColumn(generatedIdentifier, column);
        return column;
    }

    /**
     * Adds a column that shows components.
     * <p>
     * This is a shorthand for {@link #addColumn()} with a
     * {@link ComponentRenderer}.
     *
     * @param componentProvider
     *            a value provider that will return a component for the given
     *            item
     * @return the new column
     * @param <V>
     *            the column value type, extends component
     * @since 8.1
     */
    public <V extends Component> Column<T, V> addComponentColumn(ValueProvider<T, V> componentProvider) {
        return addColumn(componentProvider, new ComponentRenderer());
    }

    /**
     * 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
     * @return a new column instance
     * @param <V>
     *            the column value type
     * @param <P>
     *            the column presentation type
     *
     * @since 8.1
     */
    protected <V, P> Column<T, V> createColumn(ValueProvider<T, V> valueProvider,
            ValueProvider<V, P> presentationProvider, AbstractRenderer<? super T, ? super P> renderer) {
        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 8.8
     */
    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;
        }

        column.extend(this);
        columnSet.add(column);
        columnKeys.put(identifier, column);
        column.setInternalId(identifier);
        addDataGenerator(column.getDataGenerator());

        getState().columnOrder.add(identifier);
        getHeader().addColumn(identifier);
        getFooter().addColumn(identifier);

        if (getDefaultHeaderRow() != null) {
            getDefaultHeaderRow().getCell(column).setText(column.getCaption());
        }

        column.updateSortable();
    }

    /**
     * Removes the given column from this {@link Grid}.
     *
     * Note: If you have Editor with binding in this Grid to this property, you need to remove that
     * using removeBinding method provided by Binder.
     *
     * @param column
     *            the column to remove
     *
     * @throws IllegalArgumentException
     *             if the column is not a valid one
     */
    public void removeColumn(Column<T, ?> column) {
        if (columnSet.remove(column)) {
            String columnId = column.getInternalId();
            int displayIndex = getState(false).columnOrder.indexOf(columnId);
            assert displayIndex != -1 : "Tried to remove a column which is not included in columnOrder. This should not be possible as all columns should be in columnOrder.";
            columnKeys.remove(columnId);
            columnIds.remove(column.getId());
            column.remove();
            removeDataGenerator(column.getDataGenerator());
            getHeader().removeColumn(columnId);
            getFooter().removeColumn(columnId);
            getState(true).columnOrder.remove(columnId);

            // Remove column from sorted columns.
            List<GridSortOrder<T>> filteredSortOrder = sortOrder.stream()
                    .filter(order -> !order.getSorted().equals(column)).collect(Collectors.toList());
            if (filteredSortOrder.size() < sortOrder.size()) {
                setSortOrder(filteredSortOrder);
            }

            if (displayIndex < getFrozenColumnCount()) {
                setFrozenColumnCount(getFrozenColumnCount() - 1);
            }
        } else {
            throw new IllegalArgumentException(
                    "Column with id " + column.getId() + " cannot be removed from the grid");
        }
    }

    /**
     * Removes the column with the given column id.
     *
     * @see #removeColumn(Column)
     * @see Column#setId(String)
     *
     * @param columnId
     *            the id of the column to remove, not <code>null</code>
     */
    public void removeColumn(String columnId) {
        removeColumn(getColumnOrThrow(columnId));
    }

    /**
     * Removes all columns from this Grid.
     *
     * @since 8.0.2
     */
    public void removeAllColumns() {
        for (Column<T, ?> column : getColumns()) {
            removeColumn(column);
        }
    }

    /**
     * Requests that the column widths should be recalculated.
     * <p>
     * In most cases Grid will know when column widths need to be recalculated
     * but this method can be used to force recalculation in situations when
     * grid does not recalculate automatically.
     *
     * @since 8.1.1
     */
    public void recalculateColumnWidths() {
        getRpcProxy(GridClientRpc.class).recalculateColumnWidths();
    }

    /**
     * Sets the details component generator.
     *
     * @param generator
     *            the generator for details components
     */
    public void setDetailsGenerator(DetailsGenerator<T> generator) {
        this.detailsManager.setDetailsGenerator(generator);
    }

    /**
     * Sets the visibility of details component for given item.
     *
     * @param item
     *            the item to show details for
     * @param visible
     *            {@code true} if details component should be visible;
     *            {@code false} if it should be hidden
     */
    public void setDetailsVisible(T item, boolean visible) {
        detailsManager.setDetailsVisible(item, visible);
    }

    /**
     * Returns the visibility of details component for given item.
     *
     * @param item
     *            the item to show details for
     *
     * @return {@code true} if details component should be visible;
     *         {@code false} if it should be hidden
     */
    public boolean isDetailsVisible(T item) {
        return detailsManager.isDetailsVisible(item);
    }

    /**
     * Gets an unmodifiable collection of all columns currently in this
     * {@link Grid}.
     *
     * @return unmodifiable collection of columns
     */
    public List<Column<T, ?>> getColumns() {
        return Collections.unmodifiableList(
                getState(false).columnOrder.stream().map(columnKeys::get).collect(Collectors.toList()));
    }

    /**
     * Gets a {@link Column} of this grid by its identifying string.
     *
     * When you use the Grid constructor with bean class, the columns are
     * initialised with columnId being the property name.
     *
     * @see Column#setId(String)
     *
     * @param columnId
     *            the identifier of the column to get
     * @return the column corresponding to the given column identifier, or
     *         <code>null</code> if there is no such column
     */
    public Column<T, ?> getColumn(String columnId) {
        return columnIds.get(columnId);
    }

    private Column<T, ?> getColumnOrThrow(String columnId) {
        Objects.requireNonNull(columnId, "Column id cannot be null");
        Column<T, ?> column = getColumn(columnId);
        if (column == null) {
            throw new IllegalStateException("There is no column with the id " + columnId);
        }
        return column;
    }

    /**
     * {@inheritDoc}
     * <p>
     * Note that the order of the returned components it not specified.
     */
    @Override
    public Iterator<Component> iterator() {
        Set<Component> componentSet = new LinkedHashSet<>(extensionComponents);
        Header header = getHeader();
        for (int i = 0; i < header.getRowCount(); ++i) {
            HeaderRow row = header.getRow(i);
            componentSet.addAll(row.getComponents());
        }
        Footer footer = getFooter();
        for (int i = 0; i < footer.getRowCount(); ++i) {
            FooterRow row = footer.getRow(i);
            componentSet.addAll(row.getComponents());
        }
        return Collections.unmodifiableSet(componentSet).iterator();
    }

    /**
     * Sets the number of frozen columns in this grid. Setting the count to 0
     * means that no data columns will be frozen, but the built-in selection
     * checkbox column will still be frozen if it's in use. Setting the count to
     * -1 will also disable the selection column.
     * <p>
     * <em>NOTE:</em> this count includes {@link Column#isHidden() hidden
     * columns} in the count.
     * <p>
     * The default value is 0.
     *
     * @param numberOfColumns
     *            the number of columns that should be frozen
     *
     * @throws IllegalArgumentException
     *             if the column count is less than -1 or greater than the
     *             number of visible columns
     */
    public void setFrozenColumnCount(int numberOfColumns) {
        if (numberOfColumns < -1 || numberOfColumns > columnSet.size()) {
            throw new IllegalArgumentException("count must be between -1 and the current number of columns ("
                    + columnSet.size() + "): " + numberOfColumns);
        }
        int currentFrozenColumnState = getState(false).frozenColumnCount;
        /*
         * we remove the current value from the state so that setting frozen
         * columns will always happen after this call. This is so that the value
         * will be set also in the widget even if it happens to seem to be the
         * same as this current value we're setting.
         */
        if (currentFrozenColumnState != numberOfColumns) {
            final String diffStateKey = "frozenColumnCount";
            UI ui = getUI();
            if (ui != null) {
                JsonObject diffState = ui.getConnectorTracker().getDiffState(Grid.this);
                // if diffState is not present, there's nothing for us to clean
                if (diffState != null) {
                    diffState.remove(diffStateKey);
                }
            }
        }
        getState().frozenColumnCount = numberOfColumns;
    }

    /**
     * Gets the number of frozen columns in this grid. 0 means that no data
     * columns will be frozen, but the built-in selection checkbox column will
     * still be frozen if it's in use. -1 means that not even the selection
     * column is frozen.
     * <p>
     * <em>NOTE:</em> this count includes {@link Column#isHidden() hidden
     * columns} in the count.
     *
     * @see #setFrozenColumnCount(int)
     *
     * @return the number of frozen columns
     */
    public int getFrozenColumnCount() {
        return getState(false).frozenColumnCount;
    }

    /**
     * Sets the number of rows that should be visible in Grid's body. This
     * method will set the height mode to be {@link HeightMode#ROW}.
     *
     * @param rows
     *            The height in terms of number of rows displayed in Grid's
     *            body. If Grid doesn't contain enough rows, white space is
     *            displayed instead.
     * @throws IllegalArgumentException
     *             if {@code rows} is zero or less
     * @throws IllegalArgumentException
     *             if {@code rows} is {@link Double#isInfinite(double) infinite}
     * @throws IllegalArgumentException
     *             if {@code rows} is {@link Double#isNaN(double) NaN}
     */
    public void setHeightByRows(double rows) {
        if (rows <= 0.0d) {
            throw new IllegalArgumentException("More than zero rows must be shown.");
        }
        if (Double.isInfinite(rows)) {
            throw new IllegalArgumentException("Grid doesn't support infinite heights");
        }
        if (Double.isNaN(rows)) {
            throw new IllegalArgumentException("NaN is not a valid row count");
        }
        getState().heightMode = HeightMode.ROW;
        getState().heightByRows = rows;
    }

    /**
     * Gets the amount of rows in Grid's body that are shown, while
     * {@link #getHeightMode()} is {@link HeightMode#ROW}.
     *
     * @return the amount of rows that are being shown in Grid's body
     * @see #setHeightByRows(double)
     */
    public double getHeightByRows() {
        return getState(false).heightByRows;
    }

    /**
     * {@inheritDoc}
     * <p>
     * <em>Note:</em> This method will set the height mode to be
     * {@link HeightMode#CSS}.
     *
     * @see #setHeightMode(HeightMode)
     */
    @Override
    public void setHeight(float height, Unit unit) {
        getState().heightMode = HeightMode.CSS;
        super.setHeight(height, unit);
    }

    /**
     * Defines the mode in which the Grid widget's height is calculated.
     * <p>
     * If {@link HeightMode#CSS} is given, Grid will respect the values given
     * via a {@code setHeight}-method, and behave as a traditional Component.
     * <p>
     * If {@link HeightMode#ROW} is given, Grid will make sure that the body
     * will display as many rows as {@link #getHeightByRows()} defines.
     * <em>Note:</em> If headers/footers are inserted or removed, the widget
     * will resize itself to still display the required amount of rows in its
     * body. It also takes the horizontal scrollbar into account.
     *
     * @param heightMode
     *            the mode in to which Grid should be set
     */
    public void setHeightMode(HeightMode heightMode) {
        /**
         * This method is a workaround for the fact that Vaadin re-applies
         * widget dimensions (height/width) on each state change event. The
         * original design was to have setHeight and setHeightByRow be equals,
         * and whichever was called the latest was considered in effect.
         *
         * But, because of Vaadin always calling setHeight on the widget, this
         * approach doesn't work.
         */

        getState().heightMode = heightMode;
    }

    /**
     * Returns the current {@link HeightMode} the Grid is in.
     * <p>
     * Defaults to {@link HeightMode#CSS}.
     *
     * @return the current HeightMode
     */
    public HeightMode getHeightMode() {
        return getState(false).heightMode;
    }

    /**
     * Sets the height of body, header and footer rows. If -1 (default), the row
     * height is calculated based on the theme for an empty row before the Grid
     * is displayed.
     * <p>
     * Note that all header, body and footer rows get the same height if
     * explicitly set. In automatic mode, each section is calculated separately
     * based on an empty row of that type.
     *
     * @see #setBodyRowHeight(double)
     * @see #setHeaderRowHeight(double)
     * @see #setFooterRowHeight(double)
     *
     * @param rowHeight
     *            The height of a row in pixels or -1 for automatic calculation
     */
    public void setRowHeight(double rowHeight) {
        setBodyRowHeight(rowHeight);
        setHeaderRowHeight(rowHeight);
        setFooterRowHeight(rowHeight);
    }

    /**
     * Sets the height of a body row. If -1 (default), the row height is
     * calculated based on the theme for an empty row before the Grid is
     * displayed.
     *
     * @param rowHeight
     *            The height of a row in pixels or -1 for automatic calculation
     * @since 8.2
     */
    public void setBodyRowHeight(double rowHeight) {
        getState().bodyRowHeight = rowHeight;
    }

    /**
     * Sets the height of a header row. If -1 (default), the row height is
     * calculated based on the theme for an empty row before the Grid is
     * displayed.
     *
     * @param rowHeight
     *            The height of a row in pixels or -1 for automatic calculation
     * @since 8.2
     */
    public void setHeaderRowHeight(double rowHeight) {
        getState().headerRowHeight = rowHeight;
    }

    /**
     * Sets the height of a footer row. If -1 (default), the row height is
     * calculated based on the theme for an empty row before the Grid is
     * displayed.
     *
     * @param rowHeight
     *            The height of a row in pixels or -1 for automatic calculation
     * @since 8.2
     */
    public void setFooterRowHeight(double rowHeight) {
        getState().footerRowHeight = rowHeight;
    }

    /**
     * Returns the current body row height.-1 if row height is in automatic
     * calculation mode.
     *
     * @see #getBodyRowHeight()
     * @see #getHeaderRowHeight()
     * @see #getFooterRowHeight()
     *
     * @return body row height
     * @deprecated replaced by three separate row height controls
     */
    @Deprecated
    public double getRowHeight() {
        return getBodyRowHeight();
    }

    /**
     * Returns the current body row height. -1 if row height is in automatic
     * calculation mode.
     *
     * @return body row height
     * @since 8.2
     */
    public double getBodyRowHeight() {
        return getState(false).bodyRowHeight;
    }

    /**
     * Returns the current header row height. -1 if row height is in automatic
     * calculation mode.
     *
     * @return header row height
     * @since 8.2
     */
    public double getHeaderRowHeight() {
        return getState(false).headerRowHeight;
    }

    /**
     * Returns the current footer row height. -1 if row height is in automatic
     * calculation mode.
     *
     * @return footer row height
     * @since 8.2
     */
    public double getFooterRowHeight() {
        return getState(false).footerRowHeight;
    }

    /**
     * Sets the style generator that is used for generating class names for rows
     * in this grid. Returning null from the generator results in no custom
     * style name being set.
     *
     * Note: The style generator is applied only to the body cells, not to the
     * Editor.
     *
     * @see StyleGenerator
     *
     * @param styleGenerator
     *            the row style generator to set, not null
     * @throws NullPointerException
     *             if {@code styleGenerator} is {@code null}
     */
    public void setStyleGenerator(StyleGenerator<T> styleGenerator) {
        Objects.requireNonNull(styleGenerator, "Style generator must not be null");
        this.styleGenerator = styleGenerator;
        getDataCommunicator().reset();
    }

    /**
     * Gets the style generator that is used for generating class names for
     * rows.
     *
     * @see StyleGenerator
     *
     * @return the row style generator
     */
    public StyleGenerator<T> getStyleGenerator() {
        return styleGenerator;
    }

    /**
     * Sets the description generator that is used for generating descriptions
     * for rows. This method uses the {@link ContentMode#PREFORMATTED} content
     * mode.
     *
     * @see #setDescriptionGenerator(DescriptionGenerator, ContentMode)
     *
     * @param descriptionGenerator
     *            the row description generator to set, or <code>null</code> to
     *            remove a previously set generator
     */
    public void setDescriptionGenerator(DescriptionGenerator<T> descriptionGenerator) {
        setDescriptionGenerator(descriptionGenerator, ContentMode.PREFORMATTED);
    }

    /**
     * Sets the description generator that is used for generating descriptions
     * for rows. This method uses the given content mode.
     *
     * @see #setDescriptionGenerator(DescriptionGenerator)
     *
     * @param descriptionGenerator
     *            the row description generator to set, or {@code null} to
     *            remove a previously set generator
     * @param contentMode
     *            the content mode for row tooltips
     *
     * @since 8.2
     */
    public void setDescriptionGenerator(DescriptionGenerator<T> descriptionGenerator, ContentMode contentMode) {
        Objects.requireNonNull(contentMode, "contentMode cannot be null");
        this.descriptionGenerator = descriptionGenerator;
        getState().rowDescriptionContentMode = contentMode;
        getDataCommunicator().reset();
    }

    /**
     * Gets the description generator that is used for generating descriptions
     * for rows.
     *
     * @return the row description generator, or <code>null</code> if no
     *         generator is set
     */
    public DescriptionGenerator<T> getDescriptionGenerator() {
        return descriptionGenerator;
    }

    //
    // HEADER AND FOOTER
    //

    /**
     * Returns the header row at the given index.
     *
     * @param index
     *            the index of the row, where the topmost row has index zero
     * @return the header row at the index
     * @throws IndexOutOfBoundsException
     *             if {@code rowIndex < 0 || rowIndex >= getHeaderRowCount()}
     */
    public HeaderRow getHeaderRow(int index) {
        return getHeader().getRow(index);
    }

    /**
     * Gets the number of rows in the header section.
     *
     * @return the number of header rows
     */
    public int getHeaderRowCount() {
        return header.getRowCount();
    }

    /**
     * Inserts a new row at the given position to the header section. Shifts the
     * row currently at that position and any subsequent rows down (adds one to
     * their indices). Inserting at {@link #getHeaderRowCount()} appends the row
     * at the bottom of the header.
     *
     * @param index
     *            the index at which to insert the row, where the topmost row
     *            has index zero
     * @return the inserted header row
     *
     * @throws IndexOutOfBoundsException
     *             if {@code rowIndex < 0 || rowIndex > getHeaderRowCount()}
     *
     * @see #appendHeaderRow()
     * @see #prependHeaderRow()
     * @see #removeHeaderRow(HeaderRow)
     * @see #removeHeaderRow(int)
     */
    public HeaderRow addHeaderRowAt(int index) {
        return getHeader().addRowAt(index);
    }

    /**
     * Adds a new row at the bottom of the header section.
     *
     * @return the appended header row
     *
     * @see #prependHeaderRow()
     * @see #addHeaderRowAt(int)
     * @see #removeHeaderRow(HeaderRow)
     * @see #removeHeaderRow(int)
     */
    public HeaderRow appendHeaderRow() {
        return addHeaderRowAt(getHeaderRowCount());
    }

    /**
     * Adds a new row at the top of the header section.
     *
     * @return the prepended header row
     *
     * @see #appendHeaderRow()
     * @see #addHeaderRowAt(int)
     * @see #removeHeaderRow(HeaderRow)
     * @see #removeHeaderRow(int)
     */
    public HeaderRow prependHeaderRow() {
        return addHeaderRowAt(0);
    }

    /**
     * Removes the given row from the header section. Removing a default row
     * sets the Grid to have no default row.
     *
     * @param row
     *            the header row to be removed, not null
     *
     * @throws IllegalArgumentException
     *             if the header does not contain the row
     *
     * @see #removeHeaderRow(int)
     * @see #addHeaderRowAt(int)
     * @see #appendHeaderRow()
     * @see #prependHeaderRow()
     */
    public void removeHeaderRow(HeaderRow row) {
        getHeader().removeRow(row);
    }

    /**
     * Removes the row at the given position from the header section.
     *
     * @param index
     *            the index of the row to remove, where the topmost row has
     *            index zero
     *
     * @throws IndexOutOfBoundsException
     *             if {@code index < 0 || index >= getHeaderRowCount()}
     *
     * @see #removeHeaderRow(HeaderRow)
     * @see #addHeaderRowAt(int)
     * @see #appendHeaderRow()
     * @see #prependHeaderRow()
     */
    public void removeHeaderRow(int index) {
        getHeader().removeRow(index);
    }

    /**
     * Sets the visibility of the Header in this Grid.
     *
     * @param headerVisible
     *            {@code true} if visible; {@code false} if not
     *
     * @since 8.1.1
     */
    public void setHeaderVisible(boolean headerVisible) {
        getHeader().setVisible(headerVisible);
    }

    /**
     * Gets the visibility of the Header in this Grid.
     *
     * @return {@code true} if visible; {@code false} if not
     *
     * @since 8.1.1
     */
    public boolean isHeaderVisible() {
        return getHeader().isVisible();
    }

    /**
     * Returns the current default row of the header.
     *
     * @return the default row or null if no default row set
     *
     * @see #setDefaultHeaderRow(HeaderRow)
     */
    public HeaderRow getDefaultHeaderRow() {
        return header.getDefaultRow();
    }

    /**
     * Sets the default row of the header. The default row is a special header
     * row that displays column captions and sort indicators. By default Grid
     * has a single row which is also the default row. When a header row is set
     * as the default row, any existing cell content is replaced by the column
     * captions.
     *
     * @param row
     *            the new default row, or null for no default row
     *
     * @throws IllegalArgumentException
     *             if the header does not contain the row
     */
    public void setDefaultHeaderRow(HeaderRow row) {
        header.setDefaultRow((Row) row);
    }

    /**
     * Returns the header section of this grid. The default header contains a
     * single row, set as the {@linkplain #setDefaultHeaderRow(HeaderRow)
     * default row}.
     *
     * @return the header section
     */
    protected Header getHeader() {
        return header;
    }

    /**
     * Returns the footer row at the given index.
     *
     * @param index
     *            the index of the row, where the topmost row has index zero
     * @return the footer row at the index
     * @throws IndexOutOfBoundsException
     *             if {@code rowIndex < 0 || rowIndex >= getFooterRowCount()}
     */
    public FooterRow getFooterRow(int index) {
        return getFooter().getRow(index);
    }

    /**
     * Gets the number of rows in the footer section.
     *
     * @return the number of footer rows
     */
    public int getFooterRowCount() {
        return getFooter().getRowCount();
    }

    /**
     * Inserts a new row at the given position to the footer section. Shifts the
     * row currently at that position and any subsequent rows down (adds one to
     * their indices). Inserting at {@link #getFooterRowCount()} appends the row
     * at the bottom of the footer.
     *
     * @param index
     *            the index at which to insert the row, where the topmost row
     *            has index zero
     * @return the inserted footer row
     *
     * @throws IndexOutOfBoundsException
     *             if {@code rowIndex < 0 || rowIndex > getFooterRowCount()}
     *
     * @see #appendFooterRow()
     * @see #prependFooterRow()
     * @see #removeFooterRow(FooterRow)
     * @see #removeFooterRow(int)
     */
    public FooterRow addFooterRowAt(int index) {
        return getFooter().addRowAt(index);
    }

    /**
     * Adds a new row at the bottom of the footer section.
     *
     * @return the appended footer row
     *
     * @see #prependFooterRow()
     * @see #addFooterRowAt(int)
     * @see #removeFooterRow(FooterRow)
     * @see #removeFooterRow(int)
     */
    public FooterRow appendFooterRow() {
        return addFooterRowAt(getFooterRowCount());
    }

    /**
     * Adds a new row at the top of the footer section.
     *
     * @return the prepended footer row
     *
     * @see #appendFooterRow()
     * @see #addFooterRowAt(int)
     * @see #removeFooterRow(FooterRow)
     * @see #removeFooterRow(int)
     */
    public FooterRow prependFooterRow() {
        return addFooterRowAt(0);
    }

    /**
     * Removes the given row from the footer section. Removing a default row
     * sets the Grid to have no default row.
     *
     * @param row
     *            the footer row to be removed, not null
     *
     * @throws IllegalArgumentException
     *             if the footer does not contain the row
     *
     * @see #removeFooterRow(int)
     * @see #addFooterRowAt(int)
     * @see #appendFooterRow()
     * @see #prependFooterRow()
     */
    public void removeFooterRow(FooterRow row) {
        getFooter().removeRow(row);
    }

    /**
     * Removes the row at the given position from the footer section.
     *
     * @param index
     *            the index of the row to remove, where the topmost row has
     *            index zero
     *
     * @throws IndexOutOfBoundsException
     *             if {@code index < 0 || index >= getFooterRowCount()}
     *
     * @see #removeFooterRow(FooterRow)
     * @see #addFooterRowAt(int)
     * @see #appendFooterRow()
     * @see #prependFooterRow()
     */
    public void removeFooterRow(int index) {
        getFooter().removeRow(index);
    }

    /**
     * Sets the visibility of the Footer in this Grid.
     *
     * @param footerVisible
     *            {@code true} if visible; {@code false} if not
     *
     * @since 8.1.1
     */
    public void setFooterVisible(boolean footerVisible) {
        getFooter().setVisible(footerVisible);
    }

    /**
     * Gets the visibility of the Footer in this Grid.
     *
     * @return {@code true} if visible; {@code false} if not
     *
     * @since 8.1.1
     */
    public boolean isFooterVisible() {
        return getFooter().isVisible();
    }

    /**
     * Returns the footer section of this grid.
     *
     * @return the footer section
     */
    protected Footer getFooter() {
        return footer;
    }

    /**
     * Registers a new column reorder listener.
     *
     * @param listener
     *            the listener to register, not null
     * @return a registration for the listener
     */
    public Registration addColumnReorderListener(ColumnReorderListener listener) {
        return addListener(ColumnReorderEvent.class, listener, COLUMN_REORDER_METHOD);
    }

    /**
     * Registers a new column resize listener.
     *
     * @param listener
     *            the listener to register, not null
     * @return a registration for the listener
     */
    public Registration addColumnResizeListener(ColumnResizeListener listener) {
        return addListener(ColumnResizeEvent.class, listener, COLUMN_RESIZE_METHOD);
    }

    /**
     * Adds an item click listener. The listener is called when an item of this
     * {@code Grid} is clicked.
     *
     * @param listener
     *            the item click listener, not null
     * @return a registration for the listener
     * @see #addContextClickListener
     */
    public Registration addItemClickListener(ItemClickListener<? super T> listener) {
        return addListener(GridConstants.ITEM_CLICK_EVENT_ID, ItemClick.class, listener, ITEM_CLICK_METHOD);
    }

    /**
     * Adds a context click listener that gets notified when a context click
     * happens.
     *
     * @param listener
     *            the context click listener to add, not null actual event
     *            provided to the listener is {@link GridContextClickEvent}
     * @return a registration object for removing the listener
     *
     * @since 8.1
     * @see #addItemClickListener
     * @see Registration
     */
    @Override
    public Registration addContextClickListener(ContextClickEvent.ContextClickListener listener) {
        return super.addContextClickListener(listener);
    }

    /**
     * Registers a new column visibility change listener.
     *
     * @param listener
     *            the listener to register, not null
     * @return a registration for the listener
     */
    public Registration addColumnVisibilityChangeListener(ColumnVisibilityChangeListener listener) {
        return addListener(ColumnVisibilityChangeEvent.class, listener, COLUMN_VISIBILITY_METHOD);
    }

    /**
     * Returns whether column reordering is allowed. Default value is
     * <code>false</code>.
     *
     * @return true if reordering is allowed
     */
    public boolean isColumnReorderingAllowed() {
        return getState(false).columnReorderingAllowed;
    }

    /**
     * Sets whether or not column reordering is allowed. Default value is
     * <code>false</code>.
     *
     * @param columnReorderingAllowed
     *            specifies whether column reordering is allowed
     */
    public void setColumnReorderingAllowed(boolean columnReorderingAllowed) {
        if (isColumnReorderingAllowed() != columnReorderingAllowed) {
            getState().columnReorderingAllowed = columnReorderingAllowed;
        }
    }

    /**
     * Sets the columns and their order based on their column ids. Columns
     * currently in this grid that are not present in the list of column ids are
     * removed. This includes any column that has no id. Similarly, any new
     * column in columns will be added to this grid. New columns can only be
     * added for a <code>Grid</code> created using {@link Grid#Grid(Class)} or
     * {@link #withPropertySet(PropertySet)}.
     *
     *
     * @param columnIds
     *            the column ids to set
     *
     * @see Column#setId(String)
     */
    public void setColumns(String... columnIds) {
        // Must extract to an explicitly typed variable because otherwise javac
        // cannot determine which overload of setColumnOrder to use
        Column<T, ?>[] newColumnOrder = Stream.of(columnIds).map((Function<String, Column<T, ?>>) id -> {
            Column<T, ?> column = getColumn(id);
            if (column == null) {
                column = addColumn(id);
            }
            return column;
        }).toArray(Column[]::new);
        setColumnOrder(newColumnOrder);

        // The columns to remove are now at the end of the column list
        getColumns().stream().skip(columnIds.length).forEach(this::removeColumn);
    }

    private String getGeneratedIdentifier() {
        String columnId = "" + counter;
        counter++;
        return columnId;
    }

    /**
     * Sets a new column order for the grid. All columns which are not ordered
     * here will remain in the order they were before as the last columns of
     * grid.
     *
     * @param columns
     *            the columns in the order they should be
     */
    public void setColumnOrder(Column<T, ?>... columns) {
        setColumnOrder(Stream.of(columns));
    }

    private void setColumnOrder(Stream<Column<T, ?>> columns) {
        List<String> columnOrder = new ArrayList<>();
        columns.forEach(column -> {
            if (columnSet.contains(column)) {
                columnOrder.add(column.getInternalId());
            } else {
                throw new IllegalStateException(
                        "setColumnOrder should not be called " + "with columns that are not in the grid.");
            }
        });

        List<String> stateColumnOrder = getState().columnOrder;
        if (stateColumnOrder.size() != columnOrder.size()) {
            stateColumnOrder.removeAll(columnOrder);
            columnOrder.addAll(stateColumnOrder);
        }

        getState().columnOrder = columnOrder;
        fireColumnReorderEvent(false);
    }

    /**
     * Sets a new column order for the grid based on their column ids. All
     * columns which are not ordered here will remain in the order they were
     * before as the last columns of grid.
     *
     * @param columnIds
     *            the column ids in the order they should be
     *
     * @see Column#setId(String)
     */
    public void setColumnOrder(String... columnIds) {
        setColumnOrder(Stream.of(columnIds).map(this::getColumnOrThrow));
    }

    /**
     * Returns the selection model for this grid.
     *
     * @return the selection model, not null
     */
    public GridSelectionModel<T> getSelectionModel() {
        assert selectionModel != null : "No selection model set by " + getClass().getName() + " constructor";
        return selectionModel;
    }

    /**
     * Use this grid as a single select in {@link Binder}.
     * <p>
     * Throws {@link IllegalStateException} if the grid is not using a
     * {@link SingleSelectionModel}.
     *
     * @return the single select wrapper that can be used in binder
     * @throws IllegalStateException
     *             if not using a single selection model
     */
    public GridSingleSelect<T> asSingleSelect() {
        return new GridSingleSelect<>(this);
    }

    public Editor<T> getEditor() {
        return editor;
    }

    /**
     * User this grid as a multiselect in {@link Binder}.
     * <p>
     * Throws {@link IllegalStateException} if the grid is not using a
     * {@link MultiSelectionModel}.
     *
     * @return the multiselect wrapper that can be used in binder
     * @throws IllegalStateException
     *             if not using a multiselection model
     */
    public GridMultiSelect<T> asMultiSelect() {
        return new GridMultiSelect<>(this);
    }

    /**
     * Sets the selection model for the grid.
     * <p>
     * This method is for setting a custom selection model, and is
     * {@code protected} because {@link #setSelectionMode(SelectionMode)} should
     * be used for easy switching between built-in selection models.
     * <p>
     * The default selection model is {@link SingleSelectionModelImpl}.
     * <p>
     * To use a custom selection model, you can e.g. extend the grid call this
     * method with your custom selection model.
     *
     * @param model
     *            the selection model to use, not {@code null}
     *
     * @see #setSelectionMode(SelectionMode)
     */
    @SuppressWarnings("unchecked")
    protected void setSelectionModel(GridSelectionModel<T> model) {
        Objects.requireNonNull(model, "selection model cannot be null");
        if (selectionModel != null) { // null when called from constructor
            selectionModel.remove();
        }

        selectionModel = model;

        if (selectionModel instanceof AbstractListingExtension) {
            ((AbstractListingExtension<T>) selectionModel).extend(this);
        } else {
            addExtension(selectionModel);
        }
    }

    /**
     * Sets the grid's selection mode.
     * <p>
     * The built-in selection models are:
     * <ul>
     * <li>{@link SelectionMode#SINGLE} -> {@link SingleSelectionModelImpl},
     * <b>the default model</b></li>
     * <li>{@link SelectionMode#MULTI} -> {@link MultiSelectionModelImpl}, with
     * checkboxes in the first column for selection</li>
     * <li>{@link SelectionMode#NONE} -> {@link NoSelectionModel}, preventing
     * selection</li>
     * </ul>
     * <p>
     * To use your custom selection model, you can use
     * {@link #setSelectionModel(GridSelectionModel)}, see existing selection
     * model implementations for example.
     *
     * @param selectionMode
     *            the selection mode to switch to, not {@code null}
     * @return the used selection model
     *
     * @see SelectionMode
     * @see GridSelectionModel
     * @see #setSelectionModel(GridSelectionModel)
     */
    public GridSelectionModel<T> setSelectionMode(SelectionMode selectionMode) {
        Objects.requireNonNull(selectionMode, "Selection mode cannot be null.");
        GridSelectionModel<T> model = selectionMode.createModel();
        setSelectionModel(model);

        return model;
    }

    /**
     * This method is a shorthand that delegates to the currently set selection
     * model.
     *
     * @see #getSelectionModel()
     * @see GridSelectionModel
     */
    public Set<T> getSelectedItems() {
        return getSelectionModel().getSelectedItems();
    }

    /**
     * This method is a shorthand that delegates to the currently set selection
     * model.
     *
     * @see #getSelectionModel()
     * @see GridSelectionModel
     */
    public void select(T item) {
        getSelectionModel().select(item);
    }

    /**
     * This method is a shorthand that delegates to the currently set selection
     * model.
     *
     * @see #getSelectionModel()
     * @see GridSelectionModel
     */
    public void deselect(T item) {
        getSelectionModel().deselect(item);
    }

    /**
     * This method is a shorthand that delegates to the currently set selection
     * model.
     *
     * @see #getSelectionModel()
     * @see GridSelectionModel
     */
    public void deselectAll() {
        getSelectionModel().deselectAll();
    }

    /**
     * Adds a selection listener to the current selection model.
     * <p>
     * <em>NOTE:</em> If selection mode is switched with
     * {@link #setSelectionMode(SelectionMode)}, then this listener is not
     * triggered anymore when selection changes!
     * <p>
     * This is a shorthand for
     * {@code grid.getSelectionModel().addSelectionListener()}. To get more
     * detailed selection events, use {@link #getSelectionModel()} and either
     * {@link SingleSelectionModel#addSingleSelectionListener(SingleSelectionListener)}
     * or
     * {@link MultiSelectionModel#addMultiSelectionListener(MultiSelectionListener)}
     * depending on the used selection mode.
     *
     * @param listener
     *            the listener to add
     * @return a registration handle to remove the listener
     * @throws UnsupportedOperationException
     *             if selection has been disabled with
     *             {@link SelectionMode#NONE}
     */
    public Registration addSelectionListener(SelectionListener<T> listener) throws UnsupportedOperationException {
        return getSelectionModel().addSelectionListener(listener);
    }

    /**
     * Sort this Grid in ascending order by a specified column.
     *
     * @param column
     *            a column to sort against
     *
     */
    public void sort(Column<T, ?> column) {
        sort(column, SortDirection.ASCENDING);
    }

    /**
     * Sort this Grid in user-specified direction by a column.
     *
     * @param column
     *            a column to sort against
     * @param direction
     *            a sort order value (ascending/descending)
     *
     */
    public void sort(Column<T, ?> column, SortDirection direction) {
        setSortOrder(Collections.singletonList(new GridSortOrder<>(column, direction)));
    }

    /**
     * Sort this Grid in ascending order by a specified column defined by id.
     *
     * @param columnId
     *            the id of the column to sort against
     *
     * @see Column#setId(String)
     */
    public void sort(String columnId) {
        sort(columnId, SortDirection.ASCENDING);
    }

    /**
     * Sort this Grid in a user-specified direction by a column defined by id.
     *
     * @param columnId
     *            the id of the column to sort against
     * @param direction
     *            a sort order value (ascending/descending)
     *
     * @see Column#setId(String)
     */
    public void sort(String columnId, SortDirection direction) {
        sort(getColumnOrThrow(columnId), direction);
    }

    /**
     * Clear the current sort order, and re-sort the grid.
     */
    public void clearSortOrder() {
        setSortOrder(Collections.emptyList());
    }

    /**
     * Sets the sort order to use.
     *
     * @param order
     *            a sort order list.
     *
     * @throws IllegalArgumentException
     *             if order is null
     */
    public void setSortOrder(List<GridSortOrder<T>> order) {
        setSortOrder(order, false);
    }

    /**
     * Sets the sort order to use, given a {@link GridSortOrderBuilder}.
     * Shorthand for {@code setSortOrder(builder.build())}.
     *
     * @see GridSortOrderBuilder
     *
     * @param builder
     *            the sort builder to retrieve the sort order from
     * @throws NullPointerException
     *             if builder is null
     */
    public void setSortOrder(GridSortOrderBuilder<T> builder) {
        Objects.requireNonNull(builder, "Sort builder cannot be null");
        setSortOrder(builder.build());
    }

    /**
     * Adds a sort order change listener that gets notified when the sort order
     * changes.
     *
     * @param listener
     *            the sort order change listener to add
     */
    @Override
    public Registration addSortListener(SortListener<GridSortOrder<T>> listener) {
        return addListener(SortEvent.class, listener, SORT_ORDER_CHANGE_METHOD);
    }

    /**
     * Get the current sort order list.
     *
     * @return a sort order list
     */
    public List<GridSortOrder<T>> getSortOrder() {
        return Collections.unmodifiableList(sortOrder);
    }

    /**
     * Scrolls to a certain item, using {@link ScrollDestination#ANY}.
     * <p>
     * If the item has an open details row, its size will also be taken into
     * account.
     *
     * @param row
     *            zero based index of the item to scroll to in the current view.
     * @throws IllegalArgumentException
     *             if the provided row is outside the item range
     */
    public void scrollTo(int row) throws IllegalArgumentException {
        scrollTo(row, ScrollDestination.ANY);
    }

    /**
     * Scrolls to a certain item, using user-specified scroll destination.
     * <p>
     * If the item has an open details row, its size will also be taken into
     * account.
     *
     * @param row
     *            zero based index of the item to scroll to in the current view.
     * @param destination
     *            value specifying desired position of scrolled-to row, not
     *            {@code null}
     * @throws IllegalArgumentException
     *             if the provided row is outside the item range
     */
    public void scrollTo(int row, ScrollDestination destination) {
        Objects.requireNonNull(destination, "ScrollDestination can not be null");

        if (row >= getDataCommunicator().getDataProviderSize()) {
            throw new IllegalArgumentException("Row outside dataProvider size");
        }

        getRpcProxy(GridClientRpc.class).scrollToRow(row, destination);
    }

    /**
     * Scrolls to the beginning of the first data row.
     */
    public void scrollToStart() {
        getRpcProxy(GridClientRpc.class).scrollToStart();
    }

    /**
     * Scrolls to the end of the last data row.
     */
    public void scrollToEnd() {
        getRpcProxy(GridClientRpc.class).scrollToEnd();
    }

    @Override
    protected GridState getState() {
        return getState(true);
    }

    @Override
    protected GridState getState(boolean markAsDirty) {
        return (GridState) super.getState(markAsDirty);
    }

    /**
     * Sets the column resize mode to use. The default mode is
     * {@link ColumnResizeMode#ANIMATED}.
     *
     * @param mode
     *            a ColumnResizeMode value
     * @since 7.7.5
     */
    public void setColumnResizeMode(ColumnResizeMode mode) {
        getState().columnResizeMode = mode;
    }

    /**
     * Returns the current column resize mode. The default mode is
     * {@link ColumnResizeMode#ANIMATED}.
     *
     * @return a ColumnResizeMode value
     * @since 7.7.5
     */
    public ColumnResizeMode getColumnResizeMode() {
        return getState(false).columnResizeMode;
    }

    /**
     * Creates a new Editor instance. Can be overridden to create a custom
     * Editor. If the Editor is a {@link AbstractGridExtension}, it will be
     * automatically added to {@link DataCommunicator}.
     *
     * @return editor
     */
    protected Editor<T> createEditor() {
        return new EditorImpl<>(propertySet);
    }

    private void addExtensionComponent(Component c) {
        if (extensionComponents.add(c)) {
            c.setParent(this);
            markAsDirty();
        }
    }

    private void removeExtensionComponent(Component c) {
        if (extensionComponents.remove(c)) {
            c.setParent(null);
            markAsDirty();
        }
    }

    private void fireColumnReorderEvent(boolean userOriginated) {
        fireEvent(new ColumnReorderEvent(this, userOriginated));
    }

    private void fireColumnResizeEvent(Column<?, ?> column, boolean userOriginated) {
        fireEvent(new ColumnResizeEvent(this, column, userOriginated));
    }

    @Override
    protected void readItems(Element design, DesignContext context) {
        // Grid handles reading of items in Grid#readData
    }

    @Override
    public DataProvider<T, ?> getDataProvider() {
        return internalGetDataProvider();
    }

    @Override
    public void setDataProvider(DataProvider<T, ?> dataProvider) {
        internalSetDataProvider(dataProvider);
    }

    /**
     * Sets a CallbackDataProvider using the given fetch items callback and a
     * size callback.
     * <p>
     * This method is a shorthand for making a {@link CallbackDataProvider} that
     * handles a partial {@link Query} object.
     *
     * @param fetchItems
     *            a callback for fetching items
     * @param sizeCallback
     *            a callback for getting the count of items
     *
     * @see CallbackDataProvider
     * @see #setDataProvider(DataProvider)
     */
    public void setDataProvider(FetchItemsCallback<T> fetchItems, SerializableSupplier<Integer> sizeCallback) {
        internalSetDataProvider(new CallbackDataProvider<>(
                q -> fetchItems.fetchItems(q.getSortOrders(), q.getOffset(), q.getLimit()),
                q -> sizeCallback.get()));
    }

    @Override
    protected void doReadDesign(Element design, DesignContext context) {
        Attributes attrs = design.attributes();
        if (design.hasAttr(DECLARATIVE_DATA_ITEM_TYPE)) {
            String itemType = design.attr(DECLARATIVE_DATA_ITEM_TYPE);
            setBeanType(itemType);
        }

        if (attrs.hasKey("selection-mode")) {
            setSelectionMode(DesignAttributeHandler.readAttribute("selection-mode", attrs, SelectionMode.class));
        }
        Attributes attr = design.attributes();
        if (attr.hasKey("selection-allowed")) {
            setReadOnly(DesignAttributeHandler.readAttribute("selection-allowed", attr, Boolean.class));
        }

        if (attrs.hasKey("rows")) {
            setHeightByRows(DesignAttributeHandler.readAttribute("rows", attrs, double.class));
        }

        readStructure(design, context);

        // Read frozen columns after columns are read.
        if (attrs.hasKey("frozen-columns")) {
            setFrozenColumnCount(DesignAttributeHandler.readAttribute("frozen-columns", attrs, int.class));
        }
    }

    /**
     * Sets the bean type to use for property mapping.
     * <p>
     * This method is responsible also for setting or updating the property set
     * so that it matches the given bean type.
     * <p>
     * Protected mostly for Designer needs, typically should not be overridden
     * or even called.
     *
     * @param beanTypeClassName
     *            the fully qualified class name of the bean type
     *
     * @since 8.0.3
     */
    @SuppressWarnings("unchecked")
    protected void setBeanType(String beanTypeClassName) {
        setBeanType((Class<T>) resolveClass(beanTypeClassName));
    }

    /**
     * Sets the bean type to use for property mapping.
     * <p>
     * This method is responsible also for setting or updating the property set
     * so that it matches the given bean type.
     * <p>
     * Protected mostly for Designer needs, typically should not be overridden
     * or even called.
     *
     * @param beanType
     *            the bean type class
     *
     * @since 8.0.3
     */
    protected void setBeanType(Class<T> beanType) {
        this.beanType = beanType;
        setPropertySet(BeanPropertySet.get(beanType));
    }

    private Class<?> resolveClass(String qualifiedClassName) {
        try {
            Class<?> resolvedClass = Class.forName(qualifiedClassName, true,
                    VaadinServiceClassLoaderUtil.findDefaultClassLoader());
            return resolvedClass;
        } catch (ClassNotFoundException | SecurityException e) {
            throw new IllegalArgumentException("Unable to find class " + qualifiedClassName, e);
        }

    }

    @Override
    protected void doWriteDesign(Element design, DesignContext designContext) {
        Attributes attr = design.attributes();
        if (this.beanType != null) {
            design.attr(DECLARATIVE_DATA_ITEM_TYPE, this.beanType.getCanonicalName());
        }
        DesignAttributeHandler.writeAttribute("selection-allowed", attr, isReadOnly(), false, Boolean.class,
                designContext);

        Attributes attrs = design.attributes();
        Grid<?> defaultInstance = designContext.getDefaultInstance(this);

        DesignAttributeHandler.writeAttribute("frozen-columns", attrs, getFrozenColumnCount(),
                defaultInstance.getFrozenColumnCount(), int.class, designContext);

        if (HeightMode.ROW.equals(getHeightMode())) {
            DesignAttributeHandler.writeAttribute("rows", attrs, getHeightByRows(),
                    defaultInstance.getHeightByRows(), double.class, designContext);
        }

        SelectionMode mode = getSelectionMode();

        if (mode != null) {
            DesignAttributeHandler.writeAttribute("selection-mode", attrs, mode, SelectionMode.SINGLE,
                    SelectionMode.class, designContext);
        }

        writeStructure(design, designContext);
    }

    @Override
    protected T deserializeDeclarativeRepresentation(String item) {
        if (item == null) {
            return super.deserializeDeclarativeRepresentation(UUID.randomUUID().toString());
        }
        return super.deserializeDeclarativeRepresentation(new String(item));
    }

    @Override
    protected boolean isReadOnly() {
        SelectionMode selectionMode = getSelectionMode();
        if (SelectionMode.SINGLE.equals(selectionMode)) {
            return asSingleSelect().isReadOnly();
        }
        if (SelectionMode.MULTI.equals(selectionMode)) {
            return asMultiSelect().isReadOnly();
        }
        return false;
    }

    @Override
    protected void setReadOnly(boolean readOnly) {
        SelectionMode selectionMode = getSelectionMode();
        if (SelectionMode.SINGLE.equals(selectionMode)) {
            asSingleSelect().setReadOnly(readOnly);
        } else if (SelectionMode.MULTI.equals(selectionMode)) {
            asMultiSelect().setReadOnly(readOnly);
        }
    }

    private void readStructure(Element design, DesignContext context) {
        if (design.children().isEmpty()) {
            return;
        }
        if (design.children().size() > 1 || !design.child(0).tagName().equals("table")) {
            throw new DesignException("Grid needs to have a table element as its only child");
        }
        Element table = design.child(0);

        Elements colgroups = table.getElementsByTag("colgroup");
        if (colgroups.size() != 1) {
            throw new DesignException("Table element in declarative Grid needs to have a"
                    + " colgroup defining the columns used in Grid");
        }

        List<DeclarativeValueProvider<T>> providers = new ArrayList<>();
        for (Element col : colgroups.get(0).getElementsByTag("col")) {
            String id = DesignAttributeHandler.readAttribute("column-id", col.attributes(), null, String.class);

            // If there is a property with a matching name available,
            // map to that
            Optional<PropertyDefinition<T, ?>> property = propertySet.getProperties()
                    .filter(p -> p.getName().equals(id)).findFirst();
            Column<T, ?> column;
            if (property.isPresent()) {
                column = addColumn(id);
            } else {
                DeclarativeValueProvider<T> provider = new DeclarativeValueProvider<>();
                column = createColumn(provider, ValueProvider.identity(), new HtmlRenderer());
                addColumn(getGeneratedIdentifier(), column);
                if (id != null) {
                    column.setId(id);
                }
                providers.add(provider);
            }
            column.readDesign(col, context);
        }

        for (Element child : table.children()) {
            if (child.tagName().equals("thead")) {
                getHeader().readDesign(child, context);
            } else if (child.tagName().equals("tbody")) {
                readData(child, providers);
            } else if (child.tagName().equals("tfoot")) {
                getFooter().readDesign(child, context);
            }
        }

        // Sync default header captions to column captions
        if (getDefaultHeaderRow() != null) {
            for (Column<T, ?> c : getColumns()) {
                HeaderCell headerCell = getDefaultHeaderRow().getCell(c);
                if (headerCell.getCellType() == GridStaticCellType.TEXT) {
                    c.setCaption(headerCell.getText());
                }
            }
        }
    }

    /**
     * Reads the declarative representation of a grid's data from the given
     * element and stores it in the given {@link DeclarativeValueProvider}s.
     * Each member in the list of value providers corresponds to a column in the
     * grid.
     *
     * @since 8.1
     *
     * @param body
     *            the element to read data from
     * @param providers
     *            list of {@link DeclarativeValueProvider}s to store the data of
     *            each column to
     */
    protected void readData(Element body, List<DeclarativeValueProvider<T>> providers) {
        getSelectionModel().deselectAll();
        List<T> items = new ArrayList<>();
        List<T> selectedItems = new ArrayList<>();
        for (Element row : body.children()) {
            T item = deserializeDeclarativeRepresentation(row.attr("item"));
            items.add(item);
            if (row.hasAttr("selected")) {
                selectedItems.add(item);
            }
            Elements cells = row.children();
            int i = 0;
            for (Element cell : cells) {
                providers.get(i).addValue(item, cell.html());
                i++;
            }
        }

        setItems(items);
        selectedItems.forEach(getSelectionModel()::select);
    }

    private void writeStructure(Element design, DesignContext designContext) {
        if (getColumns().isEmpty()) {
            return;
        }
        Element tableElement = design.appendElement("table");
        Element colGroup = tableElement.appendElement("colgroup");

        getColumns().forEach(column -> column.writeDesign(colGroup.appendElement("col"), designContext));

        // Always write thead. Reads correctly when there no header rows
        getHeader().writeDesign(tableElement.appendElement("thead"), designContext);

        if (designContext.shouldWriteData(this)) {
            Element bodyElement = tableElement.appendElement("tbody");
            writeData(bodyElement, designContext);
        }

        if (getFooter().getRowCount() > 0) {
            getFooter().writeDesign(tableElement.appendElement("tfoot"), designContext);
        }
    }

    /**
     * Writes the data contained in this grid. Used when serializing a grid to
     * its declarative representation, if
     * {@link DesignContext#shouldWriteData(Component)} returns {@code true} for
     * the grid that is being written.
     *
     * @since 8.1
     *
     * @param body
     *            the body element to write the declarative representation of
     *            data to
     * @param designContext
     *            the design context
     *
     * @since 8.1
     */
    protected void writeData(Element body, DesignContext designContext) {
        getDataProvider().fetch(new Query<>()).forEach(item -> writeRow(body, item, designContext));
    }

    private void writeRow(Element container, T item, DesignContext context) {
        Element tableRow = container.appendElement("tr");
        tableRow.attr("item", serializeDeclarativeRepresentation(item));
        if (getSelectionModel().isSelected(item)) {
            tableRow.attr("selected", true);
        }
        for (Column<T, ?> column : getColumns()) {
            Object value = column.valueProvider.apply(item);
            tableRow.appendElement("td").append(Optional.ofNullable(value).map(Object::toString)
                    .map(DesignFormatter::encodeForTextNode).orElse(""));
        }
    }

    private SelectionMode getSelectionMode() {
        GridSelectionModel<T> selectionModel = getSelectionModel();
        SelectionMode mode = null;
        if (selectionModel.getClass().equals(SingleSelectionModelImpl.class)) {
            mode = SelectionMode.SINGLE;
        } else if (selectionModel.getClass().equals(MultiSelectionModelImpl.class)) {
            mode = SelectionMode.MULTI;
        } else if (selectionModel.getClass().equals(NoSelectionModel.class)) {
            mode = SelectionMode.NONE;
        }
        return mode;
    }

    /**
     * Sets a user-defined identifier for given column.
     *
     * @see Column#setId(String)
     *
     * @param column
     *            the column
     * @param id
     *            the user-defined identifier
     */
    protected void setColumnId(String id, Column<T, ?> column) {
        if (columnIds.containsKey(id)) {
            throw new IllegalArgumentException("Duplicate ID for columns");
        }
        columnIds.put(id, column);
    }

    @Override
    protected Collection<String> getCustomAttributes() {
        Collection<String> result = super.getCustomAttributes();
        // "rename" for frozen column count
        result.add("frozen-column-count");
        result.add("frozen-columns");
        // "rename" for height-mode
        result.add("height-by-rows");
        result.add("rows");
        // add a selection-mode attribute
        result.add("selection-mode");

        return result;
    }

    /**
     * Returns a column identified by its internal id. This id should not be
     * confused with the user-defined identifier.
     *
     * @param columnId
     *            the internal id of column
     * @return column identified by internal id
     */
    protected Column<T, ?> getColumnByInternalId(String columnId) {
        return columnKeys.get(columnId);
    }

    /**
     * Returns the internal id for given column. This id should not be confused
     * with the user-defined identifier.
     *
     * @param column
     *            the column
     * @return internal id of given column
     */
    protected String getInternalIdForColumn(Column<T, ?> column) {
        return column.getInternalId();
    }

    private void setSortOrder(List<GridSortOrder<T>> order, boolean userOriginated) {
        Objects.requireNonNull(order, "Sort order list cannot be null");

        // Update client state to display sort order.
        List<String> sortColumns = new ArrayList<>();
        List<SortDirection> directions = new ArrayList<>();
        order.stream().forEach(sortOrder -> {
            sortColumns.add(sortOrder.getSorted().getInternalId());
            directions.add(sortOrder.getDirection());
        });

        getState().sortColumns = sortColumns.toArray(new String[0]);
        getState().sortDirs = directions.toArray(new SortDirection[0]);

        sortOrder.clear();
        sortOrder.addAll(order);
        sort(userOriginated);
    }

    private void sort(boolean userOriginated) {
        // Set sort orders
        // In-memory comparator
        getDataCommunicator().setInMemorySorting(createSortingComparator(), false);

        // Back-end sort properties
        List<QuerySortOrder> sortProperties = new ArrayList<>();
        sortOrder.stream().map(order -> order.getSorted().getSortOrder(order.getDirection()))
                .forEach(s -> s.forEach(sortProperties::add));
        getDataCommunicator().setBackEndSorting(sortProperties, true);

        // Close grid editor if it's open.
        if (getEditor().isOpen()) {
            getEditor().cancel();
        }
        fireEvent(new SortEvent<>(this, new ArrayList<>(sortOrder), userOriginated));
    }

    /**
     * Creates a comparator for grid to sort rows.
     *
     * @return the comparator based on column sorting information.
     */
    protected SerializableComparator<T> createSortingComparator() {
        /*
         * thenComparing is defined to return a serializable comparator as long
         * as both original comparators are also serializable
         */
        BinaryOperator<SerializableComparator<T>> operator = (comparator1,
                comparator2) -> comparator1.thenComparing(comparator2)::compare;
        return sortOrder.stream().map(order -> order.getSorted().getComparator(order.getDirection()))
                .reduce((x, y) -> 0, operator);
    }

    @Override
    protected void internalSetDataProvider(DataProvider<T, ?> dataProvider) {
        super.internalSetDataProvider(dataProvider);
        for (Column<T, ?> column : getColumns()) {
            column.updateSortable();
        }
    }
}