changeset 3832:3450a1dac4a9

RT-30817: sorting cascading styles was causing expansion of declarations. Lazily create and sort cascading style list.
author David Grieve<david.grieve@oracle.com>
date Fri, 31 May 2013 17:15:40 -0400
parents 46cd0e8ca5ae
children 2f5985406164
files javafx-ui-common/src/com/sun/javafx/css/CascadingStyle.java javafx-ui-common/src/com/sun/javafx/css/CompoundSelector.java javafx-ui-common/src/com/sun/javafx/css/Selector.java javafx-ui-common/src/com/sun/javafx/css/SelectorPartitioning.java javafx-ui-common/src/com/sun/javafx/css/StyleManager.java javafx-ui-common/src/com/sun/javafx/css/StyleMap.java javafx-ui-common/src/javafx/scene/CssStyleHelper.java javafx-ui-common/src/javafx/scene/doc-files/cssref.html javafx-ui-common/test/unit/com/sun/javafx/css/SelectorPartitioningTest.java
diffstat 9 files changed, 287 insertions(+), 161 deletions(-) [+]
line wrap: on
line diff
--- a/javafx-ui-common/src/com/sun/javafx/css/CascadingStyle.java	Fri May 31 13:50:09 2013 -0400
+++ b/javafx-ui-common/src/com/sun/javafx/css/CascadingStyle.java	Fri May 31 17:15:40 2013 -0400
@@ -93,6 +93,8 @@
         return style.getDeclaration().getParsedValueImpl();
     }
     
