changeset 2876:636b8f96d38a

Merge
author "Jasper Potts"
date Mon, 11 Mar 2013 13:48:13 -0700
parents 479e1c3e1371 36fcf9f812ed
children c0cc2ab04805
files
diffstat 30 files changed, 1589 insertions(+), 309 deletions(-) [+]
line wrap: on
line diff
--- a/.hgtags	Thu Mar 07 18:46:53 2013 -0800
+++ b/.hgtags	Mon Mar 11 13:48:13 2013 -0700
@@ -67,3 +67,4 @@
 b2dfc3891302f4bf8971da20df9eeda4f903ee5a 8.0-b77
 974340922b3a5a93e1f800c0f5d777b24e3e2480 8.0-b78
 06afa65a1aa394416355346f159289f244629abe 8.0-b79
+637a0d67106fb60e2d35d02230be6034afc12f44 8.0-b80
--- a/javafx-ui-common/src/com/sun/javafx/css/StyleManager.java	Thu Mar 07 18:46:53 2013 -0800
+++ b/javafx-ui-common/src/com/sun/javafx/css/StyleManager.java	Mon Mar 11 13:48:13 2013 -0700
@@ -64,6 +64,8 @@
 import javafx.css.CssMetaData;
 import javafx.css.PseudoClass;
 import javafx.css.StyleOrigin;
