/*
 * Copyright (c) 2010, 2015, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package javafx.scene.control;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;

import com.sun.javafx.scene.control.behavior.ListCellBehavior;
import com.sun.javafx.scene.control.skin.TableViewSkinBase;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.WeakInvalidationListener;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ObjectPropertyBase;
import javafx.beans.property.ReadOnlyIntegerProperty;
import javafx.beans.property.ReadOnlyIntegerWrapper;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.WeakChangeListener;
import javafx.beans.value.WritableValue;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.MapChangeListener;
import javafx.collections.ObservableList;
import javafx.css.StyleableDoubleProperty;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.event.EventType;
import javafx.geometry.Orientation;
import javafx.scene.layout.Region;
import javafx.util.Callback;
import javafx.css.StyleableObjectProperty;
import javafx.css.CssMetaData;

import com.sun.javafx.css.converters.EnumConverter;

import javafx.collections.WeakListChangeListener;

import com.sun.javafx.css.converters.SizeConverter;
import com.sun.javafx.scene.control.skin.ListViewSkin;

import java.lang.ref.WeakReference;

import javafx.css.PseudoClass;
import javafx.beans.DefaultProperty;
import javafx.css.Styleable;
import javafx.css.StyleableProperty;
import javafx.scene.AccessibleAttribute;
import javafx.scene.AccessibleRole;
import javafx.scene.Node;

/**
 * A ListView displays a horizontal or vertical list of items from which the
 * user may select, or with which the user may interact. A ListView is able to
 * have its generic type set to represent the type of data in the backing model.
 * Doing this has the benefit of making various methods in the ListView, as well
 * as the supporting classes (mentioned below), type-safe. In addition, making
 * use of the generic supports substantially simplifies development of applications
 * making use of ListView, as all modern IDEs are able to auto-complete far
 * more successfully with the additional type information.
 *
 * <h3>Populating a ListView</h3>
 * <p>A simple example of how to create and populate a ListView of names (Strings)
 * is shown here:
 *
 * <pre>
 * {@code
 * ObservableList<String> names = FXCollections.observableArrayList(
 *          "Julia", "Ian", "Sue", "Matthew", "Hannah", "Stephan", "Denise");
 * ListView<String> listView = new ListView<String>(names);}</pre>
 *
 * <p>The elements of the ListView are contained within the
 * {@link #itemsProperty() items} {@link ObservableList}. This
 * ObservableList is automatically observed by the ListView, such that any
 * changes that occur inside the ObservableList will be automatically shown in
 * the ListView itself. If passying the <code>ObservableList</code> in to the
 * ListView constructor is not feasible, the recommended approach for setting
 * the items is to simply call:
 *
 * <pre>
 * {@code
 * ObservableList<T> content = ...
 * listView.setItems(content);}</pre>
 *
 * The end result of this is, as noted above, that the ListView will automatically
 * refresh the view to represent the items in the list.
 *
 * <p>Another approach, whilst accepted by the ListView, <b>is not the
 * recommended approach</b>:
 *
 * <pre>
 * {@code
 * List<T> content = ...
 * getItems().setAll(content);}</pre>
 *
 * The issue with the approach shown above is that the content list is being
 * copied into the items list - meaning that subsequent changes to the content
 * list are not observed, and will not be reflected visually within the ListView.
 *
 * <h3>ListView Selection / Focus APIs</h3>
 * <p>To track selection and focus, it is necessary to become familiar with the
 * {@link SelectionModel} and {@link FocusModel} classes. A ListView has at most
 * one instance of each of these classes, available from
 * {@link #selectionModelProperty() selectionModel} and
 * {@link #focusModelProperty() focusModel} properties respectively.
 * Whilst it is possible to use this API to set a new selection model, in
 * most circumstances this is not necessary - the default selection and focus
 * models should work in most circumstances.
 *
 * <p>The default {@link SelectionModel} used when instantiating a ListView is
 * an implementation of the {@link MultipleSelectionModel} abstract class.
 * However, as noted in the API documentation for
 * the {@link MultipleSelectionModel#selectionModeProperty() selectionMode}
 * property, the default value is {@link SelectionMode#SINGLE}. To enable
 * multiple selection in a default ListView instance, it is therefore necessary
 * to do the following:
 *
 * <pre>
 * {@code
 * listView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);}</pre>
 *
 * <h3>Customizing ListView Visuals</h3>
 * <p>The visuals of the ListView can be entirely customized by replacing the
 * default {@link #cellFactoryProperty() cell factory}. A cell factory is used to
 * generate {@link ListCell} instances, which are used to represent an item in the
 * ListView. See the {@link Cell} class documentation for a more complete
 * description of how to write custom Cells.
 *
 * <h3>Editing</h3>
 * <p>This control supports inline editing of values, and this section attempts to
 * give an overview of the available APIs and how you should use them.</p>
 *
 * <p>Firstly, cell editing most commonly requires a different user interface
 * than when a cell is not being edited. This is the responsibility of the
 * {@link Cell} implementation being used. For ListView, this is the responsibility
 * of the {@link #cellFactoryProperty() cell factory}. It is your choice whether the cell is
 * permanently in an editing state (e.g. this is common for {@link CheckBox} cells),
 * or to switch to a different UI when editing begins (e.g. when a double-click
 * is received on a cell).</p>
 *
 * <p>To know when editing has been requested on a cell,
 * simply override the {@link javafx.scene.control.Cell#startEdit()} method, and
 * update the cell {@link javafx.scene.control.Cell#textProperty() text} and
 * {@link javafx.scene.control.Cell#graphicProperty() graphic} properties as
 * appropriate (e.g. set the text to null and set the graphic to be a
 * {@link TextField}). Additionally, you should also override
 * {@link Cell#cancelEdit()} to reset the UI back to its original visual state
 * when the editing concludes. In both cases it is important that you also
 * ensure that you call the super method to have the cell perform all duties it
 * must do to enter or exit its editing mode.</p>
 *
 * <p>Once your cell is in an editing state, the next thing you are most probably
 * interested in is how to commit or cancel the editing that is taking place. This is your
 * responsibility as the cell factory provider. Your cell implementation will know
 * when the editing is over, based on the user input (e.g. when the user presses
 * the Enter or ESC keys on their keyboard). When this happens, it is your
 * responsibility to call {@link Cell#commitEdit(Object)} or
 * {@link Cell#cancelEdit()}, as appropriate.</p>
 *
 * <p>When you call {@link Cell#commitEdit(Object)} an event is fired to the
 * ListView, which you can observe by adding an {@link EventHandler} via
 * {@link ListView#setOnEditCommit(javafx.event.EventHandler)}. Similarly,
 * you can also observe edit events for
 * {@link ListView#setOnEditStart(javafx.event.EventHandler) edit start}
 * and {@link ListView#setOnEditCancel(javafx.event.EventHandler) edit cancel}.</p>
 *
 * <p>By default the ListView edit commit handler is non-null, with a default
 * handler that attempts to overwrite the property value for the
 * item in the currently-being-edited row. It is able to do this as the
 * {@link Cell#commitEdit(Object)} method is passed in the new value, and this
 * is passed along to the edit commit handler via the
 * {@link EditEvent} that is fired. It is simply a matter of calling
 * {@link EditEvent#getNewValue()} to retrieve this value.
 *
 * <p>It is very important to note that if you call
 * {@link ListView#setOnEditCommit(javafx.event.EventHandler)} with your own
 * {@link EventHandler}, then you will be removing the default handler. Unless
 * you then handle the writeback to the property (or the relevant data source),
 * nothing will happen. You can work around this by using the
 * {@link ListView#addEventHandler(javafx.event.EventType, javafx.event.EventHandler)}
 * method to add a {@link ListView#EDIT_COMMIT_EVENT} {@link EventType} with
 * your desired {@link EventHandler} as the second argument. Using this method,
 * you will not replace the default implementation, but you will be notified when
 * an edit commit has occurred.</p>
 *
 * <p>Hopefully this summary answers some of the commonly asked questions.
 * Fortunately, JavaFX ships with a number of pre-built cell factories that
 * handle all the editing requirements on your behalf. You can find these
 * pre-built cell factories in the javafx.scene.control.cell package.</p>
 *
 * @see ListCell
 * @see MultipleSelectionModel
 * @see FocusModel
 * @param <T> This type is used to represent the type of the objects stored in
 *          the ListViews {@link #itemsProperty() items} ObservableList. It is
 *          also used in the {@link #selectionModelProperty() selection model}
 *          and {@link #focusModelProperty() focus model}.
 * @since JavaFX 2.0
 */