+    @Override public String toString() { return getProperty(); }
+
     /**
      * When testing equality against another Style, we only care about
      * the property and pseudo-classes. In other words, we only care about
@@ -157,8 +159,9 @@
         final boolean otherImportant = otherDecl != null ? otherDecl.isImportant() : false;
         final Rule otherRule = otherDecl != null ? otherDecl.getRule() : null;
         final StyleOrigin otherSource = rule != null ? otherRule.getOrigin() : null;
-        
+
         int c = 0;
+
         if (this.skinProp && !other.skinProp) {
             c = 1;
         } else if (important != otherImportant) {
--- a/javafx-ui-common/src/com/sun/javafx/css/CompoundSelector.java	Fri May 31 13:50:09 2013 -0400
+++ b/javafx-ui-common/src/com/sun/javafx/css/CompoundSelector.java	Fri May 31 17:15:40 2013 -0400
@@ -231,19 +231,17 @@
             final Styleable parent = styleable.getStyleableParent();
             if (parent == null) return false;
             if (selectors.get(index-1).applies(parent)) {
-                PseudoClassState parentStates = new PseudoClassState();
-                parentStates.addAll(parent.getPseudoClassStates());
                 // If this call succeeds, then all preceding selectors will have
                 // matched due to the recursive nature of the call
+                Set<PseudoClass> parentStates = parent.getPseudoClassStates();
                 return stateMatches(parent, parentStates, index - 1);
             }
         } else {
             Styleable parent = styleable.getStyleableParent();
             while (parent != null) {
                 if (selectors.get(index-1).applies(parent)) { 
-                    PseudoClassState parentStates = new PseudoClassState();
-                    parentStates.addAll(parent.getPseudoClassStates());
-                    return stateMatches(parent, parentStates, index - 1);
+                    Set<PseudoClass> parentStates = parent.getPseudoClassStates();
+                    return stateMatches(parent, states, index - 1);
                 }
                 // Otherwise we need to get the next parent and try again
                 parent = parent.getStyleableParent();
--- a/javafx-ui-common/src/com/sun/javafx/css/Selector.java	Fri May 31 13:50:09 2013 -0400
+++ b/javafx-ui-common/src/com/sun/javafx/css/Selector.java	Fri May 31 17:15:40 2013 -0400
@@ -60,6 +60,14 @@
         return rule;
     }
 
+    private int ordinal = -1;
+    void setOrdinal(int ordinal) {
+        this.ordinal = ordinal;
+    }
+    int getOrdinal() {
+        return ordinal;
+    }
+
     abstract Match createMatch();
 
     // same as the matches method expect return true/false rather than a match
--- a/javafx-ui-common/src/com/sun/javafx/css/SelectorPartitioning.java	Fri May 31 13:50:09 2013 -0400
+++ b/javafx-ui-common/src/com/sun/javafx/css/SelectorPartitioning.java	Fri May 31 17:15:40 2013 -0400
@@ -25,7 +25,14 @@
 
 package com.sun.javafx.css;
 
-import java.util.*;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
 /**
  * Code to partition selectors into a tree-like structure for faster matching.
@@ -72,29 +79,6 @@
     }   
     
     /** 
-     * Since matched selectors are retrieved from the tree in an indeterminate manner,
-     * we need to keep track of the order in which the selector was added. This
-     * can't be kept in Selector itself since the order depends on both the
-     * order of the stylesheets and the order of the selectors in the stylesheets.
-     * Also, selectors can be added and removed from the stylesheet which would
-     * invalidate the order.
-     */
-    static final class SelectorData implements Comparable {
-        final Selector selector;
-        final int ordinal;
-        
-        private SelectorData(Selector selector, int ordinal) {
-            this.selector = selector;
-            this.ordinal = ordinal;
-        }
-
-        @Override
-        public int compareTo(Object t) {
-            return ordinal - ((SelectorData)t).ordinal;
-        }
-    }
-    
-    /**
      * A Partition corresponds to a selector type, id or styleclass. For any
      * given id (for example) there will be one Partition held in the
      * corresponding map (idMap, for example). Each Partition has Slots which
@@ -117,18 +101,18 @@
         
         private final PartitionKey key;
         private final Map<PartitionKey, Slot> slots;
-        private List<SelectorData> selectors;
+        private List<Selector> selectors;
         
         private Partition(PartitionKey key) {
            this.key = key;
             slots = new HashMap<PartitionKey,Slot>();
         }
 
-        private void addSelectorData(SelectorData selectorData) {
+        private void addSelector(Selector pair) {
             if (selectors == null) {
-                selectors = new ArrayList<SelectorData>();
+                selectors = new ArrayList<Selector>();
             }
-            selectors.add(selectorData);
+            selectors.add(pair);
         }
 
         /**
@@ -161,18 +145,18 @@
         private final Map<PartitionKey, Slot> referents;
 
         // Selectors that match the path to this slot
-        private List<SelectorData> selectors;
+        private List<Selector> selectors;
         
         private Slot(Partition partition) {
             this.partition = partition;
             this.referents = new HashMap<PartitionKey, Slot>();            
         }
         
-        private void addSelectorData(SelectorData selectorData) {
+        private void addSelector(Selector pair) {
             if (selectors == null) {
-                selectors = new ArrayList<SelectorData>();
+                selectors = new ArrayList<Selector>();
             }
-            selectors.add(selectorData);
+            selectors.add(pair);
         }
 
         /**
@@ -279,7 +263,7 @@
         Partition partition = null;
         Slot slot = null;
 
-        final SelectorData selectorData = new SelectorData(selector,ordinal++);
+        selector.setOrdinal(ordinal++);
 
         switch(c) {
             case ID_BIT | TYPE_BIT | STYLECLASS_BIT: 
@@ -290,7 +274,7 @@
                 if ((c & STYLECLASS_BIT) == STYLECLASS_BIT) {
                     slot = slot.partition(styleClassKey, styleClassMap);
                 }                
-                slot.addSelectorData(selectorData);
+                slot.addSelector(selector);
                 break;
                 
             case TYPE_BIT | STYLECLASS_BIT:
@@ -299,9 +283,9 @@
                 partition = getPartition(typeKey, typeMap);
                 if ((c & STYLECLASS_BIT) == STYLECLASS_BIT) {
                     slot = partition.partition(styleClassKey, styleClassMap);
-                    slot.addSelectorData(selectorData);
+                    slot.addSelector(selector);
                 } else {
-                    partition.addSelectorData(selectorData);
+                    partition.addSelector(selector);
                 }
                 break;
                 
@@ -316,7 +300,7 @@
     }
     
     /** Get the list of selectors that match this selector. Package accessible */