+import javafx.scene.image.Image;
+import javafx.util.Pair;
 import sun.util.logging.PlatformLogger;
 
 /**
@@ -224,6 +226,10 @@
         // This list should also be fairly small
         final RefList<Key> keys;
         
+        // RT-24516 -- cache images coming from this stylesheet.
+        // This just holds a hard reference to the image. 
+        final List<Image> imageCache;
+        
         final int hash;
 
         StylesheetContainer(String fname, Stylesheet stylesheet) {
@@ -256,6 +262,9 @@
             this.sceneUsers = new RefList<Scene>();
             this.parentUsers = new RefList<Parent>();
             this.keys = new RefList<Key>();
+            
+            // this just holds a hard reference to the image
+            this.imageCache = new ArrayList<Image>();
         }
 
         @Override
@@ -398,7 +407,11 @@
     private void clearCache(StylesheetContainer sc) {
 
         removeFromCacheMap(sc);
-            
+        
+        // clean up image cache by removing images from the cache that 
+        // might have come from this stylesheet
+        cleanUpImageCache(sc.fname);
+                           
         final List<Reference<Scene>> sceneList = sc.sceneUsers.list;
         final List<Reference<Parent>> parentList = sc.parentUsers.list;
                         
@@ -479,6 +492,73 @@
         }
     }
     
+    ////////////////////////////////////////////////////////////////////////////
+    //
+    // Image caching
+    //
+    ////////////////////////////////////////////////////////////////////////////
+    
+    Map<String,Image> imageCache = new HashMap<String,Image>();
+    
+    public Image getCachedImage(String url) {
+    
+        Image image = imageCache.get(url);
+        if (image == null) {
+            
+            try {
+
+                image = new Image(url);
+                imageCache.put(url, image);
+                
+            } catch (IllegalArgumentException iae) {
+                // url was empty! 
+                final PlatformLogger logger = getLogger();
+                if (logger != null && logger.isLoggable(PlatformLogger.WARNING)) {
+                        LOGGER.warning(iae.getLocalizedMessage());
+                }
+
+            } catch (NullPointerException npe) {
+                // url was null!
+                final PlatformLogger logger = getLogger();
+                if (logger != null && logger.isLoggable(PlatformLogger.WARNING)) {
+                        LOGGER.warning(npe.getLocalizedMessage());
+                }
+            }
+        } 
+        
+        return image;
+    }
+    
+    private void cleanUpImageCache(String fname) {
+
+        if (fname == null && imageCache.isEmpty()) return;
+        if (fname.trim().isEmpty()) return;
+
+        int len = fname.lastIndexOf('/');
+        final String path = (len > 0) ? fname.substring(0,len) : fname;
+        final int plen = path.length();
+        
+        final String[] entriesToRemove = new String[imageCache.size()];
+        int count = 0;
+        
+        final Set<Entry<String, Image>> entrySet = imageCache.entrySet();
+        for(Entry<String, Image> entry : entrySet) {
+            
+            final String key = entry.getKey();
+            len = key.lastIndexOf('/');
+            final String kpath = (len > 0) ? key.substring(0, len) : key;
+            final int klen = kpath.length();
+            
+            // if the longer path begins with the shorter path,
+            // then assume the image came from this path.
+            boolean match = (klen > plen) ? kpath.startsWith(path) : path.startsWith(kpath);
+            if (match) entriesToRemove[count++] = key;
+        }
+        
+        for (int n=0; n<count; n++) {
+           Image img = imageCache.remove(entriesToRemove[n]);
+       }
+    }
     
     ////////////////////////////////////////////////////////////////////////////
     //
--- a/javafx-ui-common/src/com/sun/javafx/css/converters/PaintConverter.java	Thu Mar 07 18:46:53 2013 -0800
+++ b/javafx-ui-common/src/com/sun/javafx/css/converters/PaintConverter.java	Mon Mar 11 13:48:13 2013 -0700
@@ -38,6 +38,7 @@
 import com.sun.javafx.css.Size;
 import com.sun.javafx.css.SizeUnits;
 import com.sun.javafx.css.StyleConverterImpl;
+import com.sun.javafx.css.StyleManager;
 
 
 public final class PaintConverter extends StyleConverterImpl<ParsedValue<?, Paint>, Paint> {
@@ -152,7 +153,7 @@
             ParsedValue<?,?> urlParsedValue = values[0];
             String url = (String) urlParsedValue.convert(font);
             if (values.length == 1) {
-                return new ImagePattern(new Image(url));
+                return new ImagePattern(StyleManager.getInstance().getCachedImage(url));
             }
 
             Size x = (Size) values[1].convert(font);
--- a/javafx-ui-common/src/javafx/scene/CssStyleHelper.java	Thu Mar 07 18:46:53 2013 -0800
+++ b/javafx-ui-common/src/javafx/scene/CssStyleHelper.java	Mon Mar 11 13:48:13 2013 -0700
@@ -239,6 +239,7 @@
         private final int smapId;
         private final StyleCache localStyleCache;
         private final Reference<StyleCache> sharedCacheRef;
+        private StyleCacheEntry previousCacheEntry;
     }
     
     //
@@ -293,6 +294,7 @@
                 
                 localCacheEntry = new StyleCacheEntry(sharedCacheEntry);
                 cacheMetaData.localStyleCache.putStyleCacheEntry(key, localCacheEntry);
+
             }
         }
 
@@ -362,35 +364,6 @@
         return (styleMap != null) ? styleMap.getMap() : null;
     }
     
-    /**
-     * A convenient place for holding default values for populating the
-     * List&lt;Style&gt; that is populated if the Node has a 
-     * Map&lt;WritableValue&gt;, List&lt;Style&gt;. 
-     * See handleNoStyleFound
-     */
-    private static final Map<CssMetaData<? extends Styleable, ?>,Style> stylesFromDefaults = 
-            new HashMap<CssMetaData<? extends Styleable, ?>,Style>();
-    
-    /**
-     * The List to which Declarations fabricated from StyleablePropeerty 
-     * defaults are added.
-     */
-    private static final List<Declaration> declarationsFromDefaults;
-    
-    /**
-     * The Styles in defaultsStyles need to belong to a stylesheet. 
-     */
-    private static final Stylesheet defaultsStylesheet;
-    static {
-        final Rule defaultsRule = 
-            new Rule(new ArrayList<Selector>(), Collections.<Declaration>emptyList());
-        defaultsRule.getSelectors().add(Selector.getUniversalSelector());
-        declarationsFromDefaults = defaultsRule.getDeclarations();
-        defaultsStylesheet = new Stylesheet();
-        defaultsStylesheet.setOrigin(null);
-        defaultsStylesheet.getRules().add(defaultsRule);
-    };
-        
     /** 
      * Cache of parsed, inline styles. The key is Node.style. 
      * The value is the set of property styles from parsing Node.style.
@@ -561,7 +534,6 @@
             return;
         }
         
-        
         Set<PseudoClass>[] transitionStates = getTransitionStates(node);
                 
         //
@@ -657,22 +629,67 @@
                     : null;
 
             CalculatedValue calculatedValue = null;
+            boolean isUserSet = false;
+            
             if (fastpath) {
 
                 calculatedValue = cacheEntry.get(property);
-                if (calculatedValue == null) continue;
+                if (calculatedValue == null || calculatedValue == SKIP) continue;
+                
+                // This block of code is repeated in the slowpath, but we 
+                // want to avoid calling getStyleableProperty if possible.
+                // See the use of 'if (isUserSet)' below.
+                final StyleableProperty styleableProperty = cssMetaData.getStyleableProperty(node);
+                if (styleableProperty == null) continue;
+                
+                final StyleOrigin origin = styleableProperty.getStyleOrigin();
+                
+                isUserSet = origin == StyleOrigin.USER;
+                
+            } else {
 
-            }
-
-            if (calculatedValue == null) {
-
-                boolean isUserSet = isUserSetProperty(node, cssMetaData);            
+                final StyleableProperty styleableProperty = cssMetaData.getStyleableProperty(node);
+                if (styleableProperty == null) continue;
+                
+                final StyleOrigin origin = styleableProperty.getStyleOrigin();
+                
+                isUserSet = origin == StyleOrigin.USER;
 
                 calculatedValue = lookup(node, cssMetaData, isUserSet, node.pseudoClassStates, 
                         inlineStyles, node, cacheEntry, styleList);
 
+                // lookup is not supposed to return null.
+                assert(calculatedValue != null);
+                
+                // RT-19089
+                // If the current value of the property was set by CSS 
+                // and there is no style for the property, then reset this
+                // property to its initial value. 
+                //
+                if (calculatedValue == SKIP || calculatedValue == null) {
+                    
+                    // Was value set by CSS?
+                    if (origin != StyleOrigin.USER && origin != null) {
+
+                        Object initial = cssMetaData.getInitialValue(node);
+                        
+                        calculatedValue = new CalculatedValue(initial, null, false);
+                        
+                    } else {   
+                        // was set by user or was never set
+                        continue;
+                    }
+                    
+                }
+                
+                cacheEntry.put(property, calculatedValue);
+
             }
             
+            assert (calculatedValue != SKIP);
+            if (calculatedValue == SKIP) continue;
+            
+                        
             // RT-10522:
             // If the user set the property and there is a style and
             // the style came from the user agent stylesheet, then
@@ -686,27 +703,23 @@
             // the check needs to be done on any calculated value, not just
             // calculatedValues from cache
             //
-            if (calculatedValue == SKIP
-                || (   calculatedValue != null
-                    && (   calculatedValue.getOrigin() == StyleOrigin.USER_AGENT
-                        || calculatedValue.getOrigin() == null) 
-                    && isUserSetProperty(node, cssMetaData)
-                    )
-                ) {
-                continue;
+            final StyleOrigin cvOrigin = calculatedValue.getOrigin();
+            if (isUserSet) {
+                if (cvOrigin == StyleOrigin.USER_AGENT || cvOrigin == null) { 
+                    continue;
+                }                
             }
             
-                final Object value = calculatedValue.getValue();
-                if (LOGGER.isLoggable(PlatformLogger.FINER)) {
-                    LOGGER.finer("call " + node + ".impl_cssSet(" +
-                                    property + ", " + value + ")");
-                }
+                try {
+                    
+                    final Object value = calculatedValue.getValue();
+                    if (LOGGER.isLoggable(PlatformLogger.FINER)) {
+                        LOGGER.finer(property + ", call cssMetaData.set(" + 
+                                node + ", " + String.valueOf(value) + ", " +
+                                cvOrigin + ")");
+                    }
 
-                try {
-                    cssMetaData.set(node, value, calculatedValue.getOrigin());
-                    
-                    cacheEntry.put(property, calculatedValue);
-
+                    cssMetaData.set(node, value, cvOrigin);
                     
                     if (observableStyleMap != null) {
                         StyleableProperty<?> styleableProperty = cssMetaData.getStyleableProperty(node);                            
@@ -735,6 +748,8 @@
 
             }
         
+        cacheMetaData.previousCacheEntry = cacheEntry;
+        
         // RT-20643
         CssError.setCurrentScene(null);
 
@@ -965,51 +980,11 @@
 
             return cv;
 
-        } else if (isUserSet) {
+        } else {
 
-            // Not inherited. There is no style but we don't want to
-            // set the default value if the user set the property
+            // Not inherited. There is no style
             return SKIP;
 
-        } else {
-            
-            final Map<String, List<CascadingStyle>> smap = getStyleMap();
-            if (smap == null) return SKIP;
-            
-            if (smap.containsKey(styleable.getProperty())) {
-
-                // If there is a style in the stylemap but it just doen't apply,
-                // then it may have been set and it needs to be reset to its
-                // default value. For example, if there is a style for the hover
-                // pseudo-class state, but no style for the default state.
-                Object initialValue = styleable.getInitialValue(node);
-
-                if (styleList != null) {
-
-                    Style initialStyle = stylesFromDefaults.get(styleable);
-                    if (initialStyle != null) {
-                        if (!declarationsFromDefaults.contains(initialStyle.getDeclaration())) {
-                            declarationsFromDefaults.add(initialStyle.getDeclaration());
-                        }
-                    } else {
-                        initialStyle = new Style( 
-                            Selector.getUniversalSelector(),
-                            new Declaration(styleable.getProperty(), 
-                                        new ParsedValueImpl(initialValue, null), false));
-                        stylesFromDefaults.put(styleable, initialStyle);
-                        declarationsFromDefaults.add(initialStyle.getDeclaration());
-                    }
-
-                    styleList.add(initialStyle);
-                }
-
-                return new CalculatedValue(initialValue, null, false);
-
-            } else {
-
-                return SKIP;
-
-            }
         }
     }
     /**
@@ -1414,15 +1389,7 @@
         return new CalculatedValue(null, style.getOrigin(), false);
            
     }
-    
-    /** return true if the origin of the property is USER */
-    private boolean isUserSetProperty(Node node, CssMetaData styleable) {
-        StyleableProperty styleableProperty = node != null ? styleable.getStyleableProperty(node) : null;
-        // writable could be null if this is a sub-property
-        StyleOrigin origin = styleableProperty != null ? styleableProperty.getStyleOrigin() : null;
-        return (origin == StyleOrigin.USER);    
-    }    
-            
+                
     private static final CssMetaData dummyFontProperty =
             new FontCssMetaData<Node>("-fx-font", Font.getDefault()) {
 
--- a/javafx-ui-common/src/javafx/scene/image/ImageView.java	Thu Mar 07 18:46:53 2013 -0800
+++ b/javafx-ui-common/src/javafx/scene/image/ImageView.java	Mon Mar 11 13:48:13 2013 -0700
@@ -34,9 +34,11 @@
 import javafx.scene.Node;
 
 import com.sun.javafx.beans.event.AbstractNotifyListener;
+import com.sun.javafx.css.StyleManager;
 import javafx.css.CssMetaData;
 import javafx.css.StyleableStringProperty;
 import com.sun.javafx.css.converters.StringConverter;
+import com.sun.javafx.css.converters.URLConverter;
 import com.sun.javafx.geom.BaseBounds;
 import com.sun.javafx.geom.transform.BaseTransform;
 import com.sun.javafx.jmx.MXNodeAlgorithm;
@@ -249,20 +251,9 @@
                 @Override
                 protected void invalidated() {
 
-                    String imageUrl = null;
-                    if (get() != null) {
-                        URL url = null;
-                        try {
-                            url = new URL(get());
-                        } catch (MalformedURLException malf) {
-                            // This may be a relative URL, so try resolving
-                            // it using the application classloader
-                            final ClassLoader cl = Thread.currentThread().getContextClassLoader();
-                            url = cl.getResource(get());
-                        }
-                        if (url != null) {
-                            setImage(new Image(url.toExternalForm())); 
-                        }
+                    final String imageUrl = get();
+                    if (imageUrl != null) {
+                        setImage(StyleManager.getInstance().getCachedImage(imageUrl)); 
                     } else {
                         setImage(null);
                     }                    
@@ -800,7 +791,7 @@
         // "preserve-ratio","smooth","viewport","fit-width","fit-height"
          private static final CssMetaData<ImageView, String> IMAGE = 
             new CssMetaData<ImageView,String>("-fx-image",
-                StringConverter.getInstance()) {
+                URLConverter.getInstance()) {
 
             @Override
             public boolean isSettable(ImageView n) {
--- a/javafx-ui-common/src/javafx/scene/layout/BackgroundConverter.java	Thu Mar 07 18:46:53 2013 -0800
+++ b/javafx-ui-common/src/javafx/scene/layout/BackgroundConverter.java	Mon Mar 11 13:48:13 2013 -0700
@@ -26,6 +26,7 @@
 package javafx.scene.layout;
 
 import com.sun.javafx.css.StyleConverterImpl;
+import com.sun.javafx.css.StyleManager;
 import com.sun.javafx.scene.layout.region.RepeatStruct;
 import java.util.Map;
 import javafx.css.CssMetaData;
@@ -107,7 +108,7 @@
                 // RT-21335: skip background and border images whose image url is null
                 if (imageUrls[i] == null) continue;
 
-                final Image image = new Image(imageUrls[i]);
+                final Image image = StyleManager.getInstance().getCachedImage(imageUrls[i]);
                 final RepeatStruct repeat = (repeats.length > 0) ?
                         repeats[i <= lastRepeatIndex ? i : lastRepeatIndex] : null; // min
                 final BackgroundPosition position = (positions.length > 0) ?
--- a/javafx-ui-common/src/javafx/scene/layout/BorderConverter.java	Thu Mar 07 18:46:53 2013 -0800
+++ b/javafx-ui-common/src/javafx/scene/layout/BorderConverter.java	Mon Mar 11 13:48:13 2013 -0700
@@ -26,6 +26,7 @@
 package javafx.scene.layout;
 
 import com.sun.javafx.css.StyleConverterImpl;
+import com.sun.javafx.css.StyleManager;
 import com.sun.javafx.scene.layout.region.BorderImageSlices;
 import com.sun.javafx.scene.layout.region.Margins;
 import com.sun.javafx.scene.layout.region.RepeatStruct;
@@ -163,7 +164,8 @@
                 final BorderImageSlices slice = slices.length > 0 ? slices[i <= lastSlicesIndex ? i : lastSlicesIndex] : BorderImageSlices.EMPTY;
                 final Insets inset = insets.length > 0 ? insets[i <= lastInsetsIndex ? i : lastInsetsIndex] : Insets.EMPTY;
                 final BorderWidths width = widths.length > 0 ? widths[i <= lastWidthsIndex ? i : lastWidthsIndex] : BorderWidths.DEFAULT;
-                borderImages[i] = new BorderImage(new Image(imageUrls[i]), width, inset, slice.widths, slice.filled, repeatX, repeatY);
+                final Image img = StyleManager.getInstance().getCachedImage(imageUrls[i]);
+                borderImages[i] = new BorderImage(img, width, inset, slice.widths, slice.filled, repeatX, repeatY);
             }
         }
 
--- a/javafx-ui-common/test/unit/javafx/scene/Node_effectiveOrientation_Css_Test.java	Thu Mar 07 18:46:53 2013 -0800
+++ b/javafx-ui-common/test/unit/javafx/scene/Node_effectiveOrientation_Css_Test.java	Mon Mar 11 13:48:13 2013 -0700
@@ -41,7 +41,6 @@
 /**
  * Test :dir functional pseudo-class 
  */
-@org.junit.Ignore
 public class Node_effectiveOrientation_Css_Test {
     
     private Group root;
@@ -79,7 +78,7 @@
         root.getChildren().add(rect);
 
         // CSS is applied on next pulse after child is added
-        Toolkit.getToolkit().firePulse();
+        root.impl_processCSS(true);
         
         assertEquals(scene.getEffectiveNodeOrientation(), LEFT_TO_RIGHT);
         assertEquals(Color.web("#00ff00"), rect.getFill());
@@ -100,7 +99,7 @@
 
         scene.setNodeOrientation(RIGHT_TO_LEFT);
         // CSS is applied on next pulse after child is added
-        Toolkit.getToolkit().firePulse();
+        root.impl_processCSS(true);
         
         assertEquals(scene.getEffectiveNodeOrientation(), RIGHT_TO_LEFT);
         assertEquals(Color.web("#ff0000"), rect.getFill());
@@ -120,7 +119,7 @@
         root.getChildren().add(rect);
 
         // CSS is applied on next pulse after child is added
-        Toolkit.getToolkit().firePulse();
+        root.impl_processCSS(true);
         
         assertEquals(scene.getEffectiveNodeOrientation(), LEFT_TO_RIGHT);
         assertEquals(Color.web("#00ff00"), rect.getFill());
@@ -141,7 +140,7 @@
 
         scene.setNodeOrientation(RIGHT_TO_LEFT);
         // CSS is applied on next pulse after child is added
-        Toolkit.getToolkit().firePulse();
+        root.impl_processCSS(true);
         
         assertEquals(scene.getEffectiveNodeOrientation(), RIGHT_TO_LEFT);
         assertEquals(Color.web("#ff0000"), rect.getFill());
@@ -161,7 +160,7 @@
         root.getChildren().add(rect);
 
         // CSS is applied on next pulse after child is added
-        Toolkit.getToolkit().firePulse();
+        root.impl_processCSS(true);
         
         assertEquals(scene.getEffectiveNodeOrientation(), LEFT_TO_RIGHT);
         assertEquals(Color.web("#00ff00"), rect.getFill());
@@ -182,7 +181,7 @@
 
         scene.setNodeOrientation(RIGHT_TO_LEFT);
         // CSS is applied on next pulse after child is added
-        Toolkit.getToolkit().firePulse();
+        root.impl_processCSS(true);
         
         assertEquals(scene.getEffectiveNodeOrientation(), RIGHT_TO_LEFT);
         assertEquals(Color.web("#ff0000"), rect.getFill());
@@ -204,7 +203,7 @@
         rect.setNodeOrientation(RIGHT_TO_LEFT);
 
         // CSS is applied on next pulse after child is added
-        Toolkit.getToolkit().firePulse();
+        root.impl_processCSS(true);
         
         assertEquals(scene.getEffectiveNodeOrientation(), LEFT_TO_RIGHT);
         assertEquals(rect.getEffectiveNodeOrientation(), RIGHT_TO_LEFT);
@@ -214,7 +213,7 @@
         scene.setNodeOrientation(RIGHT_TO_LEFT);
         rect.setNodeOrientation(LEFT_TO_RIGHT);
         
-        Toolkit.getToolkit().firePulse();
+        root.impl_processCSS(true);
         
         assertEquals(scene.getEffectiveNodeOrientation(), RIGHT_TO_LEFT);
         assertEquals(rect.getEffectiveNodeOrientation(), LEFT_TO_RIGHT);
--- a/javafx-ui-controls/src/com/sun/javafx/scene/control/TableColumnComparator.java	Thu Mar 07 18:46:53 2013 -0800
+++ b/javafx-ui-controls/src/com/sun/javafx/scene/control/TableColumnComparator.java	Mon Mar 11 13:48:13 2013 -0700
@@ -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/ColorPickerSkin.java	Thu Mar 07 18:46:53 2013 -0800
+++ b/javafx-ui-controls/src/com/sun/javafx/scene/control/skin/ColorPickerSkin.java	Mon Mar 11 13:48:13 2013 -0700
@@ -25,6 +25,7 @@
 
 package com.sun.javafx.scene.control.skin;
 
+import com.sun.javafx.css.StyleManager;
 import javafx.beans.property.StringProperty;
 import javafx.css.StyleOrigin;
 import javafx.css.StyleableBooleanProperty;
@@ -96,9 +97,9 @@
             } else {
                 if (pickerColorBox.getChildren().size() == 2) {
                     ImageView imageView = (ImageView)pickerColorBox.getChildren().get(1);
-                    imageView.setImage(new Image(v));
+                    imageView.setImage(StyleManager.getInstance().getCachedImage(v));
                 } else {
-                    pickerColorBox.getChildren().add(new ImageView(new Image(v)));
+                    pickerColorBox.getChildren().add(new ImageView(StyleManager.getInstance().getCachedImage(v)));
                 }
             }
         }
--- a/javafx-ui-controls/src/com/sun/javafx/scene/control/skin/LabeledText.java	Thu Mar 07 18:46:53 2013 -0800
+++ b/javafx-ui-controls/src/com/sun/javafx/scene/control/skin/LabeledText.java	Mon Mar 11 13:48:13 2013 -0700
@@ -164,14 +164,14 @@
                 StyleOrigin propOrigin = prop.getStyleOrigin();
 
                 //
+                // if propOrigin is greater than origin, then the style should
+                //    not override
                 // if propOrigin is null, then the property is in init state
                 // if origin is null, then some code is initializing this prop
-                // if propOrigin is greater than origin, then the style should
-                //    not override
                 //
-                if (propOrigin == null ||
-                    origin == null ||
-                    propOrigin.compareTo(origin) <= 0) {
+                if ((propOrigin != null && origin != null &&  
+                     propOrigin.compareTo(origin) <= 0) ||
+                    (propOrigin == null && origin == null)) {
                     super.set(node, value, origin);
                 }
             }
--- a/javafx-ui-controls/src/com/sun/javafx/scene/control/skin/ListViewSkin.java	Thu Mar 07 18:46:53 2013 -0800
+++ b/javafx-ui-controls/src/com/sun/javafx/scene/control/skin/ListViewSkin.java	Mon Mar 11 13:48:13 2013 -0700
@@ -218,8 +218,6 @@
     @Override protected void updateRowCount() {
         if (flow == null) return;
         
-        updatePlaceholderRegionVisibility();
-        
         int oldCount = itemCount;
         int newCount = listViewItems == null ? 0 : listViewItems.size();
         
@@ -227,6 +225,7 @@
         
         flow.setCellCount(newCount);
         
+        updatePlaceholderRegionVisibility();
         if (newCount != oldCount) {
             needCellsRebuilt = true;
         } else {
--- a/javafx-ui-controls/src/com/sun/javafx/scene/control/skin/ScrollBarSkin.java	Thu Mar 07 18:46:53 2013 -0800
+++ b/javafx-ui-controls/src/com/sun/javafx/scene/control/skin/ScrollBarSkin.java	Mon Mar 11 13:48:13 2013 -0700
@@ -210,6 +210,10 @@
                     */
                     if (trackLength > thumbLength) {
                         Point2D cur = thumb.localToParent(me.getX(), me.getY());
+                        if (dragStart == null) {
+                            // we're getting dragged without getting a mouse press
+                            dragStart = thumb.localToParent(me.getX(), me.getY());
+                        }
                         double dragPos = getSkinnable().getOrientation() == Orientation.VERTICAL ? cur.getY() - dragStart.getY(): cur.getX() - dragStart.getX();
                         getBehavior().thumbDragged(me, preDragThumbPos + dragPos / (trackLength - thumbLength));
                     }
@@ -247,6 +251,10 @@
                         */
                         if (trackLength > thumbLength) {
                             Point2D cur = thumb.localToParent(event.getX(), event.getY());
+                            if (dragStart == null) {
+                                // we're getting dragged without getting a mouse press
+                                dragStart = thumb.localToParent(event.getX(), event.getY());
+                            }
                             double dragPos = getSkinnable().getOrientation() == Orientation.VERTICAL ? cur.getY() - dragStart.getY(): cur.getX() - dragStart.getX();
                             getBehavior().thumbDragged(null/*todo*/, preDragThumbPos + dragPos / (trackLength - thumbLength));
                         }
--- a/javafx-ui-controls/src/com/sun/javafx/scene/control/skin/TableColumnHeader.java	Thu Mar 07 18:46:53 2013 -0800
+++ b/javafx-ui-controls/src/com/sun/javafx/scene/control/skin/TableColumnHeader.java	Mon Mar 11 13:48:13 2013 -0700
@@ -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                                                                  *
--- a/javafx-ui-controls/src/com/sun/javafx/scene/control/skin/VirtualFlow.java	Thu Mar 07 18:46:53 2013 -0800
+++ b/javafx-ui-controls/src/com/sun/javafx/scene/control/skin/VirtualFlow.java	Mon Mar 11 13:48:13 2013 -0700
@@ -145,7 +145,7 @@
                     viewportBreadth = viewportLength = lastPosition = 0;
                     hbar.setValue(0);
                     vbar.setValue(0);
-                    adjustPosition(0.0f);
+                    setPosition(0.0f);
                     setNeedsLayout(true);
                     requestLayout();
                 }
@@ -238,11 +238,10 @@
         return position;
     }
 
-    public void setPosition(double position) {
-        boolean needsUpdate = this.position != position;
-        this.position = position;
+    public void setPosition(double newPosition) {
+        boolean needsUpdate = this.position != newPosition;
+        this.position = com.sun.javafx.Utils.clamp(0, newPosition, 1);;
         if (needsUpdate) {
-            adjustPosition(position);
             requestLayout();
         }
     }
@@ -1016,7 +1015,7 @@
                 // Update the item count
 //                setItemCount(cellCount);
             } else if (currentIndex >= cellCount) {
-                adjustPosition(1.0f);
+                setPosition(1.0f);
 //                setItemCount(cellCount);
             } else if (firstCell != null) {
                 double firstCellOffset = getCellPosition(firstCell);
@@ -1118,7 +1117,7 @@
         int firstIndex = cell.getIndex();
         double firstCellPos = getCellPosition(cell);
         if (firstIndex == 0 && firstCellPos > 0) {
-            adjustPosition(0.0f);
+            setPosition(0.0f);
             offset = 0;
             for (int i = 0; i < cells.size(); i++) {
                 cell = cells.get(i);
@@ -1214,9 +1213,9 @@
             // to be at 0 instead of 1.
             start = getCellPosition(firstCell);
             if (firstCell.getIndex() == 0 && start == 0) {
-                adjustPosition(0);
+                setPosition(0);
             } else if (getPosition() != 1) {
-                adjustPosition(1);
+                setPosition(1);
             }
         }
 
@@ -2019,7 +2018,7 @@
                         T cell = cells.get(i);
                         positionCell(cell, getCellPosition(cell) + emptySize);
                     }
-                    adjustPosition(1.0f);
+                    setPosition(1.0f);
                 }
             }
 
@@ -2128,20 +2127,12 @@
         return pixelOffset - viewportOffset;
     }
 