// TODO add code examples
@DefaultProperty("items")
public class ListView<T> extends Control {

    /***************************************************************************
     *                                                                         *
     * Static properties and methods                                           *
     *                                                                         *
     **************************************************************************/

    /**
     * An EventType that indicates some edit event has occurred. It is the parent
     * type of all other edit events: {@link #EDIT_START_EVENT},
     *  {@link #EDIT_COMMIT_EVENT} and {@link #EDIT_CANCEL_EVENT}.
     */
    @SuppressWarnings("unchecked")
    public static <T> EventType<ListView.EditEvent<T>> editAnyEvent() {
        return (EventType<ListView.EditEvent<T>>) EDIT_ANY_EVENT;
    }
    private static final EventType<?> EDIT_ANY_EVENT =
            new EventType<>(Event.ANY, "LIST_VIEW_EDIT");

    /**
     * An EventType used to indicate that an edit event has started within the
     * ListView upon which the event was fired.
     */
    @SuppressWarnings("unchecked")
    public static <T> EventType<ListView.EditEvent<T>> editStartEvent() {
        return (EventType<ListView.EditEvent<T>>) EDIT_START_EVENT;
    }
    private static final EventType<?> EDIT_START_EVENT =
            new EventType<>(editAnyEvent(), "EDIT_START");

    /**
     * An EventType used to indicate that an edit event has just been canceled
     * within the ListView upon which the event was fired.
     */
    @SuppressWarnings("unchecked")
    public static <T> EventType<ListView.EditEvent<T>> editCancelEvent() {
        return (EventType<ListView.EditEvent<T>>) EDIT_CANCEL_EVENT;
    }
    private static final EventType<?> EDIT_CANCEL_EVENT =
            new EventType<>(editAnyEvent(), "EDIT_CANCEL");

    /**
     * An EventType used to indicate that an edit event has been committed
     * within the ListView upon which the event was fired.
     */
    @SuppressWarnings("unchecked")
    public static <T> EventType<ListView.EditEvent<T>> editCommitEvent() {
        return (EventType<ListView.EditEvent<T>>) EDIT_COMMIT_EVENT;
    }
    private static final EventType<?> EDIT_COMMIT_EVENT =
            new EventType<>(editAnyEvent(), "EDIT_COMMIT");



    /***************************************************************************
     *                                                                         *
     * Fields                                                                  *
     *                                                                         *
     **************************************************************************/

    // by default we always select the first row in the ListView, and when the
    // items list changes, we also reselect the first row. In some cases, such as
    // for the ComboBox, this is not desirable, so it can be disabled here.
    private boolean selectFirstRowByDefault = true;



    /***************************************************************************
     *                                                                         *
     * Constructors                                                            *
     *                                                                         *
     **************************************************************************/

    /**
     * Creates a default ListView which will display contents stacked vertically.
     * As no {@link ObservableList} is provided in this constructor, an empty
     * ObservableList is created, meaning that it is legal to directly call
     * {@link #getItems()} if so desired. However, as noted elsewhere, this
     * is not the recommended approach
     * (instead call {@link #setItems(javafx.collections.ObservableList)}).
     *
     * <p>Refer to the {@link ListView} class documentation for details on the
     * default state of other properties.
     */
    public ListView() {
        this(FXCollections.<T>observableArrayList());
    }