-    List<SelectorData> match(String selectorId, String selectorType, Set<StyleClass> selectorStyleClass) {
+    List<Selector> match(String selectorId, String selectorType, Set<StyleClass> selectorStyleClass) {
         
         final boolean hasId = 
             (selectorId != null && selectorId.isEmpty() == false);
@@ -341,7 +325,7 @@
 
         Partition partition = null;
         Slot slot = null;
-        List<SelectorData> selectorData = new ArrayList<SelectorData>();
+        List<Selector> selectors = new ArrayList<Selector>();
         
         while (c != 0) {
             
@@ -353,7 +337,7 @@
                     partition = idMap.get(idKey);
                     if (partition != null) {
                         if (partition.selectors != null) {
-                            selectorData.addAll(partition.selectors);
+                            selectors.addAll(partition.selectors);
                         }
                         // do-while handles A.b#c also matches A#c by first
                         // doing A.b#c then doing *.b#c
@@ -363,7 +347,7 @@
                             if (slot != null) {
 
                                 if (slot.selectors != null) {
-                                    selectorData.addAll(slot.selectors);
+                                    selectors.addAll(slot.selectors);
                                 }
                                 if ((c & STYLECLASS_BIT) == STYLECLASS_BIT) {
                                     Set<StyleClass> key = (Set<StyleClass>)styleClassKey.key;
@@ -371,7 +355,7 @@
                                         if (s.selectors == null || s.selectors.isEmpty()) continue;;
                                         Set<StyleClass> other = (Set<StyleClass>)s.partition.key.key;
                                         if (key.containsAll(other)) {
-                                            selectorData.addAll(s.selectors);
+                                            selectors.addAll(s.selectors);
                                         }
                                     }
                                 }
@@ -405,7 +389,7 @@
                         partition = typeMap.get(typePK);
                         if (partition != null) {
                             if (partition.selectors != null) {
-                                selectorData.addAll(partition.selectors);
+                                selectors.addAll(partition.selectors);
                             }
                             if ((c & STYLECLASS_BIT) == STYLECLASS_BIT) {
                                 Set<StyleClass> key = (Set<StyleClass>)styleClassKey.key;
@@ -413,7 +397,7 @@
                                     if (s.selectors == null || s.selectors.isEmpty()) continue;
                                     Set<StyleClass> other = (Set<StyleClass>)s.partition.key.key;
                                     if (key.containsAll(other)) {
-                                        selectorData.addAll(s.selectors);
+                                        selectors.addAll(s.selectors);
                                     }
                                 }
                             }
@@ -436,7 +420,18 @@
                     assert(false);
             }
         }
-        return selectorData;
+
+        Collections.sort(selectors, COMPARATOR);
+        return selectors;
     }
 
+    private static final Comparator<Selector> COMPARATOR =
+            new Comparator<Selector>() {
+                @Override
+                public int compare(Selector o1, Selector o2) {
+                    return o1.getOrdinal() - o2.getOrdinal();
+                }
+            };
+
+
 }
--- a/javafx-ui-common/src/com/sun/javafx/css/StyleManager.java	Fri May 31 13:50:09 2013 -0400
+++ b/javafx-ui-common/src/com/sun/javafx/css/StyleManager.java	Fri May 31 17:15:40 2013 -0400
@@ -67,6 +67,7 @@
 import javafx.css.StyleOrigin;
 import javafx.scene.image.Image;
 import javafx.stage.PopupWindow;