-    /**
-     * Simply adjusts the position to the given value, clamped between 0 and 1
-     * inclusive.
-     */
-    private void adjustPosition(double pos) {
-        setPosition(com.sun.javafx.Utils.clamp(0, pos, 1));
-    }
-
     private void adjustPositionToIndex(int index) {
         int cellCount = getCellCount();
         if (cellCount <= 0) {
             setPosition(0.0f);
         } else {            
-            adjustPosition(((double)index) / cellCount);
+            setPosition(((double)index) / cellCount);
         }
     }
 
@@ -2240,14 +2231,14 @@
         return -(viewportLength * p);
     }
     
-    /**
-     * Adjust the position based on a chunk of pixels. The position is based
-     * on the start of the scrollbar position.
-     */
-    private void adjustByPixelChunk(double numPixels) {
-        setPosition(0);
-        adjustByPixelAmount(numPixels);
-    }
+//    /**
+//     * Adjust the position based on a chunk of pixels. The position is based
+//     * on the start of the scrollbar position.
+//     */
+//    private void adjustByPixelChunk(double numPixels) {
+//        setPosition(0);
+//        adjustByPixelAmount(numPixels);
+//    }
     // end of old PositionMapper code
     
     
--- a/javafx-ui-controls/src/javafx/scene/control/Labeled.java	Thu Mar 07 18:46:53 2013 -0800
+++ b/javafx-ui-controls/src/javafx/scene/control/Labeled.java	Mon Mar 11 13:48:13 2013 -0700
@@ -28,6 +28,7 @@
 
 import com.sun.javafx.css.Selector;
 import com.sun.javafx.css.SimpleSelector;
