private boolean resolvePendingState(JsArrayInteger modifiedRows) {
        pendingStateCommand = null;

        // We are already resolving state. New changes will be flushed after the
        // current flush is finished.
        if (isResolvingState) {
            return false;
        }
        isResolvingState = true;

        // Early exit if there is no pending state.
        if (pendingState == null) {
            isResolvingState = false;
            pendingStateLoop = 0;
            return false;
        }

        // Check for an infinite loop. This can happen if user code accessed in
        // this method modifies the pending state and flushes changes.
        pendingStateLoop++;
        if (pendingStateLoop > LOOP_MAXIMUM) {
            isResolvingState = false;
            pendingStateLoop = 0; // Let user code handle exception and try
            // again.
            throw new IllegalStateException("A possible infinite loop has been detected in a Cell Widget. This "
                    + "usually happens when your SelectionModel triggers a "
                    + "SelectionChangeEvent when SelectionModel.isSelection() is "
                    + "called, which causes the table to redraw continuously.");
        }

        // Swap the states in case user code triggers more changes, which will
        // create a new pendingState.
        final State<T> oldState = state;
        final PendingState<T> newState = pendingState;
        state = pendingState;
        pendingState = null;

        // Keep track of the absolute indexes of modified rows.
        //
        // Use a native array to avoid dynamic casts associated with emulated
        // Java Collections.
        if (modifiedRows == null) {
            modifiedRows = JavaScriptObject.createArray().cast();
        }

        // Get the values used for calculations.
        final int pageStart = newState.getPageStart();
        final int pageSize = newState.getPageSize();
        final int pageEnd = pageStart + pageSize;
        final int rowDataCount = newState.getRowDataSize();

        // Resolve keyboard selection. If the row value still exists, use its
        // index. If the row value exists in multiple places, use the closest
        // index. If the row value no longer exists, use the current index.
        newState.keyboardSelectedRow = Math.max(0, Math.min(newState.keyboardSelectedRow, rowDataCount - 1));
        if (KeyboardSelectionPolicy.DISABLED == keyboardSelectionPolicy) {
            // Clear the keyboard selected state.
            newState.keyboardSelectedRow = 0;
            newState.keyboardSelectedRowValue = null;
        } else if (newState.keyboardSelectedRowChanged) {
            // Choose the row value based on the index.
            newState.keyboardSelectedRowValue = rowDataCount > 0
                    ? newState.getRowDataValue(newState.keyboardSelectedRow)
                    : null;
        } else if (newState.keyboardSelectedRowValue != null) {
            // Choose the index based on the row value.
            final int bestMatchIndex = findIndexOfBestMatch(newState, newState.keyboardSelectedRowValue,
                    newState.keyboardSelectedRow);
            if (bestMatchIndex >= 0) {
                // A match was found.
                newState.keyboardSelectedRow = bestMatchIndex;
                newState.keyboardSelectedRowValue = rowDataCount > 0
                        ? newState.getRowDataValue(newState.keyboardSelectedRow)
                        : null;
            } else {
                // No match was found, so reset to 0.
                newState.keyboardSelectedRow = 0;
                newState.keyboardSelectedRowValue = null;
            }
        }

        // Update the SelectionModel based on the keyboard selected value. We
        // only bind to selection after the user has interacted with the widget
        // at least once. This prevents values from being selected by default.
        try {
            if (KeyboardSelectionPolicy.BOUND_TO_SELECTION == keyboardSelectionPolicy && selectionModel != null
                    && newState.viewTouched) {
                final T oldValue = oldState.getSelectedValue();
                final Object oldKey = getRowValueKey(oldValue);
                final T newValue = rowDataCount > 0
                        ? newState.getRowDataValue(newState.getKeyboardSelectedRow())
                        : null;
                final Object newKey = getRowValueKey(newValue);
                // Do not deselect the old value unless we have a new value to
                // select, or we will have a null selection event while we wait*
                // for asynchronous data to load.
                if (newKey != null) {
                    // Check both values for selection before setting selection,
                    // or the
                    // selection model may resolve state early.
                    final boolean oldValueWasSelected = oldValue != null && selectionModel.isSelected(oldValue);
                    final boolean newValueWasSelected = newValue != null && selectionModel.isSelected(newValue);

                    if (!newKey.equals(oldKey)) {
                        // Deselect the old value.
                        if (oldValueWasSelected) {
                            selectionModel.setSelected(oldValue, false);
                        }

                        // Select the new value.
                        newState.selectedValue = newValue;
                        if (newValue != null && !newValueWasSelected) {
                            selectionModel.setSelected(newValue, true);
                        }
                    } else if (!newValueWasSelected) {
                        // The value was programmatically deselected.
                        newState.selectedValue = null;
                    }
                }
            }
        } catch (final RuntimeException e) {
            // Unlock the rendering loop if the user SelectionModel throw an
            // error.
            isResolvingState = false;
            pendingStateLoop = 0;
            throw e;
        }

        // If the keyboard row changes, add it to the modified set.
        final boolean keyboardRowChanged = newState.keyboardSelectedRowChanged
                || (oldState.getKeyboardSelectedRow() != newState.keyboardSelectedRow)
                || (oldState.getKeyboardSelectedRowValue() == null && newState.keyboardSelectedRowValue != null);

        // Resolve selection. Check the selection status of all row values in
        // the pending state and compare them to the status in the old state. If
        // we know longer have a SelectionModel but had selected rows, we still
        // need to update the rows.
        final Set<Integer> newlySelectedRows = new HashSet<>();
        try {
            for (int i = pageStart; i < pageStart + rowDataCount; i++) {
                // Check the new selection state.
                final T rowValue = newState.getRowDataValue(i - pageStart);

                // ----------------------------------------------------------------------------------------
                // ADDED CODE HERE TO DEAL WITH THE FACT THAT TICKBOX SELECTION DOES NOT HAVE BINARY STATE
                //
                // START OF INSERTED CODE
                // ----------------------------------------------------------------------------------------
                if (rowValue != null && selectionModel != null && selectionModel instanceof HasSelection) {
                    @SuppressWarnings("unchecked") final HasSelection<T> hasSelection =
                            (HasSelection<T>) selectionModel;
                    final boolean selectionChanged = hasSelection.hasSelectionChanged(rowValue);
                    if (selectionChanged) {
                        modifiedRows.push(i);
                    }
                } else {
                    // ------------------------------------------------------------------------------------
                    // END OF INSERTED CODE
                    // ------------------------------------------------------------------------------------
                    final boolean isSelected = (rowValue != null && selectionModel != null
                            && selectionModel.isSelected(rowValue));

                    // Compare to the old selection state.
                    final boolean wasSelected = oldState.isRowSelected(i);
                    if (isSelected) {
                        newState.selectedRows.add(i);
                        newlySelectedRows.add(i);
                        if (!wasSelected) {
                            modifiedRows.push(i);
                        }
                    } else if (wasSelected) {
                        modifiedRows.push(i);
                    }

                }

            }
        } catch (final RuntimeException e) {
            // Unlock the rendering loop if the user SelectionModel throw an
            // error.
            isResolvingState = false;
            pendingStateLoop = 0;
            throw e;
        }

        // Add the replaced ranges as modified rows.
        boolean replacedEmptyRange = false;
        for (final Range replacedRange : newState.replacedRanges) {
            final int start = replacedRange.getStart();
            final int length = replacedRange.getLength();
            // If the user set an empty range, pass it through to the view.
            if (length == 0) {
                replacedEmptyRange = true;
            }
            for (int i = start; i < start + length; i++) {
                modifiedRows.push(i);
            }
        }

        // Add keyboard rows to modified rows if we are going to render anyway.
        if (modifiedRows.length() > 0 && keyboardRowChanged) {
            modifiedRows.push(oldState.getKeyboardSelectedRow());
            modifiedRows.push(newState.keyboardSelectedRow);
        }

        // We called methods in user code that could modify the view, so early
        // exit if there is a new pending state waiting to be resolved.
        if (pendingState != null) {
            isResolvingState = false;
            // Do not reset pendingStateLoop, or we will not detect the infinite
            // loop.

            // Propagate modifications to the temporary pending state into the
            // new
            // pending state instance.
            pendingState.selectedValue = newState.selectedValue;
            pendingState.selectedRows.addAll(newlySelectedRows);
            if (keyboardRowChanged) {
                pendingState.keyboardSelectedRowChanged = true;
            }
            if (newState.keyboardStealFocus) {
                pendingState.keyboardStealFocus = true;
            }

            // Add the keyboard selected rows to the modified rows so they can
            // be re-rendered in the new state. These rows may already be added,
            // but modifiedRows can contain duplicates.
            modifiedRows.push(oldState.getKeyboardSelectedRow());
            modifiedRows.push(newState.keyboardSelectedRow);

            // Make a recursive call to resolve the state again, using the new
            // pending state that was just created. If we are successful, then
            // the modified rows will be redrawn. If we are not successful, then
            // we still need to redraw the modified rows.
            if (resolvePendingState(modifiedRows)) {
                return true;
            }
        }

        // Calculate the modified ranges.
        final List<Range> modifiedRanges = calculateModifiedRanges(modifiedRows, pageStart, pageEnd);
        final Range range0 = modifiedRanges.size() > 0
                ? modifiedRanges.get(0)
                : null;
        final Range range1 = modifiedRanges.size() > 1
                ? modifiedRanges.get(1)
                : null;
        int replaceDiff = 0; // The total number of rows to replace.
        for (final Range range : modifiedRanges) {
            replaceDiff += range.getLength();
        }

        // Check the various conditions that require redraw.
        final int oldPageStart = oldState.getPageStart();
        final int oldPageSize = oldState.getPageSize();
        final int oldRowDataCount = oldState.getRowDataSize();
        boolean redrawRequired = newState.redrawRequired;
        if (pageStart != oldPageStart) {
            // Redraw if pageStart changes.
            redrawRequired = true;
        } else if (rowDataCount < oldRowDataCount) {
            // Redraw if we have trimmed the row data.
            redrawRequired = true;
        } else if (range1 == null && range0 != null && range0.getStart() == pageStart
                && (replaceDiff >= oldRowDataCount || replaceDiff > oldPageSize)) {
            // Redraw if the new data completely overlaps the old data.
            redrawRequired = true;
        } else if (replaceDiff >= REDRAW_MINIMUM && replaceDiff > REDRAW_THRESHOLD * oldRowDataCount) {
            // Redraw if the number of modified rows represents a large portion
            // of the view, defined as greater than 30% of the rows (minimum of
            // 5).
            redrawRequired = true;
        } else if (replacedEmptyRange && oldRowDataCount == 0) {
            // If the user replaced an empty range, pass it to the view. This is
            // a useful edge case that provides consistency in the way data is
            // pushed to the view.
            redrawRequired = true;
        } else {
            for (final Range r : modifiedRanges) {
               if (r.getStart() >= newState.rowData.size() || r.getStart() + r.getLength() >= newState.rowData.size()) {
                   redrawRequired = true;
                   break;
               }
            }

            if (!redrawRequired && range0 != null) {
                final int absStart = range0.getStart();
                final int relStart = absStart - pageStart;
                if (newState.rowData.size() <= relStart || newState.rowData.size() < relStart + range0.getLength()) {
                    redrawRequired = true;
                }
            }
            if (!redrawRequired && range1 != null) {
                final int absStart = range1.getStart();
                final int relStart = absStart - pageStart;
                if (newState.rowData.size() <= relStart || newState.rowData.size() < relStart + range1.getLength()) {
                    redrawRequired = true;
                }
            }
        }

        // Update the loading state in the view.
        updateLoadingState();

        // Push changes to the view.
        try {
            if (redrawRequired) {
                // Redraw the entire content.
                // SafeHtmlBuilder sb = new SafeHtmlBuilder();
                view.replaceAllChildren(newState.rowData, selectionModel, newState.keyboardStealFocus);
                view.resetFocus();
            } else if (range0 != null) {
                // Surgically replace specific rows.

                // Replace range0.
                {
                    final int absStart = range0.getStart();
                    final int relStart = absStart - pageStart;
                    // SafeHtmlBuilder sb = new SafeHtmlBuilder();
                    final List<T> replaceValues = newState.rowData.subList(relStart, relStart + range0.getLength());
                    view.replaceChildren(replaceValues, relStart, selectionModel, newState.keyboardStealFocus);
                }

                // Replace range1 if it exists.
                if (range1 != null) {
                    final int absStart = range1.getStart();
                    final int relStart = absStart - pageStart;
                    // SafeHtmlBuilder sb = new SafeHtmlBuilder();
                    final List<T> replaceValues = newState.rowData.subList(relStart, relStart + range1.getLength());
                    view.replaceChildren(replaceValues, relStart, selectionModel, newState.keyboardStealFocus);
                }

                view.resetFocus();
            } else if (keyboardRowChanged) {
                // Update the keyboard selected rows without redrawing.
                // Deselect the old keyboard row.
                final int oldSelectedRow = oldState.getKeyboardSelectedRow();
                if (oldSelectedRow >= 0 && oldSelectedRow < rowDataCount) {
                    view.setKeyboardSelected(oldSelectedRow, false, false);
                }

                // Select the new keyboard row.
                final int newSelectedRow = newState.getKeyboardSelectedRow();
                if (newSelectedRow >= 0 && newSelectedRow < rowDataCount) {
                    view.setKeyboardSelected(newSelectedRow, true, newState.keyboardStealFocus);
                }
            }
        } catch (final Error e) {
            // Force the error into the dev mode console.
            throw new RuntimeException(e);
        } finally {
            // We are done resolving state, so unlock the rendering loop. We
            // unlock the loop even if user rendering code throws an error to
            // avoid throwing an additional, misleading IllegalStateException.
            isResolvingState = false;
        }

        // Make a recursive call to resolve any pending state. We don't expect
        // pending state here, but its always possible that pushing the changes
        // into the view could update the presenter. If there is no new state,
        // the recursive call will reset the pendingStateLoop.
        resolvePendingState(null);
        return true;
    }
Resolve the pending state and push updates to the view.

@param modifiedRows the modified rows that need to be updated, or null if none.
                    The modified rows may be mutated.
@return true if the state changed, false if not
stroom-core-client-widget/src/main/java/com/google/gwt/user/cellview/client/HasDataPresenter.java