+import javafx.util.Pair;
 import sun.util.logging.PlatformLogger;
 
 /**
@@ -85,16 +86,16 @@
  * is a two level cache. The first level cache simply maps the
  * classname/id/styleclass combination of the request node to a 2nd level cache.
  * If the node has "styles" specified then we still use this 2nd level cache,
- * but must combine its selectorData with the selectorData specified in "styles" and perform
+ * but must combine its selectors with the selectors specified in "styles" and perform
  * more work to cascade properly. <p> The 2nd level cache contains a data
  * structure called the Cache. The Cache contains an ordered sequence of Rules,
- * a Long, and a Map. The ordered sequence of selectorData are the selectorData that *may*
+ * a Long, and a Map. The ordered sequence of selectors are the selectors that *may*
  * match a node with the given classname, id, and style class. For example,
- * selectorData which may apply are any selector where the simple selector of the selector
+ * selectors which may apply are any selector where the simple selector of the selector
  * contains a reference to the id, style class, or classname of the Node, or a
  * compound selector who's "descendant" part is a simple selector which contains
  * a reference to the id, style class, or classname of the Node. <p> During
- * lookup, we will iterate over all the potential selectorData and discover if they
+ * lookup, we will iterate over all the potential selectors and discover if they
  * apply to this particular node. If so, then we toggle a bit position in the
  * Long corresponding to the position of the selector that matched. This long then
  * becomes our key into the final map. <p> Once we have established our key, we
@@ -1362,7 +1363,7 @@
             // add it to the cache map
             
             // Construct the list of Selectors that could possibly apply
-            final List<SelectorPartitioning.SelectorData> selectorData = new ArrayList<>();
+            final List<Selector> selectorData = new ArrayList<>();
 
             // User agent stylesheets have lowest precedence and go first
             if (userAgentStylesheets.isEmpty() == false) {
@@ -1370,7 +1371,7 @@
                     final StylesheetContainer container = userAgentStylesheets.get(n);
                     
                     if (container != null && container.selectorPartitioning != null) {
-                        final List<SelectorPartitioning.SelectorData> matchingRules =
+                        final List<Selector> matchingRules =
                                 container.selectorPartitioning.match(id, cname, key.styleClasses);
                         selectorData.addAll(matchingRules);
                     }
@@ -1384,7 +1385,7 @@
                     final StylesheetContainer container = sceneStylesheets.get(n);
                     if (container != null && container.selectorPartitioning != null) {
                         container.keys.add(key); // remember that this stylesheet was used in this cache
-                        final List<SelectorPartitioning.SelectorData> matchingRules =
+                        final List<Selector> matchingRules =
                                 container.selectorPartitioning.match(id, cname, key.styleClasses);
                         selectorData.addAll(matchingRules);
                     }
@@ -1398,14 +1399,14 @@
                     final StylesheetContainer container = parentStylesheets.get(n);
                     container.keys.add(key); // remember that this stylesheet was used in this cache
                     if (container.selectorPartitioning != null) {
-                        final List<SelectorPartitioning.SelectorData> matchingRules =
+                        final List<Selector> matchingRules =
                                 container.selectorPartitioning.match(id, cname, key.styleClasses);
                         selectorData.addAll(matchingRules);
                     }
                 }
             }
             
-            // create a new Cache from these selectorData.
+            // create a new Cache from these selectors.
             cache = new Cache(selectorData);
             cacheMap.put(key, cache);
             
@@ -1605,29 +1606,33 @@
             }
             
         }
-        // this must be initialized to the appropriate possible selectorData when
-        // the helper cache is created by the StylesheetContainer
-        private final List<SelectorPartitioning.SelectorData> selectorData;
+
+        // this must be initialized to the appropriate possible selectors when
+        // the helper cache is created by the StylesheetContainer. Note that
+        // SelectorPartioning sorts the matched selectors by ordinal, so this
+        // list of selectors will be in the same order in which the selectors
+        // appear in the stylesheets.
+        private final List<Selector> selectors;
         private final Map<Key, Integer> cache;
 
-        Cache(List<SelectorPartitioning.SelectorData> selectorData) {
-            this.selectorData = selectorData;
+        Cache(List<Selector> selectors) {
+            this.selectors = selectors;
             this.cache = new HashMap<Key, Integer>();
         }
 
         private StyleMap getStyleMap(CacheContainer cacheContainer, Node node, Set<PseudoClass>[] triggerStates) {
             
-            if (selectorData == null || selectorData.isEmpty()) {
+            if (selectors == null || selectors.isEmpty()) {
                 return StyleMap.EMPTY_MAP;
             }
 
-            final int selectorDataSize = selectorData.size();
+            final int selectorDataSize = selectors.size();
 
             //
-            // Since the list of selectorData is found by matching only the
-            // rightmost selector, the set of selectorData may larger than those
-            // selectorData that actually match the node. The following loop
-            // whittles the list down to those selectorData that apply.
+            // Since the list of selectors is found by matching only the
+            // rightmost selector, the set of selectors may larger than those
+            // selectors that actually match the node. The following loop
+            // whittles the list down to those selectors that apply.
             //
             //
             // To lookup from the cache, we construct a key from a Long
@@ -1638,14 +1643,16 @@
             boolean nothingMatched = true;
 
             for (int s = 0; s < selectorDataSize; s++) {
-                
+
+                final Selector sel = selectors.get(s);
+
                 //
                 // This particular flavor of applies takes a PseudoClassState[]
                 // fills in the pseudo-class states from the selectors where
                 // they apply to a node. This is an expedient to looking the
-                // applies loopa second time on the matching selectorData. This has to
+                // applies loopa second time on the matching selectors. This has to
                 // be done ahead of the cache lookup since not all nodes that
-                // have the same set of selectorData will have the same node hierarchy.
+                // have the same set of selectors will have the same node hierarchy.
                 //
                 // For example, if I have .foo:hover:focused .bar:selected {...}
                 // and the "bar" node is 4 away from the root and the foo
@@ -1656,8 +1663,6 @@
                 // Note also that, if the selector does not apply, the triggerStates
                 // is unchanged.
                 //
-                final SelectorPartitioning.SelectorData selectorDatum = selectorData.get(s);
-                final Selector sel = selectorDatum.selector;
 
                 if (sel.applies(node, triggerStates, 0)) {
                     final int index = s / Long.SIZE;
@@ -1679,15 +1684,7 @@
                 return styleMap;
             }
 
-            // if there isn't a map in cache already, create one. 
-            // 
-            // We know the selectorData apply, so they should also match. A selector
-            // might have more than one selector and match will return the
-            // selector that matches. Matches is more expensive than applies, 
-            // so we pay for it here, but only for the first time the 
-            // cache is created.
-            //
-            final List<CascadingStyle> styles = new ArrayList<CascadingStyle>();
+            final List<Selector> selectors = new ArrayList<>();
 
             for (int k = 0; k<key.length; k++) {
 
@@ -1701,65 +1698,56 @@
                     final long mask = 1l << b;
                     if ((mask & key[k]) != mask) continue;
 
-                    final SelectorPartitioning.SelectorData selectorDatum = selectorData.get(offset+b);
-                    int ordinal = selectorDatum.ordinal;
-                    final Selector selector = selectorDatum.selector;
-
-                    Match match = selector.createMatch();
-
-                    Rule rule = selector.getRule();
-
-                    List<Declaration> declarations = rule.getUnobservedDeclarationList();
-                    int dMax = declarations != null ? declarations.size() : 0;
-                    for (int d = 0; d < dMax; d++) {
-                        final Declaration decl = declarations.get(d);
-
-                        final CascadingStyle s = new CascadingStyle(
-                                new Style(match.selector, decl),
-                                match.pseudoClasses,
-                                match.specificity,
-                                // ordinal increments at declaration level since
-                                // there may be more than one declaration for the
-                                // same attribute within a selector or within a stylesheet
-                                ordinal++
-                        );
-
-                        styles.add(s);
-                    }
+                    final Selector pair = this.selectors.get(offset + b);
+                    selectors.add(pair);
                 }
             }
 
-                // return sorted styles
-            Collections.sort(styles);
-
-            
-            // We need to create a new style map, add it to the cache,
-            // and then return it.
-            final Map<String, List<CascadingStyle>> smap =
-                new HashMap<String, List<CascadingStyle>>();
-            
-            for (int i=0, max=styles.size(); i<max; i++) {
-                
-                final CascadingStyle style = styles.get(i);
-                final String property = style.getProperty();
-                
-                // This is carefully written to use the minimal amount of hashing.
-                List<CascadingStyle> list = smap.get(property);
-                if (list == null) {
-                    list = new ArrayList<CascadingStyle>(5);
-                    smap.put(property, list);
-                }
-                list.add(style);
-            }
-
             final int id = cacheContainer.nextSmapId();
-            final StyleMap styleMap = new StyleMap(id, smap);
+            final StyleMap styleMap = new StyleMap(id, selectors);
             cacheContainer.addStyleMap(styleMap);
             cache.put(keyObj, Integer.valueOf(id));
             return styleMap;
         }
 
     }
+
+    /**
+     * Get the map of property to style from the rules and declarations
+     * in the stylesheet. There is no need to do selector matching here since
+     * the stylesheet is from parsing Node.style.
+     */
+    public StyleMap createInlineStyleMap(Styleable styleable) {
+
+        Stylesheet inlineStylesheet =
+                CSSParser.getInstance().parseInlineStyle(styleable);
+        if (inlineStylesheet == null)  return StyleMap.EMPTY_MAP;
+
+        inlineStylesheet.setOrigin(StyleOrigin.INLINE);
+
+        List<Selector> pairs = new ArrayList<>(1);
+
+        int ordinal = 0;
+            
+        final List<Rule> stylesheetRules = inlineStylesheet.getRules();
+
+        List<Selector> selectorList =  null;
+
+        for (int i = 0, imax = stylesheetRules.size(); i < imax; i++) {
+            
+            final Rule rule = stylesheetRules.get(i);
+            if (rule == null) continue;
+
+            List<Selector> selectors = rule.getUnobservedSelectorList();
+            if (selectorList == null || selectors.isEmpty()) continue;
+
+            selectorList.addAll(selectors);
+        }
+
+        // TODO: should have a cacheContainer for inline styles?
+        return new StyleMap(-1, selectorList);
+    }
+
     
     /**
      * The key used in the cacheMap of the StylesheetContainer
--- a/javafx-ui-common/src/com/sun/javafx/css/StyleMap.java	Fri May 31 13:50:09 2013 -0400
+++ b/javafx-ui-common/src/com/sun/javafx/css/StyleMap.java	Fri May 31 17:15:40 2013 -0400
@@ -25,9 +25,20 @@
 
 package com.sun.javafx.css;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.function.ToDoubleFunction;
+import java.util.function.ToIntFunction;
+import java.util.function.ToLongFunction;
+import javafx.util.Pair;
 
 /**
  * A map of property name to the cascading styles that match a node.
@@ -35,22 +46,141 @@
 public final class StyleMap {
 
     public static final StyleMap EMPTY_MAP = 
-        new StyleMap(-1, Collections.<String,List<CascadingStyle>>emptyMap());
+        new StyleMap(-1, Collections.<Selector>emptyList());
 
     /** Only StyleManager creates StyleMap */