+import com.sun.javafx.css.StyleManager;
 import com.sun.javafx.css.converters.BooleanConverter;
 import com.sun.javafx.css.converters.EnumConverter;
 import com.sun.javafx.css.converters.InsetsConverter;
@@ -326,7 +327,7 @@
     public final ObjectProperty<Font> fontProperty() {
         if (font == null) {
             font = new StyleableObjectProperty<Font>(Font.getDefault()) {
-                
+                                
                 @Override
                 public void set(Font value) {
                     final Font oldValue = get();
@@ -341,9 +342,10 @@
                     // RT-20727 - if font is changed by calling setFont, then
                     // css might need to be reapplied since font size affects
                     // calculated values for styles with relative values
-                    StyleOrigin origin = ((StyleableProperty)font).getStyleOrigin();
-                    if (origin == null || origin == StyleOrigin.USER) {
+                    if(fontSetByCss == false) {
                         Labeled.this.impl_reapplyCSS();
+                    } else {
+                        fontSetByCss = false;
                     }
                 }
                 
@@ -365,7 +367,7 @@
         }
         return font;
     }
-
+    private boolean fontSetByCss = false;
     private ObjectProperty<Font> font;
     public final void setFont(Font value) { fontProperty().setValue(value); }
     public final Font getFont() { return font == null ? Font.getDefault() : font.getValue(); }
@@ -445,7 +447,8 @@
                             url = cl.getResource(v);
                         }
                         if (url != null) {
-                            ((StyleableProperty)graphicProperty()).applyStyle(origin, new ImageView(new Image(url.toExternalForm())));
+                            final Image img = StyleManager.getInstance().getCachedImage(url.toExternalForm());
+                            ((StyleableProperty)graphicProperty()).applyStyle(origin, new ImageView(img));
                         }
                     }
                 }