    /**
     * Creates a default ListView which will stack the contents retrieved from the
     * provided {@link ObservableList} vertically.
     *
     * <p>Attempts to add a listener to the {@link ObservableList}, such that all
     * subsequent changes inside the list will be shown to the user.
     *
     * <p>Refer to the {@link ListView} class documentation for details on the
     * default state of other properties.
     */
    public ListView(ObservableList<T> items) {
        getStyleClass().setAll(DEFAULT_STYLE_CLASS);
        setAccessibleRole(AccessibleRole.LIST_VIEW);

        setItems(items);

        // Install default....
        // ...selection model
        setSelectionModel(new ListView.ListViewBitSetSelectionModel<T>(this));

        // ...focus model
        setFocusModel(new ListView.ListViewFocusModel<T>(this));

        // ...edit commit handler
        setOnEditCommit(DEFAULT_EDIT_COMMIT_HANDLER);

        // Fix for RT-36651, which was introduced by RT-35679 (above) and resolved
        // by having special-case code to remove the listener when requested.
        // This is done by ComboBoxListViewSkin, so that selection is not done
        // when a ComboBox is shown.
        getProperties().addListener((MapChangeListener<Object, Object>) change -> {
            if (change.wasAdded() && "selectFirstRowByDefault".equals(change.getKey())) {
                Boolean _selectFirstRowByDefault = (Boolean) change.getValueAdded();
                if (_selectFirstRowByDefault == null) return;
                selectFirstRowByDefault = _selectFirstRowByDefault;
            }
        });
    }



    /***************************************************************************
     *                                                                         *
     * Callbacks and Events                                                    *
     *                                                                         *
     **************************************************************************/

    private EventHandler<ListView.EditEvent<T>> DEFAULT_EDIT_COMMIT_HANDLER = t -> {
        int index = t.getIndex();
        List<T> list = getItems();
        if (index < 0 || index >= list.size()) return;
        list.set(index, t.getNewValue());
    };



    /***************************************************************************
     *                                                                         *
     * Properties                                                              *
     *                                                                         *
     **************************************************************************/

    // --- Items
    private ObjectProperty<ObservableList<T>> items;

    /**
     * Sets the underlying data model for the ListView. Note that it has a generic
     * type that must match the type of the ListView itself.
     */
    public final void setItems(ObservableList<T> value) {
        itemsProperty().set(value);
    }

    /**
     * Returns an {@link ObservableList} that contains the items currently being
     * shown to the user. This may be null if
     * {@link #setItems(javafx.collections.ObservableList)} has previously been
     * called, however, by default it is an empty ObservableList.
     *
     * @return An ObservableList containing the items to be shown to the user, or
     *      null if the items have previously been set to null.
     */
    public final ObservableList<T> getItems() {
        return items == null ? null : items.get();
    }

    /**
     * The underlying data model for the ListView. Note that it has a generic
     * type that must match the type of the ListView itself.
     */
    public final ObjectProperty<ObservableList<T>> itemsProperty() {
        if (items == null) {
            items = new SimpleObjectProperty<>(this, "items");
        }
        return items;
    }


    // --- Placeholder Node
    private ObjectProperty<Node> placeholder;
    /**
     * This Node is shown to the user when the listview has no content to show.
     * This may be the case because the table model has no data in the first
     * place or that a filter has been applied to the list model, resulting
     * in there being nothing to show the user..
     * @since JavaFX 8.0
     */
    public final ObjectProperty<Node> placeholderProperty() {
        if (placeholder == null) {
            placeholder = new SimpleObjectProperty<Node>(this, "placeholder");
        }
        return placeholder;
    }
    public final void setPlaceholder(Node value) {
        placeholderProperty().set(value);
    }
    public final Node getPlaceholder() {
        return placeholder == null ? null : placeholder.get();
    }


    // --- Selection Model
    private ObjectProperty<MultipleSelectionModel<T>> selectionModel = new SimpleObjectProperty<MultipleSelectionModel<T>>(this, "selectionModel");

    /**
     * Sets the {@link MultipleSelectionModel} to be used in the ListView.
     * Despite a ListView requiring a <b>Multiple</b>SelectionModel, it is possible
     * to configure it to only allow single selection (see
     * {@link MultipleSelectionModel#setSelectionMode(javafx.scene.control.SelectionMode)}
     * for more information).
     */
    public final void setSelectionModel(MultipleSelectionModel<T> value) {
        selectionModelProperty().set(value);
    }

    /**
     * Returns the currently installed selection model.
     */
    public final MultipleSelectionModel<T> getSelectionModel() {
        return selectionModel == null ? null : selectionModel.get();
    }

    /**
     * The SelectionModel provides the API through which it is possible
     * to select single or multiple items within a ListView, as  well as inspect
     * which items have been selected by the user. Note that it has a generic
     * type that must match the type of the ListView itself.
     */
    public final ObjectProperty<MultipleSelectionModel<T>> selectionModelProperty() {
        return selectionModel;
    }


    // --- Focus Model
    private ObjectProperty<FocusModel<T>> focusModel;

    /**
     * Sets the {@link FocusModel} to be used in the ListView.
     */
    public final void setFocusModel(FocusModel<T> value) {
        focusModelProperty().set(value);
    }

    /**
     * Returns the currently installed {@link FocusModel}.
     */
    public final FocusModel<T> getFocusModel() {
        return focusModel == null ? null : focusModel.get();
    }

    /**
     * The FocusModel provides the API through which it is possible
     * to both get and set the focus on a single item within a ListView. Note
     * that it has a generic type that must match the type of the ListView itself.
     */
    public final ObjectProperty<FocusModel<T>> focusModelProperty() {
        if (focusModel == null) {
            focusModel = new SimpleObjectProperty<FocusModel<T>>(this, "focusModel");
        }
        return focusModel;
    }


    // --- Orientation
    private ObjectProperty<Orientation> orientation;

    /**
     * Sets the orientation of the ListView, which dictates whether
     * it scrolls vertically or horizontally.
     */
    public final void setOrientation(Orientation value) {
        orientationProperty().set(value);
    };

    /**
     * Returns the current orientation of the ListView, which dictates whether
     * it scrolls vertically or horizontally.
     */
    public final Orientation getOrientation() {
        return orientation == null ? Orientation.VERTICAL : orientation.get();
    }

    /**
     * The orientation of the {@code ListView} - this can either be horizontal
     * or vertical.
     */
    public final ObjectProperty<Orientation> orientationProperty() {
        if (orientation == null) {
            orientation = new StyleableObjectProperty<Orientation>(Orientation.VERTICAL) {
                @Override public void invalidated() {
                    final boolean active = (get() == Orientation.VERTICAL);
                    pseudoClassStateChanged(PSEUDO_CLASS_VERTICAL,    active);
                    pseudoClassStateChanged(PSEUDO_CLASS_HORIZONTAL, !active);
                }

                @Override
                public CssMetaData<ListView<?>,Orientation> getCssMetaData() {
                    return ListView.StyleableProperties.ORIENTATION;
                }

                @Override
                public Object getBean() {
                    return ListView.this;
                }

                @Override
                public String getName() {
                    return "orientation";
                }
            };
        }
        return orientation;
    }