-    StyleMap(int id, Map<String, List<CascadingStyle>> map) {
+    StyleMap(int id, List<Selector> selectors) {
         this.id = id;
-        this.map  = map;
+        this.selectors = selectors;
     }
 
     public int getId() {
         return id;
     }
 
-    public Map<String, List<CascadingStyle>> getMap() {
-        return map;
+    public boolean isEmpty() {
+        if (selectors != null) return selectors.isEmpty();
+        else if (cascadingStyles != null) return cascadingStyles.isEmpty();
+        else return true;
     }
         
+    public Map<String, List<CascadingStyle>> getCascadingStyles() {
+
+        if (cascadingStyles == null) {
+
+            if (selectors == null || selectors.isEmpty()) {
+                cascadingStyles = Collections.emptyMap();
+                return cascadingStyles;
+            }
+
+            //
+            // Creating the map is a three step process. First, create
+            // a list of CascadingStyles. Second, sort the CascadingStyles.
+            // And, finally, loop through the CascadingStyles to put them
+            // into the Map by property name.
+            //
+            List<CascadingStyle> cascadingStyleList = new ArrayList<>();
+
+            int ordinal = 0;
+            for (int i=0, iMax=selectors.size(); i<iMax; i++) {
+
+                final Selector selector = selectors.get(i);
+
+                final Match match = selector.createMatch();
+
+                final Rule rule = selector.getRule();
+
+                for (int d = 0, dmax = rule.getDeclarations().size(); d < dmax; d++) {
+                    final Declaration decl = rule.getDeclarations().get(d);
+
+                    final CascadingStyle s = new CascadingStyle(
+                            new Style(match.selector, decl),
+                            match.pseudoClasses,
+                            match.specificity,
+                            // ordinal increments at declaration level since
+                            // there may be more than one declaration for the
+                            // same attribute within a selector or within a stylesheet
+                            ordinal++
+                    );
+
+                    cascadingStyleList.add(s);
+
+                }
+            }
+
+            // apply the cascade. CascadingStyle's primary sort key is the
+            // property name, so the same properties should be in sequence.
+            Collections.sort(cascadingStyleList, cascadingStyleComparator);
+
+            // there may be more entries in this HashMap than we need if there
+            // is more than one CascadingStyle per property. But this is
+            // still better than
+            final int nCascadingStyles = cascadingStyleList.size();
+            cascadingStyles = new HashMap<>(nCascadingStyles);
+
+            CascadingStyle cascadingStyle = cascadingStyleList.get(0);
+            String property = cascadingStyle.getProperty();
+
+
+            for (int fromIndex=0; fromIndex<nCascadingStyles; /*increment is in code*/) {
+
+                List<CascadingStyle> value = cascadingStyles.get(property);
+                if (value == null)  {
+
+                    int toIndex = fromIndex;
+                    final String currentProperty = property;
+
+                    while (++toIndex < nCascadingStyles) {
+                        cascadingStyle = cascadingStyleList.get(toIndex);
+                        property = cascadingStyle.getProperty();
+                        if (property.equals(currentProperty) == false) break;
+                    }
+
+                    cascadingStyles.put(currentProperty, cascadingStyleList.subList(fromIndex, toIndex));
+
+                    fromIndex = toIndex;
+
+
+                } else {
+                    // logic is broken!
+                    assert(false);
+                }
+            }
+
+            selectors.clear();
+            selectors = null;
+
+        }
+
+        return cascadingStyles;
+    }
+
+    private static final Comparator<CascadingStyle> cascadingStyleComparator =
+            new Comparator<CascadingStyle>() {
+
+                @Override public int compare(CascadingStyle o1, CascadingStyle o2) {
+
+                    //
+                    // primary sort is on property. If the property names are the same,
+                    // then go through the cascade logic. Otherwise, sort by property
+                    // name since the cascade doesn't matter for dissimilar properties.
+                    //
+                    // What we want to end up with is a list where all the same properties
+                    // are together in the list.
+                    //
+                    String thisProperty = o1.getProperty();
+                    String otherProperty = o2.getProperty();
+
+                    int c = thisProperty.compareTo(otherProperty);
+                    if (c != 0) return c;
+
+                    return o1.compareTo(o2);
+
+                }
+
+            };
+
     private final int id; // unique per container
-    private final Map<String, List<CascadingStyle>> map;            
+    private List<Selector> selectors;
+    private Map<String, List<CascadingStyle>> cascadingStyles;
 }
--- a/javafx-ui-common/src/javafx/scene/CssStyleHelper.java	Fri May 31 13:50:09 2013 -0400
+++ b/javafx-ui-common/src/javafx/scene/CssStyleHelper.java	Fri May 31 17:15:40 2013 -0400
@@ -119,10 +119,7 @@
                 StyleManager.getInstance().findMatchingStyles(node, triggerStates);
 
         
-        final Map<String, List<CascadingStyle>> smap 
-                = styleMap != null ? styleMap.getMap() : null;
-        
-        if (smap == null || smap.isEmpty()) {
+        if (styleMap == null || styleMap.isEmpty()) {
             
             // If there are no styles at all, and no styles that inherit, then return
             final String inlineStyle = node.getStyle();
@@ -337,7 +334,7 @@
             final String inlineStyle = node.getStyle();
             if(inlineStyle == null || inlineStyle.isEmpty()) {
 
-                final Map<String, List<CascadingStyle>> smap = getStyleMap(node);            
+                StyleMap smap = getStyleMap(node);
                 if (smap == null || smap.isEmpty()) {
                     // We have no styles! Reset this StyleHelper to its
                     // initial state so that calls to transitionToState 
@@ -395,11 +392,16 @@
         }
     }
     
-        
-    private Map<String, List<CascadingStyle>> getStyleMap(Styleable styleable) {
+
+    private StyleMap getStyleMap(Styleable styleable) {
         if (cacheContainer == null || styleable == null) return null;
-        StyleMap styleMap = cacheContainer.getStyleMap(styleable);
-        return (styleMap != null) ? styleMap.getMap() : null;
+        return cacheContainer.getStyleMap(styleable);
+    }
+
+    private Map<String, List<CascadingStyle>> getCascadingStyles(Styleable styleable) {
+        StyleMap styleMap = getStyleMap(styleable);
+        // code looks for null return to indicate that the cache was blown away
+        return (styleMap != null) ? styleMap.getCascadingStyles() : null;
     }
     
     /** 
@@ -855,10 +857,13 @@
         final CascadingStyle inlineStyle = (inlineStyles != null) ? inlineStyles.get(property) : null;
 
         // Get all of the Styles which may apply to this particular property
-        final Map<String, List<CascadingStyle>> smap = getStyleMap(styleable);
-        if (smap == null) return inlineStyle;
+        final StyleMap smap = getStyleMap(styleable);
+        if (smap == null || smap.isEmpty()) return inlineStyle;
 
-        final List<CascadingStyle> styles = smap.get(property);
+        final Map<String, List<CascadingStyle>> cascadingStyleMap = smap.getCascadingStyles();
+        if (cascadingStyleMap == null || cascadingStyleMap.isEmpty()) return inlineStyle;
+
+        List<CascadingStyle> styles = cascadingStyleMap.get(property);
 
         // If there are no styles for this property then we can just bail
         if ((styles == null) || styles.isEmpty()) return inlineStyle;
@@ -2128,7 +2133,7 @@
                     
             String property = styleableProperty.getProperty();
             Node _node = node instanceof Node ? (Node)node : null;
-            final Map<String, List<CascadingStyle>> smap = getStyleMap(_node);
+            final Map<String, List<CascadingStyle>> smap = getCascadingStyles(_node);
             if (smap == null) return;
             
              List<CascadingStyle> styles = smap.get(property);            
@@ -2188,7 +2193,7 @@
                                              
                         final int start = styleList.size();
                         
-                        final Map<String, List<CascadingStyle>> smap = helper.getStyleMap(_parent);
+                        final Map<String, List<CascadingStyle>> smap = helper.getCascadingStyles(_parent);
                         if (smap != null) {
 
                             List<CascadingStyle> styles = smap.get(property);
--- a/javafx-ui-common/src/javafx/scene/doc-files/cssref.html	Fri May 31 13:50:09 2013 -0400
+++ b/javafx-ui-common/src/javafx/scene/doc-files/cssref.html	Fri May 31 17:15:40 2013 -0400
@@ -433,9 +433,9 @@
       default user agent style sheet is loaded. Inline styles are specified via
       the Node <span style="font-weight: bold;">setStyle</span> API. Inline
       styles are analogous to the style="..." attribute of an HTML element.
-      Styles loaded from a Scene's style sheets take precedence over selectorData from
+      Styles loaded from a Scene's style sheets take precedence over selectors from
       the user agent style sheet. Inline styles take&nbsp; precedence over
-      styles originating elsewhere. The precedence order of style selectorData can be
+      styles originating elsewhere. The precedence order of style selectors can be
       modified using "!important" in a style declaration.&nbsp;</p>
     <p>Beginning with JavaFX 2.1, the Parent class has a stylesheets property,
       allowing style sheets to be set on a container. This allows for one branch
--- a/javafx-ui-common/test/unit/com/sun/javafx/css/SelectorPartitioningTest.java	Fri May 31 13:50:09 2013 -0400
+++ b/javafx-ui-common/test/unit/com/sun/javafx/css/SelectorPartitioningTest.java	Fri May 31 17:15:40 2013 -0400
@@ -202,12 +202,12 @@
                 
         SimpleSelector simple = simpleData.selector;
         
-        List<SelectorPartitioning.SelectorData> matched = instance.match(simple.getId(), simple.getName(), simple.getStyleClassSet());
+        List<Selector> matched = instance.match(simple.getId(), simple.getName(), simple.getStyleClassSet());
         
         assertEquals(1,matched.size());
-        SelectorPartitioning.SelectorData selectorDatum = matched.get(0);
+        Selector selector = matched.get(0);
 
-        Rule rule = selectorDatum.selector.getRule();
+        Rule rule = selector.getRule();
 
         assertEquals(1,rule.getUnobservedDeclarationList().size());
         Declaration decl = rule.getUnobservedDeclarationList().get(0);
@@ -226,11 +226,10 @@
                 
         SimpleSelector simple = complexData.selector;
         
-        List<SelectorPartitioning.SelectorData> matched = instance.match(simple.getId(), simple.getName(), simple.getStyleClassSet());
+        List<Selector> matched = instance.match(simple.getId(), simple.getName(), simple.getStyleClassSet());
         assertEquals(complexData.matches, matched.size());
         
-        for(SelectorPartitioning.SelectorData selectorDatum : matched) {
-            Selector s1 = selectorDatum.selector;
+        for(Selector s1 : matched) {
             for (SimpleData datum : complexData.data) {
                 Selector s2 = (SimpleSelector)datum.selector;
                 if (s1.equals(s2)) {