changeset 2860:91f71d5a4918

Automated merge with ssh://jfxsrc.us.oracle.com//javafx/8.0/scrum/controls/jfx/rt
author jgiles
date Fri, 08 Mar 2013 15:49:08 +1300
parents 67fbda7cbc0a 65ab9f40d9ca
children 3f60345c322a ddb3874aa103
files
diffstat 10 files changed, 1342 insertions(+), 127 deletions(-) [+]
line wrap: on
line diff
--- a/javafx-ui-controls/src/com/sun/javafx/scene/control/TableColumnComparator.java	Thu Mar 07 18:37:29 2013 -0800
+++ b/javafx-ui-controls/src/com/sun/javafx/scene/control/TableColumnComparator.java	Fri Mar 08 15:49:08 2013 +1300
@@ -23,37 +23,41 @@
  * questions.
  */
 
-/*
- * To change this template, choose Tools | Templates
- * and open the template in the editor.
- */
-
 package com.sun.javafx.scene.control;
 
+import com.sun.javafx.collections.annotations.ReturnsUnmodifiableCollection;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.Comparator;
-
-import javafx.collections.FXCollections;
-import javafx.collections.ObservableList;
+import java.util.List;
 import javafx.scene.control.TableColumnBase;
 
-public class TableColumnComparator implements Comparator<Object> {
+public class TableColumnComparator<S,T> implements Comparator<S> {
 
-    private final ObservableList<TableColumnBase<?,?>> columns;
+    private final List<TableColumnBase<S,T>> columns;
 
-    public TableColumnComparator() {
-        this.columns = FXCollections.observableArrayList();
+    public TableColumnComparator(TableColumnBase<S,T>... columns) {
+        this(Arrays.asList(columns));
+    }
+    
+    public TableColumnComparator(List<TableColumnBase<S,T>> columns) {
+        this.columns = new ArrayList<TableColumnBase<S, T>>(columns);
     }
 
-    public ObservableList<TableColumnBase<?,?>> getColumns() {
-        return columns;
+    @ReturnsUnmodifiableCollection
+    public List<TableColumnBase<S,T>> getColumns() {
+        return Collections.unmodifiableList(columns);
     }
 
-    @Override public int compare(Object o1, Object o2) {
-        for (TableColumnBase tc : columns) {
-            Comparator c = tc.getComparator();
+    @Override public int compare(S o1, S o2) {
+        for (TableColumnBase<S,T> tc : columns) {
+            if (tc.getSortType() == null) continue;
+            
+            Comparator<T> c = tc.getComparator();
 
-            Object value1 = tc.getCellData(o1);
-            Object value2 = tc.getCellData(o2);
+            T value1 = tc.getCellData(o1);
+            T value2 = tc.getCellData(o2);
             
             int result = 0;
             switch (tc.getSortType()) {
@@ -87,4 +91,8 @@
         }
         return true;
     }
+
+    @Override public String toString() {
+        return "TableColumnComparator [ columns: " + getColumns() + "] ";
+    }
 }
--- a/javafx-ui-controls/src/com/sun/javafx/scene/control/skin/TableColumnHeader.java	Thu Mar 07 18:37:29 2013 -0800
+++ b/javafx-ui-controls/src/com/sun/javafx/scene/control/skin/TableColumnHeader.java	Fri Mar 08 15:49:08 2013 +1300
@@ -207,7 +207,12 @@
             if (header.getTableHeaderRow().isReordering() && header.isColumnReorderingEnabled()) {
                 header.columnReorderingComplete(me);
             } else {
-                header.sortColumn(tableColumn, me.isShiftDown());
+                TableColumnHeader.sortColumn(
+                        header.getTableViewSkin().getSortOrder(), 
+                        tableColumn, 
+                        header.isSortingEnabled(),
+                        header.isSortColumn,
+                        me.isShiftDown());
             }
             me.consume();
         }
@@ -551,8 +556,12 @@
         pseudoClassStateChanged(PSEUDO_CLASS_LAST_VISIBLE, isLastVisibleColumn);
     }
 
-    private void sortColumn(TableColumnBase column, boolean addColumn) {
-        if (! isSortingEnabled()) return;
+    public static void sortColumn(final ObservableList<TableColumnBase<?,?>> sortOrder, 
+            final TableColumnBase column, 
+            final boolean isSortingEnabled, 
+            final boolean isSortColumn, 
+            final boolean addColumn) {
+        if (! isSortingEnabled) return;
         
         // we only allow sorting on the leaf columns and columns
         // that actually have comparators defined, and are sortable
@@ -564,19 +573,19 @@
         if (addColumn) {
             if (!isSortColumn) {
                 column.setSortType(ASCENDING);
-                getTableViewSkin().getSortOrder().add(column);
+                sortOrder.add(column);
             } else if (column.getSortType() == ASCENDING) {
                 column.setSortType(DESCENDING);
             } else {
-                int i = getTableViewSkin().getSortOrder().indexOf(column);
+                int i = sortOrder.indexOf(column);
                 if (i != -1) {
-                    getTableViewSkin().getSortOrder().remove(i);
+                    sortOrder.remove(i);
                 }
             }
         } else {
             // the user has clicked on a column header - we should add this to
             // the TableView sortOrder list if it isn't already there.
-            if (isSortColumn && getTableViewSkin().getSortOrder().size() == 1) {
+            if (isSortColumn && sortOrder.size() == 1) {
                 // the column is already being sorted, and it's the only column.
                 // We therefore move through the 2nd or 3rd states:
                 //   1st click: sort ascending
@@ -586,7 +595,7 @@
                     column.setSortType(DESCENDING);
                 } else {
                     // remove from sort
-                    getTableViewSkin().getSortOrder().remove(column);
+                    sortOrder.remove(column);
                 }
             } else if (isSortColumn) {
                 // the column is already being used to sort, so we toggle its
@@ -600,20 +609,21 @@
                 // to prevent multiple sorts, we make a copy of the sort order
                 // list, moving the column value from the current position to 
                 // its new position at the front of the list
-                List<TableColumnBase> sortOrder = new ArrayList<TableColumnBase>(getTableViewSkin().getSortOrder());
-                sortOrder.remove(column);
-                sortOrder.add(0, column);
-                getTableViewSkin().getSortOrder().setAll(column);
+                List<TableColumnBase<?,?>> sortOrderCopy = new ArrayList<TableColumnBase<?,?>>(sortOrder);
+                sortOrderCopy.remove(column);
+                sortOrderCopy.add(0, column);
+                sortOrder.setAll(column);
             } else {
                 // add to the sort order, in ascending form
                 column.setSortType(ASCENDING);
-                getTableViewSkin().getSortOrder().setAll(column);
+                sortOrder.setAll(column);
             }
         }
     }
     
     
     
+    
     /***************************************************************************
      *                                                                         *
      * Layout                                                                  *
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/javafx-ui-controls/src/javafx/scene/control/SortEvent.java	Fri Mar 08 15:49:08 2013 +1300
@@ -0,0 +1,39 @@
+package javafx.scene.control;
+
+import javafx.event.Event;
+import javafx.event.EventTarget;
+import javafx.event.EventType;
+
+/**
+ * Event related to {@link TableView} and {@link TreeTableView} sorting.
+ */
+public class SortEvent<C> extends Event {
+
+    @SuppressWarnings("unchecked")
+    public static <C> EventType<SortEvent<C>> sortEvent() {
+        return (EventType<SortEvent<C>>) SORT_EVENT;
+    }
+    
+    @SuppressWarnings("unchecked")
+    private static final EventType<?> SORT_EVENT = new EventType(Event.ANY, "SORT_EVENT");
+    
+//    /**
+//     * Construct a new {@code Event} with the specified event source, target
+//     * and type. If the source or target is set to {@code null}, it is replaced
+//     * by the {@code NULL_SOURCE_TARGET} value.
+//     * 
+//     * @param source the event source which sent the event
+//     * @param target the event source which sent the event
+//     * @param type the event type
+//     * @param target the target of the scroll to operation
+//     */
+    public SortEvent(C source, EventTarget target) {
+        super(source, target, sortEvent());
+        
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override public C getSource() {
+        return (C) super.getSource();
+    }
+}
\ No newline at end of file
--- a/javafx-ui-controls/src/javafx/scene/control/TableUtil.java	Thu Mar 07 18:37:29 2013 -0800
+++ b/javafx-ui-controls/src/javafx/scene/control/TableUtil.java	Fri Mar 08 15:49:08 2013 +1300
@@ -30,6 +30,9 @@
 import java.util.List;
 import javafx.beans.InvalidationListener;
 import javafx.collections.ListChangeListener;
+import javafx.collections.ObservableList;
+import static javafx.scene.control.TableColumnBase.SortType.ASCENDING;
+import static javafx.scene.control.TableColumnBase.SortType.DESCENDING;
 
 /**
  * A package protected util class used by TableView and TreeTableView to reduce
@@ -91,9 +94,49 @@
         }
     }
     
+    static void handleSortFailure(ObservableList<? extends TableColumnBase> sortOrder, 
+            SortEventType sortEventType, final Object... supportInfo) {
+        // if the sort event is consumed we need to back out the previous
+        // action so that the UI is not in an incorrect state
+        if (sortEventType == SortEventType.COLUMN_SORT_TYPE_CHANGE) {
+            // go back to the previous sort type
+            final TableColumnBase changedColumn = (TableColumnBase) supportInfo[0];
+            final TableColumnBase.SortType sortType = changedColumn.getSortType();
+            if (sortType == ASCENDING) {
+                changedColumn.setSortType(null);
+            } else if (sortType == DESCENDING) {
+                changedColumn.setSortType(ASCENDING);
+            } else if (sortType == null) {
+                changedColumn.setSortType(DESCENDING);
+            }
+        } else if (sortEventType == SortEventType.SORT_ORDER_CHANGE) {
+            // Revert the sortOrder list to what it was previously
+            ListChangeListener.Change change = (ListChangeListener.Change) supportInfo[0];
+            
+            final List toRemove = new ArrayList();
+            final List toAdd = new ArrayList();
+            while (change.next()) {
+                if (change.wasAdded()) {
+                    toRemove.addAll(change.getAddedSubList());
+                }
+
+                if (change.wasRemoved()) {
+                    toAdd.addAll(change.getRemoved());
+                }
+            }
+            
+            sortOrder.removeAll(toRemove);
+            sortOrder.addAll(toAdd);
+        } else if (sortEventType == SortEventType.COLUMN_SORTABLE_CHANGE) {
+            // no-op - it is ok for the sortable type to remain as-is
+        }
+    }
     
-    
-    
+    static enum SortEventType {
+         SORT_ORDER_CHANGE,
+         COLUMN_SORT_TYPE_CHANGE,
+         COLUMN_SORTABLE_CHANGE
+     }
     
     
     
--- a/javafx-ui-controls/src/javafx/scene/control/TableView.java	Thu Mar 07 18:37:29 2013 -0800
+++ b/javafx-ui-controls/src/javafx/scene/control/TableView.java	Fri Mar 08 15:49:08 2013 +1300
@@ -29,9 +29,7 @@
 import com.sun.javafx.collections.NonIterableChange;
 
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
-import java.util.Set;
 
 import javafx.beans.InvalidationListener;
 import javafx.beans.Observable;
@@ -46,8 +44,6 @@
 import javafx.collections.ListChangeListener;
 import javafx.collections.MapChangeListener;
 import javafx.collections.ObservableList;
-import com.sun.javafx.collections.transformation.SortableList;
-import com.sun.javafx.collections.transformation.TransformationList;
 
 import javafx.scene.Node;
 import javafx.scene.layout.GridPane;
@@ -57,22 +53,23 @@
 import javafx.css.PseudoClass;
 import com.sun.javafx.scene.control.ReadOnlyUnbackedObservableList;
 import com.sun.javafx.scene.control.TableColumnComparator;
+import com.sun.javafx.scene.control.skin.TableColumnHeader;
 import javafx.collections.WeakListChangeListener;
-import javafx.event.Event;
 import javafx.event.EventHandler;
 import javafx.event.EventType;
 
 import com.sun.javafx.scene.control.skin.TableViewSkin;
 import com.sun.javafx.scene.control.skin.TableViewSkinBase;
-import com.sun.javafx.scene.control.skin.VirtualContainerBase;
 import java.lang.ref.WeakReference;
+import java.util.Comparator;
 import java.util.HashMap;
 import javafx.beans.DefaultProperty;
 import javafx.beans.WeakInvalidationListener;
 import javafx.beans.property.ReadOnlyObjectProperty;
 import javafx.beans.property.ReadOnlyObjectWrapper;
 import javafx.beans.value.WeakChangeListener;
-import javafx.scene.control.cell.TreeItemPropertyValueFactory;
+import static javafx.scene.control.TableColumnBase.SortType.ASCENDING;
+import static javafx.scene.control.TableColumnBase.SortType.DESCENDING;
 
 /**
  * The TableView control is designed to visualize an unlimited number of rows
@@ -319,6 +316,34 @@
         }
     };
     
+    /**
+     * The default {@link #sortPolicyProperty() sort policy} that this TableView
+     * will use if no other policy is specified. The sort policy is a simple 
+     * {@link Callback} that accepts a TableView as the sole argument and expects
+     * a Boolean response representing whether the sort succeeded or not. A Boolean
+     * response of true represents success, and a response of false (or null) will
+     * be considered to represent failure.
+     */
+    public static final Callback<TableView, Boolean> DEFAULT_SORT_POLICY = new Callback<TableView, Boolean>() {
+        @Override public Boolean call(TableView table) {
+            try {
+                FXCollections.sort(table.getItems(), table.getComparator());
+                return true;
+            } catch (UnsupportedOperationException e) {
+                // TODO might need to support other exception types including:
+                // ClassCastException - if the class of the specified element prevents it from being added to this list
+                // NullPointerException - if the specified element is null and this list does not permit null elements
+                // IllegalArgumentException - if some property of this element prevents it from being added to this list
+
+                // If we are here the list does not support sorting, so we gracefully 
+                // fail the sort request and ensure the UI is put back to its previous
+                // state. This is handled in the code that calls the sort policy.
+                
+                return false;
+            }
+        }
+    };
+    
     
     
     /***************************************************************************
@@ -395,7 +420,7 @@
         // the sort method.
         getSortOrder().addListener(new ListChangeListener<TableColumn<S,?>>() {
             @Override public void onChanged(Change<? extends TableColumn<S,?>> c) {
-                sort();
+                doSort(TableUtil.SortEventType.SORT_ORDER_CHANGE, c);
             }
         });
 
@@ -490,7 +515,7 @@
         @Override public void invalidated(Observable valueModel) {
             TableColumn col = (TableColumn) ((BooleanProperty)valueModel).getBean();
             if (! getSortOrder().contains(col)) return;
-            sort();
+            doSort(TableUtil.SortEventType.COLUMN_SORTABLE_CHANGE, col);
         }
     };
 
@@ -498,7 +523,7 @@
         @Override public void invalidated(Observable valueModel) {
             TableColumn col = (TableColumn) ((ObjectProperty)valueModel).getBean();
             if (! getSortOrder().contains(col)) return;
-            sort();
+            doSort(TableUtil.SortEventType.COLUMN_SORT_TYPE_CHANGE, col);
         }
     };
     
@@ -817,6 +842,108 @@
         return editingCell;
     }
 
+    
+    // --- Comparator (built via sortOrder list, so read-only)
+    /**
+     * The comparator property is a read-only property that is representative of the
+     * current state of the {@link #getSortOrder() sort order} list. The sort
+     * order list contains the columns that have been added to it either programmatically
+     * or via a user clicking on the headers themselves.
+     */
+    private ReadOnlyObjectWrapper<Comparator<S>> comparator;
+    private void setComparator(Comparator<S> value) {
+        comparatorPropertyImpl().set(value);
+    }
+    public final Comparator<S> getComparator() {
+        return comparator == null ? null : comparator.get();
+    }
+    public final ReadOnlyObjectProperty<Comparator<S>> comparatorProperty() {
+        return comparatorPropertyImpl().getReadOnlyProperty();
+    }
+    private ReadOnlyObjectWrapper<Comparator<S>> comparatorPropertyImpl() {
+        if (comparator == null) {
+            comparator = new ReadOnlyObjectWrapper<Comparator<S>>(this, "comparator");
+        }
+        return comparator;
+    }
+    
+    
+    // --- sortPolicy
+    /**
+     * The sort policy specifies how sorting in this TableView should be performed.
+     * For example, a basic sort policy may just call 
+     * {@code FXCollections.sort(tableView.getItems())}, whereas a more advanced
+     * sort policy may call to a database to perform the necessary sorting on the
+     * server-side.
+     * 
+     * <p>TableView ships with a {@link TableView#DEFAULT_SORT_POLICY default
+     * sort policy} that does precisely as mentioned above: it simply attempts
+     * to sort the items list in-place.
+     * 
+     * <p>It is recommended that rather than override the {@link TableView#sort() sort}
+     * method that a different sort policy be provided instead.
+     */
+    private ObjectProperty<Callback<TableView<S>, Boolean>> sortPolicy;
+    public final void setSortPolicy(Callback<TableView<S>, Boolean> callback) {
+        sortPolicyProperty().set(callback);
+    }
+    @SuppressWarnings("unchecked") 
+    public final Callback<TableView<S>, Boolean> getSortPolicy() {
+        return sortPolicy == null ? 
+                (Callback<TableView<S>, Boolean>)(Object) DEFAULT_SORT_POLICY : 
+                sortPolicy.get();
+    }
+    @SuppressWarnings("unchecked")
+    public final ObjectProperty<Callback<TableView<S>, Boolean>> sortPolicyProperty() {
+        if (sortPolicy == null) {
+            sortPolicy = new SimpleObjectProperty<Callback<TableView<S>, Boolean>>(
+                    this, "sortPolicy", (Callback<TableView<S>, Boolean>)(Object) DEFAULT_SORT_POLICY) {
+                @Override protected void invalidated() {
+                    sort();
+                }
+            };
+        }
+        return sortPolicy;
+    }
+    
+    
+    // onSort
+    /**
+     * Called when there's a request to sort the control.
+     */
+    private ObjectProperty<EventHandler<SortEvent<TableView<S>>>> onSort;
+    
+    public void setOnSort(EventHandler<SortEvent<TableView<S>>> value) {
+        onSortProperty().set(value);
+    }
+    
+    public EventHandler<SortEvent<TableView<S>>> getOnSort() {
+        if( onSort != null ) {
+            return onSort.get();
+        }
+        return null;
+    }
+    
+    public ObjectProperty<EventHandler<SortEvent<TableView<S>>>> onSortProperty() {
+        if( onSort == null ) {
+            onSort = new ObjectPropertyBase<EventHandler<SortEvent<TableView<S>>>>() {
+                @Override protected void invalidated() {
+                    EventType<SortEvent<TableView<S>>> eventType = SortEvent.sortEvent();
+                    EventHandler<SortEvent<TableView<S>>> eventHandler = get();
+                    setEventHandler(eventType, eventHandler);
+                }
+                
+                @Override public Object getBean() {
+                    return TableView.this;
+                }
+
+                @Override public String getName() {
+                    return "onSort";
+                }
+            };
+        }
+        return onSort;
+    }
 
     
     /***************************************************************************
@@ -1030,12 +1157,80 @@
     @Override protected Skin<?> createDefaultSkin() {
         return new TableViewSkin(this);
     }
+    
+    /**
+     * The sort method forces the TableView to re-run its sorting algorithm. More 
+     * often than not it is not necessary to call this method directly, as it is
+     * automatically called when the {@link #getSortOrder() sort order}, 
+     * {@link #sortPolicyProperty() sort policy}, or the state of the 
+     * TableColumn {@link TableColumn#sortTypeProperty() sort type} properties 
+     * change. In other words, this method should only be called directly when
+     * something external changes and a sort is required.
+     */
+    public void sort() {
+        final ObservableList<? extends TableColumnBase> sortOrder = getSortOrder();
+        
+        // update the Comparator property
+        final Comparator<S> oldComparator = getComparator();
+        if (sortOrder.isEmpty()) {
+            setComparator(null);
+        } else {
+            Comparator<S> newComparator = new TableColumnComparator(sortOrder);
+            setComparator(newComparator);
+        }
+        
+        // fire the onSort event and check if it is consumed, if
+        // so, don't run the sort
+        SortEvent<TableView<S>> sortEvent = new SortEvent<TableView<S>>(TableView.this, TableView.this);
+        fireEvent(sortEvent);
+        if (sortEvent.isConsumed()) {
+            // if the sort is consumed we could back out the last action (the code
+            // is commented out right below), but we don't as we take it as a 
+            // sign that the developer has decided to handle the event themselves.
+            
+            // sortLock = true;
+            // TableUtil.handleSortFailure(sortOrder, lastSortEventType, lastSortEventSupportInfo);
+            // sortLock = false;
+            return;
+        }
+
+        // get the sort policy and run it
+        Callback<TableView<S>, Boolean> sortPolicy = getSortPolicy();
+        if (sortPolicy == null) return;
+        Boolean success = sortPolicy.call(this);
+        
+        if (success == null || ! success) {
+            // the sort was a failure. Need to backout if possible
+            sortLock = true;
+            TableUtil.handleSortFailure(sortOrder, lastSortEventType, lastSortEventSupportInfo);
+            setComparator(oldComparator);
+            sortLock = false;
+        }
+    }
+    
+    
 
     /***************************************************************************
      *                                                                         *
      * Private Implementation                                                  *
      *                                                                         *
      **************************************************************************/
+    
+    private boolean sortLock = false;
+    private TableUtil.SortEventType lastSortEventType = null;
+    private Object[] lastSortEventSupportInfo = null;
+    
+    private void doSort(final TableUtil.SortEventType sortEventType, final Object... supportInfo) {
+        if (sortLock) {
+            return;
+        }
+        
+        this.lastSortEventType = sortEventType;
+        this.lastSortEventSupportInfo = supportInfo;
+        sort();
+        this.lastSortEventType = null;
+        this.lastSortEventSupportInfo = null;
+    }
 
     /**
      * Call this function to force the TableView to re-evaluate itself. This is
@@ -1049,56 +1244,6 @@
         getProperties().put(TableViewSkinBase.REFRESH, Boolean.TRUE);
     }
 
-    /**
-     * Sometimes we want to force a sort to run - this is the recommended way
-     * of doing it internally. External users of the TableView API should just
-     * stick to modifying the TableView.sortOrder ObservableList (or the contents
-     * of the TableColumns within it - in particular the
-     * TableColumn.sortAscending boolean).
-     */
-    private void sort() {
-        // build up a new comparator based on the current table columms
-        TableColumnComparator comparator = new TableColumnComparator();
-        for (TableColumn<S,?> tc : getSortOrder()) {
-            comparator.getColumns().add(tc);
-        }
-
-        // If the items are a TransformationList, but not a SortableList, then we need
-        // to get the source of the TransformationList and check it.
-        if (getItems() instanceof TransformationList) {
-            // FIXME this is temporary code whilst I await for similar functionality
-            // within FXCollections.sort, such that it does the unwrapping that is
-            // shown below
-            List list = getItems();
-            while (list != null) {
-                if (list instanceof SortableList) {
-                    break;
-                } else if (list instanceof TransformationList) {
-                    list = ((TransformationList)list).getDirectSource();
-                } else {
-                    break;
-                }
-            }
-
-            if (list instanceof SortableList) {
-                SortableList sortableList = (SortableList) list;
-                // TODO review - note that we're changing the comparator based on
-                // what columns the user has set.
-                sortableList.setComparator(comparator);
-                
-                if (sortableList.getMode() == SortableList.SortMode.BATCH) {
-                    sortableList.sort();
-                }
-                
-                return;
-            }
-        }
-
-        // If we are here, we will use the default sort functionality available
-        // in FXCollections
-        FXCollections.sort(getItems(), comparator);
-    }
-
 
     // --- Content width
     private void setContentWidth(double contentWidth) {
--- a/javafx-ui-controls/src/javafx/scene/control/TreeItem.java	Thu Mar 07 18:37:29 2013 -0800
+++ b/javafx-ui-controls/src/javafx/scene/control/TreeItem.java	Fri Mar 08 15:49:08 2013 +1300
@@ -799,6 +799,9 @@
     private void sort(final ObservableList<TreeItem<T>> children, 
                          final Comparator<TreeItem<T>> comparator, 
                          final TreeSortMode sortMode) {
+        
+        if (comparator == null) return;
+        
         runSort(children, comparator, sortMode);
         
         // if we're at the root node, we'll fire an event so that the control
@@ -839,7 +842,6 @@
 //            
         } else {
             // Unknown sort mode
-            System.out.println("Unknown sort mode in TreeItem.runSort()");
         }
     }
     
--- a/javafx-ui-controls/src/javafx/scene/control/TreeTableView.java	Thu Mar 07 18:37:29 2013 -0800
+++ b/javafx-ui-controls/src/javafx/scene/control/TreeTableView.java	Fri Mar 08 15:49:08 2013 +1300
@@ -36,6 +36,7 @@
 import com.sun.javafx.scene.control.skin.VirtualContainerBase;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
+import java.util.Comparator;
 import java.util.List;
 import java.util.Set;
 import javafx.application.Platform;
@@ -322,7 +323,7 @@
         // the sort method.
         getSortOrder().addListener(new ListChangeListener<TreeTableColumn<S,?>>() {
             @Override public void onChanged(ListChangeListener.Change<? extends TreeTableColumn<S,?>> c) {
-                sort();
+                doSort(TableUtil.SortEventType.SORT_ORDER_CHANGE, c);
             }
         });
 
@@ -480,6 +481,42 @@
         }
     };
     
+    /**
+     * The default {@link #sortPolicyProperty() sort policy} that this TreeTableView
+     * will use if no other policy is specified. The sort policy is a simple 
+     * {@link Callback} that accepts a TreeTableView as the sole argument and expects
+     * a Boolean response representing whether the sort succeeded or not. A Boolean
+     * response of true represents success, and a response of false (or null) will
+     * be considered to represent failure.
+     */
+    public static final Callback<TreeTableView, Boolean> DEFAULT_SORT_POLICY = new Callback<TreeTableView, Boolean>() {
+        @Override public Boolean call(TreeTableView table) {
+            try {
+                TreeItem rootItem = table.getRoot();
+                if (rootItem == null) return false;
+
+                TreeSortMode sortMode = table.getSortMode();
+                if (sortMode == null) return false;
+
+                rootItem.lastSortMode = sortMode;
+                rootItem.lastComparator = table.getComparator();
+                rootItem.sort();
+                return true;
+            } catch (UnsupportedOperationException e) {
+                // TODO might need to support other exception types including:
+                // ClassCastException - if the class of the specified element prevents it from being added to this list
+                // NullPointerException - if the specified element is null and this list does not permit null elements
+                // IllegalArgumentException - if some property of this element prevents it from being added to this list
+
+                // If we are here the list does not support sorting, so we gracefully 
+                // fail the sort request and ensure the UI is put back to its previous
+                // state. This is handled in the code that calls the sort policy.
+                
+                return false;
+            }
+        }
+    };
+    
     
     
     /***************************************************************************
@@ -581,7 +618,7 @@
         @Override public void invalidated(Observable valueModel) {
             TreeTableColumn col = (TreeTableColumn) ((BooleanProperty)valueModel).getBean();
             if (! getSortOrder().contains(col)) return;
-            sort();
+            doSort(TableUtil.SortEventType.COLUMN_SORTABLE_CHANGE, col);
         }
     };
 
@@ -589,7 +626,7 @@
         @Override public void invalidated(Observable valueModel) {
             TreeTableColumn col = (TreeTableColumn) ((ObjectProperty)valueModel).getBean();
             if (! getSortOrder().contains(col)) return;
-            sort();
+            doSort(TableUtil.SortEventType.COLUMN_SORT_TYPE_CHANGE, col);
         }
     };
     
@@ -1206,6 +1243,108 @@
     }
     
     
+    // --- Comparator (built via sortOrder list, so read-only)
+    /**
+     * The comparator property is a read-only property that is representative of the
+     * current state of the {@link #getSortOrder() sort order} list. The sort
+     * order list contains the columns that have been added to it either programmatically
+     * or via a user clicking on the headers themselves.
+     */
+    private ReadOnlyObjectWrapper<Comparator<S>> comparator;
+    private void setComparator(Comparator<S> value) {
+        comparatorPropertyImpl().set(value);
+    }
+    public final Comparator<S> getComparator() {
+        return comparator == null ? null : comparator.get();
+    }
+    public final ReadOnlyObjectProperty<Comparator<S>> comparatorProperty() {
+        return comparatorPropertyImpl().getReadOnlyProperty();
+    }
+    private ReadOnlyObjectWrapper<Comparator<S>> comparatorPropertyImpl() {
+        if (comparator == null) {
+            comparator = new ReadOnlyObjectWrapper<Comparator<S>>(this, "comparator");
+        }
+        return comparator;
+    }
+    
+    
+    // --- sortPolicy
+    /**
+     * The sort policy specifies how sorting in this TreeTableView should be performed.
+     * For example, a basic sort policy may just recursively sort the children of 
+     * the root tree item, whereas a more advanced sort policy may call to a 
+     * database to perform the necessary sorting on the server-side.
+     * 
+     * <p>TreeTableView ships with a {@link TableView#DEFAULT_SORT_POLICY default
+     * sort policy} that does precisely as mentioned above: it simply attempts
+     * to sort the tree hierarchy in-place.
+     * 
+     * <p>It is recommended that rather than override the {@link TreeTableView#sort() sort}
+     * method that a different sort policy be provided instead.
+     */
+    private ObjectProperty<Callback<TreeTableView<S>, Boolean>> sortPolicy;
+    public final void setSortPolicy(Callback<TreeTableView<S>, Boolean> callback) {
+        sortPolicyProperty().set(callback);
+    }
+    @SuppressWarnings("unchecked") 
+    public final Callback<TreeTableView<S>, Boolean> getSortPolicy() {
+        return sortPolicy == null ? 
+                (Callback<TreeTableView<S>, Boolean>)(Object) DEFAULT_SORT_POLICY : 
+                sortPolicy.get();
+    }
+    @SuppressWarnings("unchecked")
+    public final ObjectProperty<Callback<TreeTableView<S>, Boolean>> sortPolicyProperty() {
+        if (sortPolicy == null) {
+            sortPolicy = new SimpleObjectProperty<Callback<TreeTableView<S>, Boolean>>(
+                    this, "sortPolicy", (Callback<TreeTableView<S>, Boolean>)(Object) DEFAULT_SORT_POLICY) {
+                @Override protected void invalidated() {
+                    sort();
+                }
+            };
+        }
+        return sortPolicy;
+    }
+    
+    
+    // onSort
+    /**
+     * Called when there's a request to sort the control.
+     */
+    private ObjectProperty<EventHandler<SortEvent<TreeTableView<S>>>> onSort;
+    
+    public void setOnSort(EventHandler<SortEvent<TreeTableView<S>>> value) {
+        onSortProperty().set(value);
+    }
+    
+    public EventHandler<SortEvent<TreeTableView<S>>> getOnSort() {
+        if( onSort != null ) {
+            return onSort.get();
+        }
+        return null;
+    }
+    
+    public ObjectProperty<EventHandler<SortEvent<TreeTableView<S>>>> onSortProperty() {
+        if( onSort == null ) {
+            onSort = new ObjectPropertyBase<EventHandler<SortEvent<TreeTableView<S>>>>() {
+                @Override protected void invalidated() {
+                    EventType<SortEvent<TreeTableView<S>>> eventType = SortEvent.sortEvent();
+                    EventHandler<SortEvent<TreeTableView<S>>> eventHandler = get();
+                    setEventHandler(eventType, eventHandler);
+                }
+                
+                @Override public Object getBean() {
+                    return TreeTableView.this;
+                }
+
+                @Override public String getName() {
+                    return "onSort";
+                }
+            };
+        }
+        return onSort;
+    }
+    
+    
     
     /***************************************************************************
      *                                                                         *
@@ -1455,6 +1594,56 @@
         return visibleLeafColumns.get(column);
     }
 
+    /**
+     * The sort method forces the TreeTableView to re-run its sorting algorithm. More 
+     * often than not it is not necessary to call this method directly, as it is
+     * automatically called when the {@link #getSortOrder() sort order}, 
+     * {@link #sortPolicyProperty() sort policy}, or the state of the 
+     * TableColumn {@link TableColumn#sortTypeProperty() sort type} properties 
+     * change. In other words, this method should only be called directly when
+     * something external changes and a sort is required.
+     */
+    public void sort() {
+        final ObservableList<? extends TableColumnBase> sortOrder = getSortOrder();
+        
+        // update the Comparator property
+        final Comparator<S> oldComparator = getComparator();
+        if (sortOrder.isEmpty()) {
+            setComparator(null);
+        } else {
+            Comparator<S> newComparator = new TableColumnComparator(sortOrder);
+            setComparator(newComparator);
+        }
+        
+        // fire the onSort event and check if it is consumed, if
+        // so, don't run the sort
+        SortEvent<TreeTableView<S>> sortEvent = new SortEvent<TreeTableView<S>>(TreeTableView.this, TreeTableView.this);
+        fireEvent(sortEvent);
+        if (sortEvent.isConsumed()) {
+            // if the sort is consumed we could back out the last action (the code
+            // is commented out right below), but we don't as we take it as a 
+            // sign that the developer has decided to handle the event themselves.
+            
+            // sortLock = true;
+            // TableUtil.handleSortFailure(sortOrder, lastSortEventType, lastSortEventSupportInfo);
+            // sortLock = false;
+            return;
+        }
+
+        // get the sort policy and run it
+        Callback<TreeTableView<S>, Boolean> sortPolicy = getSortPolicy();
+        if (sortPolicy == null) return;
+        Boolean success = sortPolicy.call(this);
+        
+        if (success == null || ! success) {
+            // the sort was a failure. Need to backout if possible
+            sortLock = true;
+            TableUtil.handleSortFailure(sortOrder, lastSortEventType, lastSortEventSupportInfo);
+            setComparator(oldComparator);
+            sortLock = false;
+        }
+    }
+    
     
     
     /***************************************************************************
@@ -1463,6 +1652,22 @@
      *                                                                         *
      **************************************************************************/
     
+    private boolean sortLock = false;
+    private TableUtil.SortEventType lastSortEventType = null;
+    private Object[] lastSortEventSupportInfo = null;
+    
+    private void doSort(final TableUtil.SortEventType sortEventType, final Object... supportInfo) {
+        if (sortLock) {
+            return;
+        }
+        
+        this.lastSortEventType = sortEventType;
+        this.lastSortEventSupportInfo = supportInfo;
+        sort();
+        this.lastSortEventType = null;
+        this.lastSortEventSupportInfo = null;
+    }
+    
     private void updateExpandedItemCount(TreeItem treeItem) {
         setExpandedItemCount(TreeUtil.updateExpandedItemCount(treeItem, expandedItemCountDirty, isShowRoot()));
         expandedItemCountDirty = false;
@@ -1488,31 +1693,6 @@
         getProperties().put(TableViewSkinBase.REFRESH, Boolean.TRUE);
     }
     
-    /**
-     * Sometimes we want to force a sort to run - this is the recommended way
-     * of doing it internally. External users of the TableView API should just
-     * stick to modifying the TableView.sortOrder ObservableList (or the contents
-     * of the TreeTableColumns within it - in particular the
-     * TreeTableColumn.sortAscending boolean).
-     */
-    private void sort() {
-        TreeItem rootItem = getRoot();
-        if (rootItem == null) return;
-        
-        TreeSortMode sortMode = getSortMode();
-        if (sortMode == null) return;
-        
-        // build up a new comparator based on the current table columms
-        TableColumnComparator comparator = new TableColumnComparator();
-        for (TreeTableColumn<S,?> tc : getSortOrder()) {
-            comparator.getColumns().add(tc);
-        }
-
-        rootItem.lastSortMode = sortMode;
-        rootItem.lastComparator = comparator;
-        rootItem.sort();
-    }
-    
     // --- Content width
     private void setContentWidth(double contentWidth) {
         this.contentWidth = contentWidth;
--- a/javafx-ui-controls/test/com/sun/javafx/scene/control/test/ControlAsserts.java	Thu Mar 07 18:37:29 2013 -0800
+++ b/javafx-ui-controls/test/com/sun/javafx/scene/control/test/ControlAsserts.java	Fri Mar 08 15:49:08 2013 +1300
@@ -42,6 +42,14 @@
 
 public class ControlAsserts {
     
+    public static void assertListContainsItemsInOrder(final List items, final Object... expected) {
+        assertEquals(expected.length, items.size());
+        for (int i = 0; i < expected.length; i++) {
+            Object item = items.get(i);
+            assertEquals(expected[i], item);
+        }
+    }
+    
     public static void assertRowsEmpty(final Control control, final int startRow, final int endRow) {
         assertRows(control, startRow, endRow, true);
     }
--- a/javafx-ui-controls/test/javafx/scene/control/TableViewTest.java	Thu Mar 07 18:37:29 2013 -0800
+++ b/javafx-ui-controls/test/javafx/scene/control/TableViewTest.java	Fri Mar 08 15:49:08 2013 +1300
@@ -25,6 +25,7 @@
 
 package javafx.scene.control;
 
+import com.sun.javafx.scene.control.TableColumnComparator;
 import com.sun.javafx.scene.control.test.ControlAsserts;
 import com.sun.javafx.scene.control.test.Person;
 import com.sun.javafx.scene.control.test.RT_22463_Person;
@@ -33,18 +34,26 @@
 import static org.junit.Assert.*;
 
 import java.util.Arrays;
+import java.util.Comparator;
 import java.util.List;
 
 import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.ReadOnlyObjectWrapper;
 import javafx.beans.property.SimpleObjectProperty;
+import javafx.beans.value.ObservableValue;
 import javafx.collections.FXCollections;
 import javafx.collections.ObservableList;
+import javafx.event.EventHandler;
 import javafx.scene.control.cell.PropertyValueFactory;
+import javafx.util.Callback;
 
 import org.junit.Before;
 import org.junit.Ignore;
 import org.junit.Test;
 
+import static javafx.scene.control.TableColumnBase.SortType.ASCENDING;
+import static javafx.scene.control.TableColumnBase.SortType.DESCENDING;
+
 public class TableViewTest {
     private TableView<String> table;
     private TableView.TableViewSelectionModel sm;
@@ -70,6 +79,18 @@
     @Test public void noArgConstructorSetsNonNullItems() {
         assertNotNull(table.getItems());
     }
+    
+    @Test public void noArgConstructorSetsNonNullSortPolicy() {
+        assertNotNull(table.getSortPolicy());
+    }
+    
+    @Test public void noArgConstructorSetsNullComparator() {
+        assertNull(table.getComparator());
+    }
+    
+    @Test public void noArgConstructorSetsNullOnSort() {
+        assertNull(table.getOnSort());
+    }
 
     @Test public void noArgConstructor_selectedItemIsNull() {
         assertNull(sm.getSelectedItem());
@@ -285,11 +306,377 @@
         table.getColumns().addAll(first, second);
         table.getSortOrder().setAll(first, second);
         table.getColumns().remove(first);
-        assertEquals(false, table.getSortOrder().contains(first));
+        assertFalse(table.getSortOrder().contains(first));
     } 
     
     
     /*********************************************************************
+     * Tests for new sorting API in JavaFX 8.0                           *
+     ********************************************************************/
+    
+    // TODO test for sort policies returning null
+    // TODO test for changing column sortType out of order
+    // TODO test comparator returns to original when sort fails / is consumed
+    
+    private static final Callback<TableView<String>, Boolean> NO_SORT_FAILED_SORT_POLICY = 
+            new Callback<TableView<String>, Boolean>() {
+        @Override public Boolean call(TableView<String> tableView) {
+            return false;
+        }
+    };
+    
+    private static final Callback<TableView<String>, Boolean> SORT_SUCCESS_ASCENDING_SORT_POLICY = 
+            new Callback<TableView<String>, Boolean>() {
+        @Override public Boolean call(TableView<String> tableView) {
+            if (tableView.getSortOrder().isEmpty()) return true;
+            FXCollections.sort(tableView.getItems(), new Comparator<String>() {
+                @Override public int compare(String o1, String o2) {
+                    return o1.compareTo(o2);
+                }
+            });
+            return true;
+        }
+    };
+    
+    private TableColumn<String, String> initSortTestStructure() {
+        TableColumn<String, String> col = new TableColumn<String, String>("column");
+        col.setSortType(ASCENDING);
+        col.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<String, String>, ObservableValue<String>>() {
+            @Override public ObservableValue<String> call(TableColumn.CellDataFeatures<String, String> param) {
+                return new ReadOnlyObjectWrapper<String>(param.getValue());
+            }
+        });
+        table.getColumns().add(col);
+        table.getItems().addAll("Apple", "Orange", "Banana");
+        return col;
+    }
+    
+    @Ignore("This test is only valid if sort event consumption should revert changes")
+    @Test public void testSortEventCanBeConsumedToStopSortOccurring_changeSortOrderList() {
+        TableColumn<String, String> col = initSortTestStructure();
+        table.setOnSort(new EventHandler<SortEvent<TableView<String>>>() {
+            @Override public void handle(SortEvent<TableView<String>> event) {
+                event.consume();
+            }
+        });
+        
+        ControlAsserts.assertListContainsItemsInOrder(table.getItems(), "Apple", "Orange", "Banana");
+        table.getSortOrder().add(col);
+        ControlAsserts.assertListContainsItemsInOrder(table.getItems(), "Apple", "Orange", "Banana");
+        
+        // the sort order list should be returned back to its original state
+        assertTrue(table.getSortOrder().isEmpty());
+    }
+    
+    @Test public void testSortEventCanBeNotConsumedToAllowSortToOccur_changeSortOrderList() {
+        TableColumn<String, String> col = initSortTestStructure();
+        table.setOnSort(new EventHandler<SortEvent<TableView<String>>>() {
+            @Override public void handle(SortEvent<TableView<String>> event) {
+                // do not consume here - this allows the sort to happen
+            }
+        });
+        
+        ControlAsserts.assertListContainsItemsInOrder(table.getItems(), "Apple", "Orange", "Banana");
+        table.getSortOrder().add(col);
+        ControlAsserts.assertListContainsItemsInOrder(table.getItems(), "Apple", "Banana", "Orange");
+        
+        ControlAsserts.assertListContainsItemsInOrder(table.getSortOrder(), col);
+    }
+    
+    @Ignore("This test is only valid if sort event consumption should revert changes")
+    @Test public void testSortEventCanBeConsumedToStopSortOccurring_changeColumnSortType_AscendingToDescending() {
+        TableColumn<String, String> col = initSortTestStructure();
+        assertEquals(ASCENDING, col.getSortType());
+        table.getSortOrder().add(col);
+        table.setOnSort(new EventHandler<SortEvent<TableView<String>>>() {
+            @Override public void handle(SortEvent<TableView<String>> event) {
+                event.consume();
+            }
+        });
+        
+        ControlAsserts.assertListContainsItemsInOrder(table.getItems(), "Apple", "Banana", "Orange");
+        
+        // when we change from ASCENDING to DESCENDING we don't expect the sort
+        // to actually change (and in fact we expect the sort type to resort
+        // back to being ASCENDING)
+        col.setSortType(DESCENDING);
+        ControlAsserts.assertListContainsItemsInOrder(table.getItems(), "Apple", "Banana", "Orange");
+        assertEquals(ASCENDING, col.getSortType());
+        
+        ControlAsserts.assertListContainsItemsInOrder(table.getSortOrder(), col);
+    }
+    
+    @Test public void testSortEventCanBeNotConsumedToAllowSortToOccur_changeColumnSortType_AscendingToDescending() {
+        TableColumn<String, String> col = initSortTestStructure();
+        assertEquals(ASCENDING, col.getSortType());
+        table.getSortOrder().add(col);
+        table.setOnSort(new EventHandler<SortEvent<TableView<String>>>() {
+            @Override public void handle(SortEvent<TableView<String>> event) {
+                // do not consume here - this allows the sort to happen
+            }
+        });
+        
+        ControlAsserts.assertListContainsItemsInOrder(table.getItems(), "Apple", "Banana", "Orange");
+        
+        col.setSortType(DESCENDING);
+        ControlAsserts.assertListContainsItemsInOrder(table.getItems(), "Orange", "Banana", "Apple");
+        assertEquals(DESCENDING, col.getSortType());
+        
+        ControlAsserts.assertListContainsItemsInOrder(table.getSortOrder(), col);
+    }
+    
+    @Ignore("This test is only valid if sort event consumption should revert changes")
+    @Test public void testSortEventCanBeConsumedToStopSortOccurring_changeColumnSortType_DescendingToNull() {
+        TableColumn<String, String> col = initSortTestStructure();
+        col.setSortType(DESCENDING);
+        assertEquals(DESCENDING, col.getSortType());
+        table.getSortOrder().add(col);
+        table.setOnSort(new EventHandler<SortEvent<TableView<String>>>() {
+            @Override public void handle(SortEvent<TableView<String>> event) {
+                event.consume();
+            }
+        });
+        
+        ControlAsserts.assertListContainsItemsInOrder(table.getItems(), "Orange", "Banana", "Apple");
+        
+        col.setSortType(null);
+        ControlAsserts.assertListContainsItemsInOrder(table.getItems(), "Orange", "Banana", "Apple");
+        assertEquals(DESCENDING, col.getSortType());
+        
+        ControlAsserts.assertListContainsItemsInOrder(table.getSortOrder(), col);
+    }
+    
+    @Test public void testSortEventCanBeNotConsumedToAllowSortToOccur_changeColumnSortType_DescendingToNull() {
+        TableColumn<String, String> col = initSortTestStructure();
+        col.setSortType(DESCENDING);
+        assertEquals(DESCENDING, col.getSortType());
+        table.getSortOrder().add(col);
+        table.setOnSort(new EventHandler<SortEvent<TableView<String>>>() {
+            @Override public void handle(SortEvent<TableView<String>> event) {
+                // do not consume here - this allows the sort to happen
+            }
+        });
+        
+        ControlAsserts.assertListContainsItemsInOrder(table.getItems(), "Orange", "Banana", "Apple");
+        
+        col.setSortType(null);
+        ControlAsserts.assertListContainsItemsInOrder(table.getItems(), "Orange", "Banana", "Apple");
+        assertNull(col.getSortType());
+        
+        ControlAsserts.assertListContainsItemsInOrder(table.getSortOrder(), col);
+    }
+    
+    @Ignore("This test is only valid if sort event consumption should revert changes")
+    @Test public void testSortEventCanBeConsumedToStopSortOccurring_changeColumnSortType_NullToAscending() {
+        TableColumn<String, String> col = initSortTestStructure();
+        col.setSortType(null);
+        assertNull(col.getSortType());
+        table.getSortOrder().add(col);
+        table.setOnSort(new EventHandler<SortEvent<TableView<String>>>() {
+            @Override public void handle(SortEvent<TableView<String>> event) {
+                event.consume();
+            }
+        });
+        
+        ControlAsserts.assertListContainsItemsInOrder(table.getItems(), "Apple", "Orange", "Banana");
+        
+        col.setSortType(ASCENDING);
+        ControlAsserts.assertListContainsItemsInOrder(table.getItems(), "Apple", "Orange", "Banana");
+        assertNull(col.getSortType());
+        
+        ControlAsserts.assertListContainsItemsInOrder(table.getSortOrder(), col);
+    }
+    
+    @Test public void testSortEventCanBeNotConsumedToAllowSortToOccur_changeColumnSortType_NullToAscending() {
+        TableColumn<String, String> col = initSortTestStructure();
+        col.setSortType(null);
+        assertNull(col.getSortType());
+        table.getSortOrder().add(col);
+        table.setOnSort(new EventHandler<SortEvent<TableView<String>>>() {
+            @Override public void handle(SortEvent<TableView<String>> event) {
+                // do not consume here - this allows the sort to happen
+            }
+        });
+        
+        ControlAsserts.assertListContainsItemsInOrder(table.getItems(), "Apple", "Orange", "Banana");
+        
+        col.setSortType(ASCENDING);
+        ControlAsserts.assertListContainsItemsInOrder(table.getItems(), "Apple", "Banana", "Orange");
+        assertEquals(ASCENDING, col.getSortType());
+        
+        ControlAsserts.assertListContainsItemsInOrder(table.getSortOrder(), col);
+    }
+
+    @Test public void testSortMethodWithNullSortPolicy() {
+        TableColumn<String, String> col = initSortTestStructure();
+        table.setSortPolicy(null);
+        assertNull(table.getSortPolicy());
+        table.sort();
+    }
+    
+    @Test public void testChangingSortPolicyUpdatesItemsList() {
+        TableColumn<String, String> col = initSortTestStructure();
+        col.setSortType(DESCENDING);
+        ControlAsserts.assertListContainsItemsInOrder(table.getItems(), "Apple", "Orange", "Banana");
+        table.getSortOrder().add(col);
+        ControlAsserts.assertListContainsItemsInOrder(table.getItems(), "Orange", "Banana", "Apple");
+        table.setSortPolicy(SORT_SUCCESS_ASCENDING_SORT_POLICY);
+        ControlAsserts.assertListContainsItemsInOrder(table.getItems(), "Apple", "Banana", "Orange");
+    }
+    
+    @Test public void testChangingSortPolicyDoesNotUpdateItemsListWhenTheSortOrderListIsEmpty() {
+        TableColumn<String, String> col = initSortTestStructure();
+        col.setSortType(DESCENDING);
+        ControlAsserts.assertListContainsItemsInOrder(table.getItems(), "Apple", "Orange", "Banana");
+        
+        table.setSortPolicy(SORT_SUCCESS_ASCENDING_SORT_POLICY);
+        ControlAsserts.assertListContainsItemsInOrder(table.getItems(), "Apple", "Orange", "Banana");
+    }
+    
+    @Test public void testFailedSortPolicyBacksOutLastChange_sortOrderAddition() {
+        TableColumn<String, String> col = initSortTestStructure();
+        col.setSortType(DESCENDING);
+        ControlAsserts.assertListContainsItemsInOrder(table.getItems(), "Apple", "Orange", "Banana");
+        table.setSortPolicy(NO_SORT_FAILED_SORT_POLICY);
+        
+        table.getSortOrder().add(col);
+        
+        // no sort should be run (as we have a custom sort policy), and the 
+        // sortOrder list should be empty as the sortPolicy failed
+        ControlAsserts.assertListContainsItemsInOrder(table.getItems(), "Apple", "Orange", "Banana");
+        assertTrue(table.getSortOrder().isEmpty());
+    }
+    
+    @Test public void testFailedSortPolicyBacksOutLastChange_sortOrderRemoval() {
+        TableColumn<String, String> col = initSortTestStructure();
+        col.setSortType(DESCENDING);
+        table.getSortOrder().add(col);
+        ControlAsserts.assertListContainsItemsInOrder(table.getItems(), "Orange", "Banana", "Apple");
+        
+        table.setSortPolicy(NO_SORT_FAILED_SORT_POLICY);
+
+        // even though we remove the column from the sort order here, because the
+        // sort policy fails the items list should remain unchanged and the sort
+        // order list should continue to have the column in it.
+        table.getSortOrder().remove(col);
+        ControlAsserts.assertListContainsItemsInOrder(table.getItems(), "Orange", "Banana", "Apple");
+        ControlAsserts.assertListContainsItemsInOrder(table.getSortOrder(), col);
+    }
+    
+    @Test public void testFailedSortPolicyBacksOutLastChange_sortTypeChange_ascendingToDescending() {
+        TableColumn<String, String> col = initSortTestStructure();
+        col.setSortType(ASCENDING);
+        table.getSortOrder().add(col);
+        ControlAsserts.assertListContainsItemsInOrder(table.getItems(), "Apple", "Banana", "Orange");
+        
+        table.setSortPolicy(NO_SORT_FAILED_SORT_POLICY);
+
+        col.setSortType(DESCENDING);
+        ControlAsserts.assertListContainsItemsInOrder(table.getItems(), "Apple", "Banana", "Orange");
+        assertEquals(ASCENDING, col.getSortType());
+    }
+    
+    @Test public void testFailedSortPolicyBacksOutLastChange_sortTypeChange_descendingToNull() {
+        TableColumn<String, String> col = initSortTestStructure();
+        col.setSortType(DESCENDING);
+        table.getSortOrder().add(col);
+        ControlAsserts.assertListContainsItemsInOrder(table.getItems(), "Orange", "Banana", "Apple");
+        
+        table.setSortPolicy(NO_SORT_FAILED_SORT_POLICY);
+
+        col.setSortType(null);
+        ControlAsserts.assertListContainsItemsInOrder(table.getItems(), "Orange", "Banana", "Apple");
+        assertEquals(DESCENDING, col.getSortType());
+    }
+    
+    @Test public void testFailedSortPolicyBacksOutLastChange_sortTypeChange_nullToAscending() {
+        TableColumn<String, String> col = initSortTestStructure();
+        col.setSortType(null);
+        table.getSortOrder().add(col);
+        ControlAsserts.assertListContainsItemsInOrder(table.getItems(), "Apple", "Orange", "Banana");
+        
+        table.setSortPolicy(NO_SORT_FAILED_SORT_POLICY);
+
+        col.setSortType(ASCENDING);
+        ControlAsserts.assertListContainsItemsInOrder(table.getItems(), "Apple", "Orange", "Banana");
+        assertNull(col.getSortType());
+    }
+    
+    @Test public void testComparatorChangesInSyncWithSortOrder_1() {
+        TableColumn<String, String> col = initSortTestStructure();
+        assertNull(table.getComparator());
+        assertTrue(table.getSortOrder().isEmpty());
+        
+        table.getSortOrder().add(col);
+        TableColumnComparator c = (TableColumnComparator)table.getComparator();
+        assertNotNull(c);
+        ControlAsserts.assertListContainsItemsInOrder(c.getColumns(), col);
+    }
+    
+    @Test public void testComparatorChangesInSyncWithSortOrder_2() {
+        // same as test above
+        TableColumn<String, String> col = initSortTestStructure();
+        assertNull(table.getComparator());
+        assertTrue(table.getSortOrder().isEmpty());
+        
+        table.getSortOrder().add(col);
+        TableColumnComparator c = (TableColumnComparator)table.getComparator();
+        assertNotNull(c);
+        ControlAsserts.assertListContainsItemsInOrder(c.getColumns(), col);
+        
+        // now remove column from sort order, and the comparator should go to
+        // being null
+        table.getSortOrder().remove(col);
+        assertNull(table.getComparator());
+    }
+    
+    @Test public void testFailedSortPolicyBacksOutComparatorChange_sortOrderAddition() {
+        TableColumn<String, String> col = initSortTestStructure();
+        final TableColumnComparator oldComparator = (TableColumnComparator)table.getComparator();
+        
+        col.setSortType(DESCENDING);
+        ControlAsserts.assertListContainsItemsInOrder(table.getItems(), "Apple", "Orange", "Banana");
+        table.setSortPolicy(NO_SORT_FAILED_SORT_POLICY);
+        
+        table.getSortOrder().add(col);
+        
+        assertEquals(oldComparator, table.getComparator());
+    }
+    
+    @Test public void testFailedSortPolicyBacksOutComparatorChange_sortOrderRemoval() {
+        TableColumn<String, String> col = initSortTestStructure();
+        TableColumnComparator oldComparator = (TableColumnComparator)table.getComparator();
+        assertNull(oldComparator);
+
+        col.setSortType(DESCENDING);
+        table.getSortOrder().add(col);
+        ControlAsserts.assertListContainsItemsInOrder(table.getItems(), "Orange", "Banana", "Apple");
+        oldComparator = (TableColumnComparator)table.getComparator();
+        ControlAsserts.assertListContainsItemsInOrder(oldComparator.getColumns(), col);
+        
+        table.setSortPolicy(NO_SORT_FAILED_SORT_POLICY);
+        table.getSortOrder().remove(col);
+        
+        assertTrue(table.getSortOrder().contains(col));
+        ControlAsserts.assertListContainsItemsInOrder(oldComparator.getColumns(), col);
+    }
+    
+    @Test public void testFailedSortPolicyBacksOutComparatorChange_sortTypeChange() {
+        TableColumn<String, String> col = initSortTestStructure();
+        final TableColumnComparator oldComparator = (TableColumnComparator)table.getComparator();
+        assertNull(oldComparator);
+        
+        table.setSortPolicy(NO_SORT_FAILED_SORT_POLICY);
+        table.getSortOrder().add(col);
+        col.setSortType(ASCENDING);
+        
+        assertTrue(table.getSortOrder().isEmpty());
+        assertNull(oldComparator);
+    }
+    
+    
+    
+    /*********************************************************************
      * Tests for specific bugs                                           *
      ********************************************************************/
     @Test public void test_rt16019() {
--- a/javafx-ui-controls/test/javafx/scene/control/TreeTableViewTest.java	Thu Mar 07 18:37:29 2013 -0800
+++ b/javafx-ui-controls/test/javafx/scene/control/TreeTableViewTest.java	Fri Mar 08 15:49:08 2013 +1300
@@ -25,22 +25,28 @@
 
 package javafx.scene.control;
 
+import com.sun.javafx.scene.control.TableColumnComparator;
 import com.sun.javafx.scene.control.test.ControlAsserts;
 import com.sun.javafx.scene.control.test.Person;
 import com.sun.javafx.scene.control.test.RT_22463_Person;
 import java.util.Arrays;
+import java.util.Comparator;
 import javafx.beans.InvalidationListener;
 import javafx.beans.Observable;
 import static javafx.scene.control.ControlTestUtils.assertStyleClassContains;
 import static org.junit.Assert.*;
 import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.ReadOnlyObjectWrapper;
 import javafx.beans.property.SimpleObjectProperty;
 import javafx.beans.value.ChangeListener;
 import javafx.beans.value.ObservableValue;
 import javafx.collections.FXCollections;
 import javafx.collections.ObservableList;
+import javafx.event.EventHandler;
 import javafx.scene.Group;
 import javafx.scene.Scene;
+import static javafx.scene.control.TableColumnBase.SortType.ASCENDING;
+import static javafx.scene.control.TableColumnBase.SortType.DESCENDING;
 import javafx.scene.control.cell.PropertyValueFactory;
 import javafx.scene.control.cell.TreeItemPropertyValueFactory;
 import javafx.stage.Stage;
@@ -163,6 +169,18 @@
     @Test public void noArgConstructor_selectedIndexIsNegativeOne() {
         assertEquals(-1, sm.getSelectedIndex());
     }
+    
+    @Test public void noArgConstructorSetsNonNullSortPolicy() {
+        assertNotNull(treeTableView.getSortPolicy());
+    }
+    
+    @Test public void noArgConstructorSetsNullComparator() {
+        assertNull(treeTableView.getComparator());
+    }
+    
+    @Test public void noArgConstructorSetsNullOnSort() {
+        assertNull(treeTableView.getOnSort());
+    }
 
     @Test public void singleArgConstructorSetsNonNullSelectionModel() {
         final TableView<String> b2 = new TableView<String>(FXCollections.observableArrayList("Hi"));
@@ -221,8 +239,7 @@
     }
     
     @Test public void testSortOrderCleanup() {
-//        ObservableList<ObservablePerson> persons = ObservablePerson.createFXPersonList();
-        TreeTableView table = new TreeTableView();
+        TreeTableView treeTableView = new TreeTableView();
         TreeTableColumn<String,String> first = new TreeTableColumn<String,String>("first");
         first.setCellValueFactory(new PropertyValueFactory("firstName"));
         TreeTableColumn<String,String> second = new TreeTableColumn<String,String>("second");
@@ -230,11 +247,387 @@
         treeTableView.getColumns().addAll(first, second);
         treeTableView.getSortOrder().setAll(first, second);
         treeTableView.getColumns().remove(first);
-        assertEquals(false, treeTableView.getSortOrder().contains(first));
+        assertFalse(treeTableView.getSortOrder().contains(first));
     } 
     
     
     /*********************************************************************
+     * Tests for new sorting API in JavaFX 8.0                           *
+     ********************************************************************/
+    
+    private TreeItem<String> apple, orange, banana;
+    
+    // TODO test for sort policies returning null
+    // TODO test for changing column sortType out of order
+    
+    private static final Callback<TreeTableView<String>, Boolean> NO_SORT_FAILED_SORT_POLICY = 
+            new Callback<TreeTableView<String>, Boolean>() {
+        @Override public Boolean call(TreeTableView<String> treeTableView) {
+            return false;
+        }
+    };
+    
+    private static final Callback<TreeTableView<String>, Boolean> SORT_SUCCESS_ASCENDING_SORT_POLICY = 
+            new Callback<TreeTableView<String>, Boolean>() {
+        @Override public Boolean call(TreeTableView<String> treeTableView) {
+            if (treeTableView.getSortOrder().isEmpty()) return true;
+            FXCollections.sort(treeTableView.getRoot().getChildren(), new Comparator<TreeItem<String>>() {
+                @Override public int compare(TreeItem<String> o1, TreeItem<String> o2) {
+                    return o1.getValue().compareTo(o2.getValue());
+                }
+            });
+            return true;
+        }
+    };
+    
+    private TreeTableColumn<String, String> initSortTestStructure() {
+        TreeTableColumn<String, String> col = new TreeTableColumn<String, String>("column");
+        col.setSortType(ASCENDING);
+        col.setCellValueFactory(new Callback<TreeTableColumn.CellDataFeatures<String, String>, ObservableValue<String>>() {
+            @Override public ObservableValue<String> call(TreeTableColumn.CellDataFeatures<String, String> param) {
+                return new ReadOnlyObjectWrapper<String>(param.getValue().getValue());
+            }
+        });
+        treeTableView.getColumns().add(col);
+        
+        TreeItem<String> newRoot = new TreeItem<String>("root");
+        newRoot.setExpanded(true);
+        newRoot.getChildren().addAll(
+                apple  = new TreeItem("Apple"), 
+                orange = new TreeItem("Orange"), 
+                banana = new TreeItem("Banana"));
+        
+        treeTableView.setRoot(newRoot);
+        
+        return col;
+    }
+    
+    @Ignore("This test is only valid if sort event consumption should revert changes")
+    @Test public void testSortEventCanBeConsumedToStopSortOccurring_changeSortOrderList() {
+        TreeTableColumn<String, String> col = initSortTestStructure();
+        treeTableView.setOnSort(new EventHandler<SortEvent<TreeTableView<String>>>() {
+            @Override public void handle(SortEvent<TreeTableView<String>> event) {
+                event.consume();
+            }
+        });
+        
+        ControlAsserts.assertListContainsItemsInOrder(treeTableView.getRoot().getChildren(), apple, orange, banana);
+        treeTableView.getSortOrder().add(col);
+        ControlAsserts.assertListContainsItemsInOrder(treeTableView.getRoot().getChildren(), apple, orange, banana);
+        
+        // the sort order list should be returned back to its original state
+        assertTrue(treeTableView.getSortOrder().isEmpty());
+    }
+    
+    @Test public void testSortEventCanBeNotConsumedToAllowSortToOccur_changeSortOrderList() {
+        TreeTableColumn<String, String> col = initSortTestStructure();
+        treeTableView.setOnSort(new EventHandler<SortEvent<TreeTableView<String>>>() {
+            @Override public void handle(SortEvent<TreeTableView<String>> event) {
+                // do not consume here - this allows the sort to happen
+            }
+        });
+        
+        ControlAsserts.assertListContainsItemsInOrder(treeTableView.getRoot().getChildren(), apple, orange, banana);
+        treeTableView.getSortOrder().add(col);
+        ControlAsserts.assertListContainsItemsInOrder(treeTableView.getRoot().getChildren(), apple, banana, orange);
+        
+        ControlAsserts.assertListContainsItemsInOrder(treeTableView.getSortOrder(), col);
+    }
+    
+    @Ignore("This test is only valid if sort event consumption should revert changes")
+    @Test public void testSortEventCanBeConsumedToStopSortOccurring_changeColumnSortType_AscendingToDescending() {
+        TreeTableColumn<String, String> col = initSortTestStructure();
+        assertEquals(ASCENDING, col.getSortType());
+        treeTableView.getSortOrder().add(col);
+        treeTableView.setOnSort(new EventHandler<SortEvent<TreeTableView<String>>>() {
+            @Override public void handle(SortEvent<TreeTableView<String>> event) {
+                event.consume();
+            }
+        });
+        
+        ControlAsserts.assertListContainsItemsInOrder(treeTableView.getRoot().getChildren(), apple, banana, orange);
+        
+        // when we change from ASCENDING to DESCENDING we don't expect the sort
+        // to actually change (and in fact we expect the sort type to resort
+        // back to being ASCENDING)
+        col.setSortType(DESCENDING);
+        ControlAsserts.assertListContainsItemsInOrder(treeTableView.getRoot().getChildren(), apple, banana, orange);
+        assertEquals(ASCENDING, col.getSortType());
+        
+        ControlAsserts.assertListContainsItemsInOrder(treeTableView.getSortOrder(), col);
+    }
+    
+    @Test public void testSortEventCanBeNotConsumedToAllowSortToOccur_changeColumnSortType_AscendingToDescending() {
+        TreeTableColumn<String, String> col = initSortTestStructure();
+        assertEquals(ASCENDING, col.getSortType());
+        treeTableView.getSortOrder().add(col);
+        treeTableView.setOnSort(new EventHandler<SortEvent<TreeTableView<String>>>() {
+            @Override public void handle(SortEvent<TreeTableView<String>> event) {
+                // do not consume here - this allows the sort to happen
+            }
+        });
+        
+        ControlAsserts.assertListContainsItemsInOrder(treeTableView.getRoot().getChildren(), apple, banana, orange);
+        
+        col.setSortType(DESCENDING);
+        ControlAsserts.assertListContainsItemsInOrder(treeTableView.getRoot().getChildren(), orange, banana, apple);
+        assertEquals(DESCENDING, col.getSortType());
+        
+        ControlAsserts.assertListContainsItemsInOrder(treeTableView.getSortOrder(), col);
+    }
+    
+    @Ignore("This test is only valid if sort event consumption should revert changes")
+    @Test public void testSortEventCanBeConsumedToStopSortOccurring_changeColumnSortType_DescendingToNull() {
+        TreeTableColumn<String, String> col = initSortTestStructure();
+        col.setSortType(DESCENDING);
+        assertEquals(DESCENDING, col.getSortType());
+        treeTableView.getSortOrder().add(col);
+        treeTableView.setOnSort(new EventHandler<SortEvent<TreeTableView<String>>>() {
+            @Override public void handle(SortEvent<TreeTableView<String>> event) {
+                event.consume();
+            }
+        });
+        
+        ControlAsserts.assertListContainsItemsInOrder(treeTableView.getRoot().getChildren(), orange, banana, apple);
+        
+        col.setSortType(null);
+        ControlAsserts.assertListContainsItemsInOrder(treeTableView.getRoot().getChildren(), orange, banana, apple);
+        assertEquals(DESCENDING, col.getSortType());
+        
+        ControlAsserts.assertListContainsItemsInOrder(treeTableView.getSortOrder(), col);
+    }
+    
+    @Test public void testSortEventCanBeNotConsumedToAllowSortToOccur_changeColumnSortType_DescendingToNull() {
+        TreeTableColumn<String, String> col = initSortTestStructure();
+        col.setSortType(DESCENDING);
+        assertEquals(DESCENDING, col.getSortType());
+        treeTableView.getSortOrder().add(col);
+        treeTableView.setOnSort(new EventHandler<SortEvent<TreeTableView<String>>>() {
+            @Override public void handle(SortEvent<TreeTableView<String>> event) {
+                // do not consume here - this allows the sort to happen
+            }
+        });
+        
+        ControlAsserts.assertListContainsItemsInOrder(treeTableView.getRoot().getChildren(), orange, banana, apple);
+        
+        col.setSortType(null);
+        ControlAsserts.assertListContainsItemsInOrder(treeTableView.getRoot().getChildren(), orange, banana, apple);
+        assertNull(col.getSortType());
+        
+        ControlAsserts.assertListContainsItemsInOrder(treeTableView.getSortOrder(), col);
+    }
+    
+    @Ignore("This test is only valid if sort event consumption should revert changes")
+    @Test public void testSortEventCanBeConsumedToStopSortOccurring_changeColumnSortType_NullToAscending() {
+        TreeTableColumn<String, String> col = initSortTestStructure();
+        col.setSortType(null);
+        assertNull(col.getSortType());
+        treeTableView.getSortOrder().add(col);
+        treeTableView.setOnSort(new EventHandler<SortEvent<TreeTableView<String>>>() {
+            @Override public void handle(SortEvent<TreeTableView<String>> event) {
+                event.consume();
+            }
+        });
+        
+        ControlAsserts.assertListContainsItemsInOrder(treeTableView.getRoot().getChildren(), apple, orange, banana);
+        
+        col.setSortType(ASCENDING);
+        ControlAsserts.assertListContainsItemsInOrder(treeTableView.getRoot().getChildren(), apple, orange, banana);
+        assertNull(col.getSortType());
+        
+        ControlAsserts.assertListContainsItemsInOrder(treeTableView.getSortOrder(), col);
+    }
+    
+    @Test public void testSortEventCanBeNotConsumedToAllowSortToOccur_changeColumnSortType_NullToAscending() {
+        TreeTableColumn<String, String> col = initSortTestStructure();
+        col.setSortType(null);
+        assertNull(col.getSortType());
+        treeTableView.getSortOrder().add(col);
+        treeTableView.setOnSort(new EventHandler<SortEvent<TreeTableView<String>>>() {
+            @Override public void handle(SortEvent<TreeTableView<String>> event) {
+                // do not consume here - this allows the sort to happen
+            }
+        });
+        
+        ControlAsserts.assertListContainsItemsInOrder(treeTableView.getRoot().getChildren(), apple, orange, banana);
+        
+        col.setSortType(ASCENDING);
+        ControlAsserts.assertListContainsItemsInOrder(treeTableView.getRoot().getChildren(), apple, banana, orange);
+        assertEquals(ASCENDING, col.getSortType());
+        
+        ControlAsserts.assertListContainsItemsInOrder(treeTableView.getSortOrder(), col);
+    }
+    
+    @Test public void testSortMethodWithNullSortPolicy() {
+        TreeTableColumn<String, String> col = initSortTestStructure();
+        treeTableView.setSortPolicy(null);
+        assertNull(treeTableView.getSortPolicy());
+        treeTableView.sort();
+    }
+    
+    @Test public void testChangingSortPolicyUpdatesItemsList() {
+        TreeTableColumn<String, String> col = initSortTestStructure();
+        col.setSortType(DESCENDING);
+        ControlAsserts.assertListContainsItemsInOrder(treeTableView.getRoot().getChildren(), apple, orange, banana);
+        treeTableView.getSortOrder().add(col);
+        ControlAsserts.assertListContainsItemsInOrder(treeTableView.getRoot().getChildren(), orange, banana, apple);
+        treeTableView.setSortPolicy(SORT_SUCCESS_ASCENDING_SORT_POLICY);
+        ControlAsserts.assertListContainsItemsInOrder(treeTableView.getRoot().getChildren(), apple, banana, orange);
+    }
+    
+    @Test public void testChangingSortPolicyDoesNotUpdateItemsListWhenTheSortOrderListIsEmpty() {
+        TreeTableColumn<String, String> col = initSortTestStructure();
+        col.setSortType(DESCENDING);
+        ControlAsserts.assertListContainsItemsInOrder(treeTableView.getRoot().getChildren(), apple, orange, banana);
+        
+        treeTableView.setSortPolicy(SORT_SUCCESS_ASCENDING_SORT_POLICY);
+        ControlAsserts.assertListContainsItemsInOrder(treeTableView.getRoot().getChildren(), apple, orange, banana);
+    }
+    
+    @Test public void testFailedSortPolicyBacksOutLastChange_sortOrderAddition() {
+        TreeTableColumn<String, String> col = initSortTestStructure();
+        col.setSortType(DESCENDING);
+        ControlAsserts.assertListContainsItemsInOrder(treeTableView.getRoot().getChildren(), apple, orange, banana);
+        treeTableView.setSortPolicy(NO_SORT_FAILED_SORT_POLICY);
+        
+        treeTableView.getSortOrder().add(col);
+        
+        // no sort should be run (as we have a custom sort policy), and the 
+        // sortOrder list should be empty as the sortPolicy failed
+        ControlAsserts.assertListContainsItemsInOrder(treeTableView.getRoot().getChildren(), apple, orange, banana);
+        assertTrue(treeTableView.getSortOrder().isEmpty());
+    }
+    
+    @Test public void testFailedSortPolicyBacksOutLastChange_sortOrderRemoval() {
+        TreeTableColumn<String, String> col = initSortTestStructure();
+        col.setSortType(DESCENDING);
+        treeTableView.getSortOrder().add(col);
+        ControlAsserts.assertListContainsItemsInOrder(treeTableView.getRoot().getChildren(), orange, banana, apple);
+        
+        treeTableView.setSortPolicy(NO_SORT_FAILED_SORT_POLICY);
+
+        // even though we remove the column from the sort order here, because the
+        // sort policy fails the items list should remain unchanged and the sort
+        // order list should continue to have the column in it.
+        treeTableView.getSortOrder().remove(col);
+        ControlAsserts.assertListContainsItemsInOrder(treeTableView.getRoot().getChildren(), orange, banana, apple);
+        ControlAsserts.assertListContainsItemsInOrder(treeTableView.getSortOrder(), col);
+    }
+    
+    @Test public void testFailedSortPolicyBacksOutLastChange_sortTypeChange_ascendingToDescending() {
+        TreeTableColumn<String, String> col = initSortTestStructure();
+        col.setSortType(ASCENDING);
+        treeTableView.getSortOrder().add(col);
+        ControlAsserts.assertListContainsItemsInOrder(treeTableView.getRoot().getChildren(), apple, banana, orange);
+        
+        treeTableView.setSortPolicy(NO_SORT_FAILED_SORT_POLICY);
+
+        col.setSortType(DESCENDING);
+        ControlAsserts.assertListContainsItemsInOrder(treeTableView.getRoot().getChildren(), apple, banana, orange);
+        assertEquals(ASCENDING, col.getSortType());
+    }
+    
+    @Test public void testFailedSortPolicyBacksOutLastChange_sortTypeChange_descendingToNull() {
+        TreeTableColumn<String, String> col = initSortTestStructure();
+        col.setSortType(DESCENDING);
+        treeTableView.getSortOrder().add(col);
+        ControlAsserts.assertListContainsItemsInOrder(treeTableView.getRoot().getChildren(), orange, banana, apple);
+        
+        treeTableView.setSortPolicy(NO_SORT_FAILED_SORT_POLICY);
+
+        col.setSortType(null);
+        ControlAsserts.assertListContainsItemsInOrder(treeTableView.getRoot().getChildren(), orange, banana, apple);
+        assertEquals(DESCENDING, col.getSortType());
+    }
+    
+    @Test public void testFailedSortPolicyBacksOutLastChange_sortTypeChange_nullToAscending() {
+        TreeTableColumn<String, String> col = initSortTestStructure();
+        col.setSortType(null);
+        treeTableView.getSortOrder().add(col);
+        ControlAsserts.assertListContainsItemsInOrder(treeTableView.getRoot().getChildren(), apple, orange, banana);
+        
+        treeTableView.setSortPolicy(NO_SORT_FAILED_SORT_POLICY);
+
+        col.setSortType(ASCENDING);
+        ControlAsserts.assertListContainsItemsInOrder(treeTableView.getRoot().getChildren(), apple, orange, banana);
+        assertNull(col.getSortType());
+    }
+    
+    @Test public void testComparatorChangesInSyncWithSortOrder_1() {
+        TreeTableColumn<String, String> col = initSortTestStructure();
+        assertNull(treeTableView.getComparator());
+        assertTrue(treeTableView.getSortOrder().isEmpty());
+        
+        treeTableView.getSortOrder().add(col);
+        TableColumnComparator c = (TableColumnComparator)treeTableView.getComparator();
+        assertNotNull(c);
+        ControlAsserts.assertListContainsItemsInOrder(c.getColumns(), col);
+    }
+    
+    @Test public void testComparatorChangesInSyncWithSortOrder_2() {
+        // same as test above
+        TreeTableColumn<String, String> col = initSortTestStructure();
+        assertNull(treeTableView.getComparator());
+        assertTrue(treeTableView.getSortOrder().isEmpty());
+        
+        treeTableView.getSortOrder().add(col);
+        TableColumnComparator c = (TableColumnComparator)treeTableView.getComparator();
+        assertNotNull(c);
+        ControlAsserts.assertListContainsItemsInOrder(c.getColumns(), col);
+        
+        // now remove column from sort order, and the comparator should go to
+        // being null
+        treeTableView.getSortOrder().remove(col);
+        assertNull(treeTableView.getComparator());
+    }
+    
+    @Test public void testFailedSortPolicyBacksOutComparatorChange_sortOrderAddition() {
+        TreeTableColumn<String, String> col = initSortTestStructure();
+        final TableColumnComparator oldComparator = (TableColumnComparator)treeTableView.getComparator();
+        
+        col.setSortType(DESCENDING);
+        ControlAsserts.assertListContainsItemsInOrder(treeTableView.getRoot().getChildren(), apple, orange, banana);
+        treeTableView.setSortPolicy(NO_SORT_FAILED_SORT_POLICY);
+        
+        treeTableView.getSortOrder().add(col);
+        
+        assertEquals(oldComparator, treeTableView.getComparator());
+    }
+    
+    @Test public void testFailedSortPolicyBacksOutComparatorChange_sortOrderRemoval() {
+        TreeTableColumn<String, String> col = initSortTestStructure();
+        TableColumnComparator oldComparator = (TableColumnComparator)treeTableView.getComparator();
+        assertNull(oldComparator);
+
+        col.setSortType(DESCENDING);
+        treeTableView.getSortOrder().add(col);
+        ControlAsserts.assertListContainsItemsInOrder(treeTableView.getRoot().getChildren(), orange, banana, apple);
+        oldComparator = (TableColumnComparator)treeTableView.getComparator();
+        ControlAsserts.assertListContainsItemsInOrder(oldComparator.getColumns(), col);
+        
+        treeTableView.setSortPolicy(NO_SORT_FAILED_SORT_POLICY);
+        treeTableView.getSortOrder().remove(col);
+        
+        assertTrue(treeTableView.getSortOrder().contains(col));
+        ControlAsserts.assertListContainsItemsInOrder(oldComparator.getColumns(), col);
+    }
+    
+    @Test public void testFailedSortPolicyBacksOutComparatorChange_sortTypeChange() {
+        TreeTableColumn<String, String> col = initSortTestStructure();
+        final TableColumnComparator oldComparator = (TableColumnComparator)treeTableView.getComparator();
+        assertNull(oldComparator);
+        
+        treeTableView.setSortPolicy(NO_SORT_FAILED_SORT_POLICY);
+        treeTableView.getSortOrder().add(col);
+        col.setSortType(ASCENDING);
+        
+        assertTrue(treeTableView.getSortOrder().isEmpty());
+        assertNull(oldComparator);
+    }
+    
+    
+    
+    /*********************************************************************
      * Tests for specific bugs                                           *
      ********************************************************************/
     @Test public void test_rt16019() {