@@ -734,6 +737,15 @@
             new FontCssMetaData<Labeled>("-fx-font", Font.getDefault()) {
 
             @Override
+            public void set(Labeled styleable, Font value, StyleOrigin origin) {
+                // RT-20727 - if font is changed by calling setFont, then
+                // css might need to be reapplied since font size affects
+                // calculated values for styles with relative values
+                styleable.fontSetByCss = origin != StyleOrigin.USER;
+                super.set(styleable, value, origin);                
+            }
+
+            @Override
             public boolean isSettable(Labeled n) {
                 return n.font == null || !n.font.isBound();
             }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/javafx-ui-controls/src/javafx/scene/control/SortEvent.java	Mon Mar 11 13:48:13 2013 -0700
@@ -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:46:53 2013 -0800
+++ b/javafx-ui-controls/src/javafx/scene/control/TableUtil.java	Mon Mar 11 13:48:13 2013 -0700
@@ -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:46:53 2013 -0800
+++ b/javafx-ui-controls/src/javafx/scene/control/TableView.java	Mon Mar 11 13:48:13 2013 -0700
@@ -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/TextInputControl.java	Thu Mar 07 18:46:53 2013 -0800
+++ b/javafx-ui-controls/src/javafx/scene/control/TextInputControl.java	Mon Mar 11 13:48:13 2013 -0700
@@ -187,9 +187,10 @@
                     // RT-20727 - if font is changed by calling setFont, then
                     // css might need to be reapplied since font size affects
                     // calculated values for styles with relative values
-                    StyleOrigin origin = ((StyleableProperty)font).getStyleOrigin();
-                    if (origin == null || origin == StyleOrigin.USER) {
+                    if(fontSetByCss == false) {
                         TextInputControl.this.impl_reapplyCSS();
+                    } else {
+                        fontSetByCss = false;
                     }
                 }
 
@@ -212,6 +213,7 @@
         return font;
     }
 