    // --- Cell Factory
    private ObjectProperty<Callback<ListView<T>, ListCell<T>>> cellFactory;

    /**
     * Sets a new cell factory to use in the ListView. This forces all old
     * {@link ListCell}'s to be thrown away, and new ListCell's created with
     * the new cell factory.
     */
    public final void setCellFactory(Callback<ListView<T>, ListCell<T>> value) {
        cellFactoryProperty().set(value);
    }

    /**
     * Returns the current cell factory.
     */
    public final Callback<ListView<T>, ListCell<T>> getCellFactory() {
        return cellFactory == null ? null : cellFactory.get();
    }

    /**
     * <p>Setting a custom cell factory has the effect of deferring all cell
     * creation, allowing for total customization of the cell. Internally, the
     * ListView is responsible for reusing ListCells - all that is necessary
     * is for the custom cell factory to return from this function a ListCell
     * which might be usable for representing any item in the ListView.
     *
     * <p>Refer to the {@link Cell} class documentation for more detail.
     */
    public final ObjectProperty<Callback<ListView<T>, ListCell<T>>> cellFactoryProperty() {
        if (cellFactory == null) {
            cellFactory = new SimpleObjectProperty<Callback<ListView<T>, ListCell<T>>>(this, "cellFactory");
        }
        return cellFactory;
    }


    // --- Fixed cell size
    private DoubleProperty fixedCellSize;

    /**
     * Sets the new fixed cell size for this control. Any value greater than
     * zero will enable fixed cell size mode, whereas a zero or negative value
     * (or Region.USE_COMPUTED_SIZE) will be used to disabled fixed cell size
     * mode.
     *
     * @param value The new fixed cell size value, or a value less than or equal
     *              to zero (or Region.USE_COMPUTED_SIZE) to disable.
     * @since JavaFX 8.0
     */
    public final void setFixedCellSize(double value) {
        fixedCellSizeProperty().set(value);
    }

    /**
     * Returns the fixed cell size value. A value less than or equal to zero is
     * used to represent that fixed cell size mode is disabled, and a value
     * greater than zero represents the size of all cells in this control.
     *
     * @return A double representing the fixed cell size of this control, or a
     *      value less than or equal to zero if fixed cell size mode is disabled.
     * @since JavaFX 8.0
     */
    public final double getFixedCellSize() {
        return fixedCellSize == null ? Region.USE_COMPUTED_SIZE : fixedCellSize.get();
    }
    /**
     * Specifies whether this control has cells that are a fixed height (of the
     * specified value). If this value is less than or equal to zero,
     * then all cells are individually sized and positioned. This is a slow
     * operation. Therefore, when performance matters and developers are not
     * dependent on variable cell sizes it is a good idea to set the fixed cell
     * size value. Generally cells are around 24px, so setting a fixed cell size
     * of 24 is likely to result in very little difference in visuals, but a
     * improvement to performance.
     *
     * <p>To set this property via CSS, use the -fx-fixed-cell-size property.
     * This should not be confused with the -fx-cell-size property. The difference
     * between these two CSS properties is that -fx-cell-size will size all
     * cells to the specified size, but it will not enforce that this is the
     * only size (thus allowing for variable cell sizes, and preventing the
     * performance gains from being possible). Therefore, when performance matters
     * use -fx-fixed-cell-size, instead of -fx-cell-size. If both properties are
     * specified in CSS, -fx-fixed-cell-size takes precedence.</p>
     *
     * @since JavaFX 8.0
     */
    public final DoubleProperty fixedCellSizeProperty() {
        if (fixedCellSize == null) {
            fixedCellSize = new StyleableDoubleProperty(Region.USE_COMPUTED_SIZE) {
                @Override public CssMetaData<ListView<?>,Number> getCssMetaData() {
                    return StyleableProperties.FIXED_CELL_SIZE;
                }

                @Override public Object getBean() {
                    return ListView.this;
                }

                @Override public String getName() {
                    return "fixedCellSize";
                }
            };
        }
        return fixedCellSize;
    }


    // --- Editable
    private BooleanProperty editable;
    public final void setEditable(boolean value) {
        editableProperty().set(value);
    }
    public final boolean isEditable() {
        return editable == null ? false : editable.get();
    }
    /**
     * Specifies whether this ListView is editable - only if the ListView and
     * the ListCells within it are both editable will a ListCell be able to go
     * into their editing state.
     */
    public final BooleanProperty editableProperty() {
        if (editable == null) {
            editable = new SimpleBooleanProperty(this, "editable", false);
        }
        return editable;
    }


    // --- Editing Index
    private ReadOnlyIntegerWrapper editingIndex;

    private void setEditingIndex(int value) {
        editingIndexPropertyImpl().set(value);
    }

    /**
     * Returns the index of the item currently being edited in the ListView,
     * or -1 if no item is being edited.
     */
    public final int getEditingIndex() {
        return editingIndex == null ? -1 : editingIndex.get();
    }

    /**
     * <p>A property used to represent the index of the item currently being edited
     * in the ListView, if editing is taking place, or -1 if no item is being edited.
     *
     * <p>It is not possible to set the editing index, instead it is required that
     * you call {@link #edit(int)}.
     */
    public final ReadOnlyIntegerProperty editingIndexProperty() {
        return editingIndexPropertyImpl().getReadOnlyProperty();
    }

    private ReadOnlyIntegerWrapper editingIndexPropertyImpl() {
        if (editingIndex == null) {
            editingIndex = new ReadOnlyIntegerWrapper(this, "editingIndex", -1);
        }
        return editingIndex;
    }


    // --- On Edit Start
    private ObjectProperty<EventHandler<ListView.EditEvent<T>>> onEditStart;

    /**
     * Sets the {@link EventHandler} that will be called when the user begins
     * an edit.
     *
     * <p>This is a convenience method - the same result can be
     * achieved by calling
     * <code>addEventHandler(ListView.EDIT_START_EVENT, eventHandler)</code>.
     */
    public final void setOnEditStart(EventHandler<ListView.EditEvent<T>> value) {
        onEditStartProperty().set(value);
    }