+    private boolean fontSetByCss = false;
     private ObjectProperty<Font> font;
     public final void setFont(Font value) { fontProperty().setValue(value); }
     public final Font getFont() { return font == null ? Font.getDefault() : font.getValue(); }
@@ -1117,6 +1119,15 @@
             new FontCssMetaData<TextInputControl>("-fx-font", Font.getDefault()) {
 
             @Override
+            public void set(TextInputControl styleable, Font value, StyleOrigin origin) {
+                // RT-20727 - if font is changed by calling setFont, then
+                // css might need to be reapplied since font size affects
+                // calculated values for styles with relative values
+                styleable.fontSetByCss = origin != StyleOrigin.USER;
+                super.set(styleable, value, origin);                
+            }
+
+            @Override
             public boolean isSettable(TextInputControl n) {
                 return n.font == null || !n.font.isBound();
             }
--- a/javafx-ui-controls/src/javafx/scene/control/Tooltip.java	Thu Mar 07 18:46:53 2013 -0800
+++ b/javafx-ui-controls/src/javafx/scene/control/Tooltip.java	Mon Mar 11 13:48:13 2013 -0700
@@ -26,6 +26,7 @@
 package javafx.scene.control;
 
 
+import com.sun.javafx.css.StyleManager;
 import javafx.css.StyleableBooleanProperty;
 import javafx.css.StyleableDoubleProperty;
 import javafx.css.StyleableObjectProperty;
@@ -596,7 +597,8 @@
                                 url = cl.getResource(get());
                             }
                             if (url != null) {
-                                setGraphic(new ImageView(new Image(url.toExternalForm())));                            
+                                final Image img = StyleManager.getInstance().getCachedImage(url.toExternalForm());
+                                setGraphic(new ImageView(img));                            
                             }
                         } else {
                             setGraphic(null);
--- a/javafx-ui-controls/src/javafx/scene/control/TreeItem.java	Thu Mar 07 18:46:53 2013 -0800
+++ b/javafx-ui-controls/src/javafx/scene/control/TreeItem.java	Mon Mar 11 13:48:13 2013 -0700
@@ -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:46:53 2013 -0800
+++ b/javafx-ui-controls/src/javafx/scene/control/TreeTableView.java	Mon Mar 11 13:48:13 2013 -0700
@@ -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:46:53 2013 -0800
+++ b/javafx-ui-controls/test/com/sun/javafx/scene/control/test/ControlAsserts.java	Mon Mar 11 13:48:13 2013 -0700
@@ -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/ListViewKeyInputTest.java	Thu Mar 07 18:46:53 2013 -0800
+++ b/javafx-ui-controls/test/javafx/scene/control/ListViewKeyInputTest.java	Mon Mar 11 13:48:13 2013 -0700
@@ -27,7 +27,6 @@
 
 import com.sun.javafx.Utils;
 import com.sun.javafx.scene.control.behavior.ListViewAnchorRetriever;
-import com.sun.javafx.tk.Toolkit;
 import static org.junit.Assert.*;
 
 import java.util.List;
@@ -42,7 +41,7 @@
 import org.junit.Ignore;
 import org.junit.Test;
 
-@Ignore("Disabling tests as they fail with OOM in continuous builds")
+//@Ignore("Disabling tests as they fail with OOM in continuous builds")
 public class ListViewKeyInputTest {
     private ListView<String> listView;
     private MultipleSelectionModel<String> sm;
--- a/javafx-ui-controls/test/javafx/scene/control/TableViewKeyInputTest.java	Thu Mar 07 18:46:53 2013 -0800
+++ b/javafx-ui-controls/test/javafx/scene/control/TableViewKeyInputTest.java	Mon Mar 11 13:48:13 2013 -0700
@@ -42,7 +42,7 @@
 import org.junit.Ignore;
 import org.junit.Test;
 
-@Ignore("Disabling tests as they fail with OOM in continuous builds")
+//@Ignore("Disabling tests as they fail with OOM in continuous builds")
 public class TableViewKeyInputTest {
     private TableView<String> tableView;
     private TableView.TableViewSelectionModel<String> sm;
--- a/javafx-ui-controls/test/javafx/scene/control/TableViewTest.java	Thu Mar 07 18:46:53 2013 -0800
+++ b/javafx-ui-controls/test/javafx/scene/control/TableViewTest.java	Mon Mar 11 13:48:13 2013 -0700
@@ -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/TreeTableViewKeyInputTest.java	Thu Mar 07 18:46:53 2013 -0800
+++ b/javafx-ui-controls/test/javafx/scene/control/TreeTableViewKeyInputTest.java	Mon Mar 11 13:48:13 2013 -0700
@@ -41,7 +41,7 @@
 import org.junit.Ignore;
 import org.junit.Test;
 
-@Ignore("Disabling tests as they fail with OOM in continuous builds")
+//@Ignore("Disabling tests as they fail with OOM in continuous builds")
 public class TreeTableViewKeyInputTest {
     private TreeTableView<String> tableView;
     private TreeTableView.TreeTableViewSelectionModel<String> sm;
--- a/javafx-ui-controls/test/javafx/scene/control/TreeTableViewTest.java	Thu Mar 07 18:46:53 2013 -0800
+++ b/javafx-ui-controls/test/javafx/scene/control/TreeTableViewTest.java	Mon Mar 11 13:48:13 2013 -0700
@@ -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() {
--- a/javafx-ui-controls/test/javafx/scene/control/TreeViewKeyInputTest.java	Thu Mar 07 18:46:53 2013 -0800
+++ b/javafx-ui-controls/test/javafx/scene/control/TreeViewKeyInputTest.java	Mon Mar 11 13:48:13 2013 -0700
@@ -41,7 +41,7 @@
 import org.junit.Ignore;
 import org.junit.Test;
 
-@Ignore("Disabling tests as they fail with OOM in continuous builds")
+//@Ignore("Disabling tests as they fail with OOM in continuous builds")
 public class TreeViewKeyInputTest {
     private TreeView<String> treeView;
     private MultipleSelectionModel<TreeItem<String>> sm;