    /**
     * Returns the {@link EventHandler} that will be called when the user begins
     * an edit.
     */
    public final EventHandler<ListView.EditEvent<T>> getOnEditStart() {
        return onEditStart == null ? null : onEditStart.get();
    }

    /**
     * This event handler will be fired when the user successfully initiates
     * editing.
     */
    public final ObjectProperty<EventHandler<ListView.EditEvent<T>>> onEditStartProperty() {
        if (onEditStart == null) {
            onEditStart = new ObjectPropertyBase<EventHandler<ListView.EditEvent<T>>>() {
                @Override protected void invalidated() {
                    setEventHandler(ListView.<T>editStartEvent(), get());
                }

                @Override
                public Object getBean() {
                    return ListView.this;
                }

                @Override
                public String getName() {
                    return "onEditStart";
                }
            };
        }
        return onEditStart;
    }


    // --- On Edit Commit
    private ObjectProperty<EventHandler<ListView.EditEvent<T>>> onEditCommit;

    /**
     * Sets the {@link EventHandler} that will be called when the user has
     * completed their editing. This is called as part of the
     * {@link ListCell#commitEdit(java.lang.Object)} method.
     *
     * <p>This is a convenience method - the same result can be
     * achieved by calling
     * <code>addEventHandler(ListView.EDIT_START_EVENT, eventHandler)</code>.
     */
    public final void setOnEditCommit(EventHandler<ListView.EditEvent<T>> value) {
        onEditCommitProperty().set(value);
    }

    /**
     * Returns the {@link EventHandler} that will be called when the user commits
     * an edit.
     */
    public final EventHandler<ListView.EditEvent<T>> getOnEditCommit() {
        return onEditCommit == null ? null : onEditCommit.get();
    }

    /**
     * <p>This property is used when the user performs an action that should
     * result in their editing input being persisted.</p>
     *
     * <p>The EventHandler in this property should not be called directly -
     * instead call {@link ListCell#commitEdit(java.lang.Object)} from within
     * your custom ListCell. This will handle firing this event, updating the
     * view, and switching out of the editing state.</p>
     */
    public final ObjectProperty<EventHandler<ListView.EditEvent<T>>> onEditCommitProperty() {
        if (onEditCommit == null) {
            onEditCommit = new ObjectPropertyBase<EventHandler<ListView.EditEvent<T>>>() {
                @Override protected void invalidated() {
                    setEventHandler(ListView.<T>editCommitEvent(), get());
                }

                @Override
                public Object getBean() {
                    return ListView.this;
                }

                @Override
                public String getName() {
                    return "onEditCommit";
                }
            };
        }
        return onEditCommit;
    }


    // --- On Edit Cancel
    private ObjectProperty<EventHandler<ListView.EditEvent<T>>> onEditCancel;

    /**
     * Sets the {@link EventHandler} that will be called when the user cancels
     * an edit.
     */
    public final void setOnEditCancel(EventHandler<ListView.EditEvent<T>> value) {
        onEditCancelProperty().set(value);
    }

    /**
     * Returns the {@link EventHandler} that will be called when the user cancels
     * an edit.
     */
    public final EventHandler<ListView.EditEvent<T>> getOnEditCancel() {
        return onEditCancel == null ? null : onEditCancel.get();
    }

    /**
     * This event handler will be fired when the user cancels editing a cell.
     */
    public final ObjectProperty<EventHandler<ListView.EditEvent<T>>> onEditCancelProperty() {
        if (onEditCancel == null) {
            onEditCancel = new ObjectPropertyBase<EventHandler<ListView.EditEvent<T>>>() {
                @Override protected void invalidated() {
                    setEventHandler(ListView.<T>editCancelEvent(), get());
                }

                @Override
                public Object getBean() {
                    return ListView.this;
                }

                @Override
                public String getName() {
                    return "onEditCancel";
                }
            };
        }
        return onEditCancel;
    }




    /***************************************************************************
     *                                                                         *
     * Public API                                                              *
     *                                                                         *
     **************************************************************************/

    /**
     * Instructs the ListView to begin editing the item in the given index, if
     * the ListView is {@link #editableProperty() editable}. Once
     * this method is called, if the current {@link #cellFactoryProperty()} is
     * set up to support editing, the Cell will switch its visual state to enable
     * for user input to take place.
     *
     * @param itemIndex The index of the item in the ListView that should be
     *     edited.
     */
    public void edit(int itemIndex) {
        if (!isEditable()) return;
        setEditingIndex(itemIndex);
    }

    /**
     * Scrolls the ListView such that the item in the given index is visible to
     * the end user.
     *
     * @param index The index that should be made visible to the user, assuming
     *      of course that it is greater than, or equal to 0, and less than the
     *      size of the items list contained within the given ListView.
     */
    public void scrollTo(int index) {
        ControlUtils.scrollToIndex(this, index);
    }

    /**
     * Scrolls the TableView so that the given object is visible within the viewport.
     * @param object The object that should be visible to the user.
     * @since JavaFX 8.0
     */
    public void scrollTo(T object) {
        if( getItems() != null ) {
            int idx = getItems().indexOf(object);
            if( idx >= 0 ) {
                ControlUtils.scrollToIndex(this, idx);
            }
        }
    }

    /**
     * Called when there's a request to scroll an index into view using {@link #scrollTo(int)}
     * or {@link #scrollTo(Object)}
     * @since JavaFX 8.0
     */
    private ObjectProperty<EventHandler<ScrollToEvent<Integer>>> onScrollTo;

    public void setOnScrollTo(EventHandler<ScrollToEvent<Integer>> value) {
        onScrollToProperty().set(value);
    }

    public EventHandler<ScrollToEvent<Integer>> getOnScrollTo() {
        if( onScrollTo != null ) {
            return onScrollTo.get();
        }
        return null;
    }

    public ObjectProperty<EventHandler<ScrollToEvent<Integer>>> onScrollToProperty() {
        if( onScrollTo == null ) {
            onScrollTo = new ObjectPropertyBase<EventHandler<ScrollToEvent<Integer>>>() {
                @Override protected void invalidated() {
                    setEventHandler(ScrollToEvent.scrollToTopIndex(), get());
                }

                @Override public Object getBean() {
                    return ListView.this;
                }

                @Override public String getName() {
                    return "onScrollTo";
                }
            };
        }
        return onScrollTo;
    }

    /** {@inheritDoc} */
    @Override protected Skin<?> createDefaultSkin() {
        return new ListViewSkin<T>(this);
    }

    /**
     * Calling {@code refresh()} forces the ListView control to recreate and
     * repopulate the cells necessary to populate the visual bounds of the control.
     * In other words, this forces the ListView to update what it is showing to
     * the user. This is useful in cases where the underlying data source has
     * changed in a way that is not observed by the ListView itself.
     *
     * @since JavaFX 8u60
     */
    public void refresh() {
        getProperties().put(ListViewSkin.RECREATE, Boolean.TRUE);
    }



    /***************************************************************************
     *                                                                         *
     * Private Implementation                                                  *
     *                                                                         *
     **************************************************************************/



    /***************************************************************************
     *                                                                         *
     * Stylesheet Handling                                                     *
     *                                                                         *
     **************************************************************************/

    private static final String DEFAULT_STYLE_CLASS = "list-view";

    /** @treatAsPrivate */
    private static class StyleableProperties {
        private static final CssMetaData<ListView<?>,Orientation> ORIENTATION =
            new CssMetaData<ListView<?>,Orientation>("-fx-orientation",
                new EnumConverter<Orientation>(Orientation.class),
                Orientation.VERTICAL) {

            @Override
            public Orientation getInitialValue(ListView<?> node) {
                // A vertical ListView should remain vertical
                return node.getOrientation();
            }

            @Override
            public boolean isSettable(ListView<?> n) {
                return n.orientation == null || !n.orientation.isBound();
            }

            @SuppressWarnings("unchecked") // orientationProperty() is a StyleableProperty<Orientation>
            @Override
            public StyleableProperty<Orientation> getStyleableProperty(ListView<?> n) {
                return (StyleableProperty<Orientation>)n.orientationProperty();
            }
        };

        private static final CssMetaData<ListView<?>,Number> FIXED_CELL_SIZE =
            new CssMetaData<ListView<?>,Number>("-fx-fixed-cell-size",
                                                SizeConverter.getInstance(),
                                                Region.USE_COMPUTED_SIZE) {

                @Override public Double getInitialValue(ListView<?> node) {
                    return node.getFixedCellSize();
                }

                @Override public boolean isSettable(ListView<?> n) {
                    return n.fixedCellSize == null || !n.fixedCellSize.isBound();
                }

                @Override public StyleableProperty<Number> getStyleableProperty(ListView<?> n) {
                    return (StyleableProperty<Number>)(WritableValue<Number>)n.fixedCellSizeProperty();
                }
            };

        private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;
        static {
            final List<CssMetaData<? extends Styleable, ?>> styleables =
                new ArrayList<CssMetaData<? extends Styleable, ?>>(Control.getClassCssMetaData());
            styleables.add(ORIENTATION);
            styleables.add(FIXED_CELL_SIZE);
            STYLEABLES = Collections.unmodifiableList(styleables);
        }
    }

    /**
     * @return The CssMetaData associated with this class, which may include the
     * CssMetaData of its super classes.
     * @since JavaFX 8.0
     */
    public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
        return StyleableProperties.STYLEABLES;
    }

    /**
     * {@inheritDoc}
     * @since JavaFX 8.0
     */
    @Override
    public List<CssMetaData<? extends Styleable, ?>> getControlCssMetaData() {
        return getClassCssMetaData();
    }

    private static final PseudoClass PSEUDO_CLASS_VERTICAL =
            PseudoClass.getPseudoClass("vertical");
    private static final PseudoClass PSEUDO_CLASS_HORIZONTAL =
            PseudoClass.getPseudoClass("horizontal");



    /***************************************************************************
     *                                                                         *
     * Accessibility handling                                                  *
     *                                                                         *
     **************************************************************************/

    @Override
    public Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) {
        switch (attribute) {
            case MULTIPLE_SELECTION: {
                MultipleSelectionModel<T> sm = getSelectionModel();
                return sm != null && sm.getSelectionMode() == SelectionMode.MULTIPLE;
            }
            default: return super.queryAccessibleAttribute(attribute, parameters);
        }
    }


    /***************************************************************************
     *                                                                         *
     * Support Interfaces                                                      *
     *                                                                         *
     **************************************************************************/



    /***************************************************************************
     *                                                                         *
     * Support Classes                                                         *
     *                                                                         *
     **************************************************************************/

    /**
     * An {@link Event} subclass used specifically in ListView for representing
     * edit-related events. It provides additional API to easily access the
     * index that the edit event took place on, as well as the input provided
     * by the end user.
     *
     * @param <T> The type of the input, which is the same type as the ListView
     *      itself.
     * @since JavaFX 2.0
     */
    public static class EditEvent<T> extends Event {
        private final T newValue;
        private final int editIndex;

        private static final long serialVersionUID = 20130724L;

        /**
         * Common supertype for all edit event types.
         * @since JavaFX 8.0
         */
        public static final EventType<?> ANY = EDIT_ANY_EVENT;

        /**
         * Creates a new EditEvent instance to represent an edit event. This
         * event is used for {@link #EDIT_START_EVENT},
         * {@link #EDIT_COMMIT_EVENT} and {@link #EDIT_CANCEL_EVENT} types.
         */
        public EditEvent(ListView<T> source,
                         EventType<? extends ListView.EditEvent<T>> eventType,
                         T newValue,
                         int editIndex) {
            super(source, Event.NULL_SOURCE_TARGET, eventType);
            this.editIndex = editIndex;
            this.newValue = newValue;
        }

        /**
         * Returns the ListView upon which the edit took place.
         */
        @Override public ListView<T> getSource() {
            return (ListView<T>) super.getSource();
        }

        /**
         * Returns the index in which the edit took place.
         */
        public int getIndex() {
            return editIndex;
        }

        /**
         * Returns the value of the new input provided by the end user.
         */
        public T getNewValue() {
            return newValue;
        }

        /**
         * Returns a string representation of this {@code EditEvent} object.
         * @return a string representation of this {@code EditEvent} object.
         */
        @Override public String toString() {
            return "ListViewEditEvent [ newValue: " + getNewValue() + ", ListView: " + getSource() + " ]";
        }
    }



    // package for testing
    static class ListViewBitSetSelectionModel<T> extends MultipleSelectionModelBase<T> {

        /***********************************************************************
         *                                                                     *
         * Constructors                                                        *
         *                                                                     *
         **********************************************************************/

        public ListViewBitSetSelectionModel(final ListView<T> listView) {
            if (listView == null) {
                throw new IllegalArgumentException("ListView can not be null");
            }

            this.listView = listView;


            /*
             * The following two listeners are used in conjunction with
             * SelectionModel.select(T obj) to allow for a developer to select
             * an item that is not actually in the data model. When this occurs,
             * we actively try to find an index that matches this object, going
             * so far as to actually watch for all changes to the items list,
             * rechecking each time.
             */
            itemsObserver = new InvalidationListener() {
                private WeakReference<ObservableList<T>> weakItemsRef = new WeakReference<>(listView.getItems());

                @Override public void invalidated(Observable observable) {
                    ObservableList<T> oldItems = weakItemsRef.get();
                    weakItemsRef = new WeakReference<>(listView.getItems());
                    updateItemsObserver(oldItems, listView.getItems());
                }
            };

            this.listView.itemsProperty().addListener(new WeakInvalidationListener(itemsObserver));
            if (listView.getItems() != null) {
                this.listView.getItems().addListener(weakItemsContentObserver);
            }

            updateItemCount();

            updateDefaultSelection();
        }

        // watching for changes to the items list content
        private final ListChangeListener<T> itemsContentObserver = new ListChangeListener<T>() {
            @Override public void onChanged(Change<? extends T> c) {
                updateItemCount();

                while (c.next()) {
                    final T selectedItem = getSelectedItem();
                    final int selectedIndex = getSelectedIndex();

                    if (listView.getItems() == null || listView.getItems().isEmpty()) {
                        selectedItemChange = c;
                        clearSelection();
                        selectedItemChange = null;
                    } else if (selectedIndex == -1 && selectedItem != null) {
                        int newIndex = listView.getItems().indexOf(selectedItem);
                        if (newIndex != -1) {
                            setSelectedIndex(newIndex);
                        }
                    } else if (c.wasRemoved() &&
                            c.getRemovedSize() == 1 &&
                            ! c.wasAdded() &&
                            selectedItem != null &&
                            selectedItem.equals(c.getRemoved().get(0))) {
                        // Bug fix for RT-28637
                        if (getSelectedIndex() < getItemCount()) {
                            final int previousRow = selectedIndex == 0 ? 0 : selectedIndex - 1;
                            T newSelectedItem = getModelItem(previousRow);
                            if (! selectedItem.equals(newSelectedItem)) {
                                startAtomic();
                                clearSelection(selectedIndex);
                                stopAtomic();
                                select(newSelectedItem);
                            }
                        }
                    }
                }

                updateSelection(c);
            }
        };

        // watching for changes to the items list
        private final InvalidationListener itemsObserver;

        private WeakListChangeListener<T> weakItemsContentObserver =
                new WeakListChangeListener<>(itemsContentObserver);




        /***********************************************************************
         *                                                                     *
         * Internal properties                                                 *
         *                                                                     *
         **********************************************************************/

        private final ListView<T> listView;

        private int itemCount = 0;

        private int previousModelSize = 0;

        // Listen to changes in the listview items list, such that when it
        // changes we can update the selected indices bitset to refer to the
        // new indices.
        // At present this is basically a left/right shift operation, which
        // seems to work ok.
        private void updateSelection(Change<? extends T> c) {
//            // debugging output
//            System.out.println(listView.getId());
//            if (c.wasAdded()) {
//                System.out.println("\tAdded size: " + c.getAddedSize() + ", Added sublist: " + c.getAddedSubList());
//            }
//            if (c.wasRemoved()) {
//                System.out.println("\tRemoved size: " + c.getRemovedSize() + ", Removed sublist: " + c.getRemoved());
//            }
//            if (c.wasReplaced()) {
//                System.out.println("\tWas replaced");
//            }
//            if (c.wasPermutated()) {
//                System.out.println("\tWas permutated");
//            }
            c.reset();

            int shift = 0;
            while (c.next()) {
                if (c.wasReplaced()) {
                    if (c.getList().isEmpty()) {
                        // the entire items list was emptied - clear selection
                        clearSelection();
                    } else {
                        int index = getSelectedIndex();

                        if (previousModelSize == c.getRemovedSize()) {
                            // all items were removed from the model
                            clearSelection();
                        } else if (index < getItemCount() && index >= 0) {
                            // Fix for RT-18969: the list had setAll called on it
                            // Use of makeAtomic is a fix for RT-20945
                            startAtomic();
                            clearSelection(index);
                            stopAtomic();
                            select(index);
                        } else {
                            // Fix for RT-22079
                            clearSelection();
                        }
                    }
                } else if (c.wasAdded() || c.wasRemoved()) {
                    shift += c.wasAdded() ? c.getAddedSize() : -c.getRemovedSize();
                } else if (c.wasPermutated()) {

                    // General approach:
                    //   -- detected a sort has happened
                    //   -- Create a permutation lookup map (1)
                    //   -- dump all the selected indices into a list (2)
                    //   -- clear the selected items / indexes (3)
                    //   -- create a list containing the new indices (4)
                    //   -- for each previously-selected index (5)
                    //     -- if index is in the permutation lookup map
                    //       -- add the new index to the new indices list
                    //   -- Perform batch selection (6)

                    // (1)
                    int length = c.getTo() - c.getFrom();
                    HashMap<Integer, Integer> pMap = new HashMap<Integer, Integer>(length);
                    for (int i = c.getFrom(); i < c.getTo(); i++) {
                        pMap.put(i, c.getPermutation(i));
                    }

                    // (2)
                    List<Integer> selectedIndices = new ArrayList<Integer>(getSelectedIndices());


                    // (3)
                    clearSelection();

                    // (4)
                    List<Integer> newIndices = new ArrayList<Integer>(getSelectedIndices().size());

                    // (5)
                    for (int i = 0; i < selectedIndices.size(); i++) {
                        int oldIndex = selectedIndices.get(i);

                        if (pMap.containsKey(oldIndex)) {
                            Integer newIndex = pMap.get(oldIndex);
                            newIndices.add(newIndex);
                        }
                    }

                    // (6)
                    if (!newIndices.isEmpty()) {
                        if (newIndices.size() == 1) {
                            select(newIndices.get(0));
                        } else {
                            int[] ints = new int[newIndices.size() - 1];
                            for (int i = 0; i < newIndices.size() - 1; i++) {
                                ints[i] = newIndices.get(i + 1);
                            }
                            selectIndices(newIndices.get(0), ints);
                        }
                    }
                }
            }

            if (shift != 0) {
                shiftSelection(c.getFrom(), shift, null);
            }

            previousModelSize = getItemCount();
        }



        /***********************************************************************
         *                                                                     *
         * Public selection API                                                *
         *                                                                     *
         **********************************************************************/

        /** {@inheritDoc} */
        @Override public void selectAll() {
            // when a selectAll happens, the anchor should not change, so we store it
            // before, and restore it afterwards
            final int anchor = ListCellBehavior.getAnchor(listView, -1);
            super.selectAll();
            ListCellBehavior.setAnchor(listView, anchor, false);
        }

        /** {@inheritDoc} */
        @Override public void clearAndSelect(int row) {
            ListCellBehavior.setAnchor(listView, row, false);
            super.clearAndSelect(row);
        }

        /** {@inheritDoc} */
        @Override protected void focus(int row) {
            if (listView.getFocusModel() == null) return;
            listView.getFocusModel().focus(row);

            listView.notifyAccessibleAttributeChanged(AccessibleAttribute.FOCUS_ITEM);
        }

        /** {@inheritDoc} */
        @Override protected int getFocusedIndex() {
            if (listView.getFocusModel() == null) return -1;
            return listView.getFocusModel().getFocusedIndex();
        }

        @Override protected int getItemCount() {
            return itemCount;
        }

        @Override protected T getModelItem(int index) {
            List<T> items = listView.getItems();
            if (items == null) return null;
            if (index < 0 || index >= itemCount) return null;

            return items.get(index);
        }



        /***********************************************************************
         *                                                                     *
         * Private implementation                                              *
         *                                                                     *
         **********************************************************************/

        private void updateItemCount() {
            if (listView == null) {
                itemCount = -1;
            } else {
                List<T> items = listView.getItems();
                itemCount = items == null ? -1 : items.size();
            }
        }

        private void updateItemsObserver(ObservableList<T> oldList, ObservableList<T> newList) {
            // update listeners
            if (oldList != null) {
                oldList.removeListener(weakItemsContentObserver);
            }
            if (newList != null) {
                newList.addListener(weakItemsContentObserver);
            }

            updateItemCount();
            updateDefaultSelection();
        }

        private void updateDefaultSelection() {
            // when the items list totally changes, we should clear out
            // the selection and focus
            int newSelectionIndex = -1;
            int newFocusIndex = -1;
            if (listView.getItems() != null) {
                T selectedItem = getSelectedItem();
                if (selectedItem != null) {
                    newSelectionIndex = listView.getItems().indexOf(selectedItem);
                    newFocusIndex = newSelectionIndex;
                }

                // we put focus onto the first item, if there is at least
                // one item in the list
                if (listView.selectFirstRowByDefault && newFocusIndex == -1) {
                    newFocusIndex = listView.getItems().size() > 0 ? 0 : -1;
                }
            }

            clearSelection();
            select(newSelectionIndex);
            focus(newFocusIndex);
        }
    }



    // package for testing
    static class ListViewFocusModel<T> extends FocusModel<T> {

        private final ListView<T> listView;
        private int itemCount = 0;

        public ListViewFocusModel(final ListView<T> listView) {
            if (listView == null) {
                throw new IllegalArgumentException("ListView can not be null");
            }

            this.listView = listView;

            itemsObserver = new InvalidationListener() {
                private WeakReference<ObservableList<T>> weakItemsRef = new WeakReference<>(listView.getItems());

                @Override public void invalidated(Observable observable) {
                    ObservableList<T> oldItems = weakItemsRef.get();
                    weakItemsRef = new WeakReference<>(listView.getItems());
                    updateItemsObserver(oldItems, listView.getItems());
                }
            };
            this.listView.itemsProperty().addListener(new WeakInvalidationListener(itemsObserver));
            if (listView.getItems() != null) {
                this.listView.getItems().addListener(weakItemsContentListener);
            }

            updateItemCount();

            if (itemCount > 0) {
                focus(0);
            }
        }


        private void updateItemsObserver(ObservableList<T> oldList, ObservableList<T> newList) {
            // the listview items list has changed, we need to observe
            // the new list, and remove any observer we had from the old list
            if (oldList != null) oldList.removeListener(weakItemsContentListener);
            if (newList != null) newList.addListener(weakItemsContentListener);

            updateItemCount();
        }

        private final InvalidationListener itemsObserver;

        // Listen to changes in the listview items list, such that when it
        // changes we can update the focused index to refer to the new indices.
        private final ListChangeListener<T> itemsContentListener = c -> {
            updateItemCount();

            while (c.next()) {
                // looking at the first change
                int from = c.getFrom();
                if (getFocusedIndex() == -1 || from > getFocusedIndex()) {
                    return;
                }

                c.reset();
                boolean added = false;
                boolean removed = false;
                int addedSize = 0;
                int removedSize = 0;
                while (c.next()) {
                    added |= c.wasAdded();
                    removed |= c.wasRemoved();
                    addedSize += c.getAddedSize();
                    removedSize += c.getRemovedSize();
                }

                if (added && !removed) {
                    focus(Math.min(getItemCount() - 1, getFocusedIndex() + addedSize));
                } else if (!added && removed) {
                    focus(Math.max(0, getFocusedIndex() - removedSize));
                }
            }
        };

        private WeakListChangeListener<T> weakItemsContentListener
                = new WeakListChangeListener<T>(itemsContentListener);

        @Override protected int getItemCount() {
            return itemCount;
        }

        @Override protected T getModelItem(int index) {
            if (isEmpty()) return null;
            if (index < 0 || index >= itemCount) return null;

            return listView.getItems().get(index);
        }

        private boolean isEmpty() {
            return itemCount == -1;
        }

        private void updateItemCount() {
            if (listView == null) {
                itemCount = -1;
            } else {
                List<T> items = listView.getItems();
                itemCount = items == null ? -1 : items.size();
            }
        }
    }
}
