changeset 1391:ad5af91935ff

RT-22565: parent stylesheets may be in the default stylesheet container
author David Grieve<david.grieve@oracle.com>
date Fri, 29 Jun 2012 13:18:52 -0400
parents 29a95b63c732
children 121a87ea1d97
files javafx-ui-common/src/com/sun/javafx/css/StyleManager.java javafx-ui-common/src/javafx/scene/Parent.java
diffstat 2 files changed, 187 insertions(+), 50 deletions(-) [+]
line wrap: on
line diff
--- a/javafx-ui-common/src/com/sun/javafx/css/StyleManager.java	Thu Jun 28 18:21:54 2012 -0400
+++ b/javafx-ui-common/src/com/sun/javafx/css/StyleManager.java	Fri Jun 29 13:18:52 2012 -0400
@@ -136,44 +136,163 @@
     }
 
     /**
-     * called from Parent's stylesheets onChanged method
+     * A container for Parent stylesheets and the Parents that use them.
+     * If a Parent stylesheet is removed by one parent, then all other
+     * parents that use that stylesheet should get new styles if the 
+     * stylesheet is added back in (typical of SceneBuilder). This container
+     * provides the hooks to get back to those parents.
+     * 
+     * ParentStylesheetContainers are created and added to parentStylesheetMap
+     * in the StylesheetContainer method gatherParentStylesheets
      */
-    public void parentStylesheetsChanged(Scene scene, Change c) {
+    private static class ParentStylesheetContainer {
+        // the stylesheet url
+        private final String fname;
+        // the parsed stylesheet so we don't reparse for every parent that uses it
+        private final Stylesheet stylesheet;
+        // the parents that use this stylesheet. Typically, this list should
+        // be very small.
+        private final RefList<Parent> parents;
+        // the keys for finding Cache entries that use this stylesheet. 
+        // This list should also be fairly small
+        private final RefList<Key> keys;
+
+        private ParentStylesheetContainer(String fname, Stylesheet stylesheet) {
+            this.fname = fname;
+            this.stylesheet = stylesheet;
+            this.parents = new RefList<Parent>();
+            this.keys = new RefList<Key>();
+        }
+    }
+    
+    private static class RefList<K> {
         
+        private final List<Reference<K>> list = new ArrayList<Reference<K>>();
+        
+        private void add(K key) {
+
+            for (int n=list.size()-1; 0<=n; --n) {
+                final Reference<K> ref = list.get(n);
+                final K k = ref.get();
+                if (k == null) {
+                    // stale reference, remove it.
+                    list.remove(n);
+                } else {
+                    // already have it, bail
+                    if (k == key) return;
+                }
+            }
+            // not found, add it.
+            list.add(new WeakReference<K>(key));
+        }
+        
+    }
+    
+    /**
+    * Another map from String => Stylesheet. This one is for stylesheets that
+    * hang off a Parent. These are considered "author" stylesheets but are not
+    * added to the authorStylesheetMap because we don't want the scene's
+    * list of stylesheets in the container to be updated.
+    */
+    private static Map<String,ParentStylesheetContainer> parentStylesheetMap =
+            new HashMap<String,ParentStylesheetContainer>();
+
+    
+
+    /**
+     * called from Parent's stylesheets property's onChanged method
+     */
+    public void parentStylesheetsChanged(Parent parent, Change<String> c) {
+        
+        final Scene scene = parent.getScene();
         if (scene == null) return;
 
-        if (containerMap == null) {
-            containerMap = new HashMap<WeakReference<Scene>,StylesheetContainer>(); 
-        }
-        StylesheetContainer container = containerMap.get(scene);
-        if (container == null) {
-            container = new StylesheetContainer(null);
-            put(containerMap, scene, container);
-        }
-        
+        boolean wasRemoved = false;
+                
         while (c.next()) {
             
-            List<String> list = null;
-            // If the String was removed, remove it from parentStylesheetMap since
-            // it isn't referenced any more. It may be referenced by some other
-            // parent, in which case the stylesheet will be reparsed and added
+            // RT-22565
+            // If the String was removed, remove it from parentStylesheetMap
+            // and remove all Caches that got styles from the stylesheet. 
+            // The stylesheet may still be referenced by some other parent, 
+            // in which case the stylesheet will be reparsed and added
             // back to parentStylesheetMap when the StyleHelper is recreated.
-            if (c.wasRemoved()) list = c.getRemoved();
-            // If the String was added, remove it from parentStylesheetMap. When
-            // the StyleHelper is recreated, the stylesheet will be reparsed
-            // and added back to the parentStyleMap.
-            else if (c.wasAdded()) list = c.getAddedSubList();
+            // This incurs a some overhead since the stylesheet has to be
+            // reparsed, but this keeps the other Parents that use this
+            // in sync. For example, SceneBuilder will remove and then add
+            // a stylesheet after it has been edited and all Parents that use
+            // that stylesheet should get the new values. 
+            // 
+            if (c.wasRemoved()) {
+                
+                final List<String> list = c.getRemoved();
+                int nMax = list != null ? list.size() : 0;
+                for (int n=0; n<nMax; n++) {                
+                    final String fname = list.get(n);
+                    
+                    // remove this parent from the container and clear
+                    // all caches used by this stylesheet
+                    ParentStylesheetContainer psc = parentStylesheetMap.remove(fname);
+                    if (psc != null) {
+                        clearParentCache(psc);
+                    }
+                }                
+            }
             
-            // must have been a permutation. continue on to the next change.
-            if (list == null) continue;
+            // RT-22565: only wasRemoved matters. If the logic was applied to
+            // wasAdded, then the stylesheet would be reparsed each time a
+            // a Parent added it. 
             
-            for (int n=0, nMax=list.size(); n<nMax; n++) {
-                container.parentStylesheetMap.remove(list.get(n));
-            }                
         }
+        // parent uses change also
+        c.reset();
+    }
+
+    // RT-22565: Called from parentStylesheetsChanged to clear the cache entries
+    // for parents that use the same stylesheet
+    private void clearParentCache(ParentStylesheetContainer psc) {
         
-        container.clearCaches();        
+        final List<Reference<Parent>> parentList = psc.parents.list;
+        final List<Reference<Key>>    keyList    = psc.keys.list;
+        for (int n=parentList.size()-1; 0<=n; --n) {
+            
+            final Reference<Parent> ref = parentList.get(n);
+            final Parent parent = ref.get();
+            if (parent == null) continue;
+            
+            final Scene scene = parent.getScene();
+            if (scene == null) continue;
+
+            StylesheetContainer container = null;            
+            if (containerMap != null 
+                && (container = get(containerMap, scene)) != null) {
+                clearParentCache(container, keyList);
+            }
+
+            if (defaultContainer != null) {
+                clearParentCache(defaultContainer, keyList);
+            }
+            
+            // tell parent it needs to reapply css
+            parent.impl_reapplyCSS();
+        }
     }
+    
+    // RT-22565: Called from clearParentCache to clear the cache entries.
+    private void clearParentCache(StylesheetContainer container, List<Reference<Key>> keyList) {
+        
+        if (container.cacheMap.isEmpty()) return;
+        
+        for (int n=keyList.size()-1; 0<=n; --n) {
+            
+            final Reference<Key> ref = keyList.get(n);
+            final Key key = ref.get();
+            if (key == null) continue;
+            
+            final Cache cache = container.cacheMap.remove(key);
+        }
+    }
+    
     /**
      * A map from Scene => StylesheetContainer. This provides us a way to find
      * the stylesheets which apply to any given scene.
@@ -887,15 +1006,6 @@
         private final List<Stylesheet> stylesheets;
 
         /**
-        * Another map from String => Stylesheet. This one is for stylesheets that
-        * hang off a Parent. These are considered "author" stylesheets but are not
-        * added to the authorStylesheetMap because we don't want the scene's
-        * list of stylesheets in the container to be updated.
-        */
-        private static Map<String,Stylesheet> parentStylesheetMap =
-                new HashMap<String,Stylesheet>();
-
-        /**
          * The map of Caches, key'd by a combination of class name, style class,
          * and id.
          */
@@ -991,7 +1101,7 @@
         // stylesheets further down the tree (closer to the leaf) have
         // a higer ordinal in the cascade.
         //
-        private List<Stylesheet> gatherParentStylesheets(Parent parent) {
+        private List<ParentStylesheetContainer> gatherParentStylesheets(Parent parent) {
             
             if (parent == null) return null; 
             
@@ -999,22 +1109,33 @@
             
             if (parentStylesheets == null || parentStylesheets.isEmpty()) return null;
             
-            final List<Stylesheet> list = new ArrayList<Stylesheet>();
+            final List<ParentStylesheetContainer> list = new ArrayList<ParentStylesheetContainer>();
             
             for (int n=0, nMax=parentStylesheets.size(); n<nMax; n++) {
                 final String fname = parentStylesheets.get(n);
-                Stylesheet stylesheet = null;
+                ParentStylesheetContainer container = null;
                 if (parentStylesheetMap.containsKey(fname)) {
-                    stylesheet = parentStylesheetMap.get(fname);
+                    container = parentStylesheetMap.get(fname);
+                    // RT-22565: remember that this parent uses this stylesheet.
+                    // Later, if the cache is cleared, the parent is told to 
+                    // reapply css.
+                    container.parents.add(parent);
                 } else {
-                    stylesheet = StyleManager.getInstance().loadStylesheet(fname);
+                    final Stylesheet stylesheet = 
+                        StyleManager.getInstance().loadStylesheet(fname);
                     // stylesheet may be null which would mean that some IOException
                     // was thrown while trying to load it. Add it to the 
                     // parentStylesheetMap anyway as this will prevent further
-                    // attempts to parse the file. 
-                    parentStylesheetMap.put(fname, stylesheet);
+                    // attempts to parse the file
+                    container =
+                            new ParentStylesheetContainer(fname, stylesheet);
+                    // RT-22565: remember that this parent uses this stylesheet.
+                    // Later, if the cache is cleared, the parent is told to 
+                    // reapply css.
+                    container.parents.add(parent);
+                    parentStylesheetMap.put(fname, container);
                 }
-                if (stylesheet != null) list.add(stylesheet);
+                if (container != null) list.add(container);
             }
             
             return list;
@@ -1052,14 +1173,14 @@
             key.id = id;
             key.styleClass = styleClass;
             key.indices = hasParentStylesheets ? indicesOfParentsWithStylesheets : null;
- 
+            
             Cache cache = cacheMap.get(key);
 
             // the key is an instance variable and so we need to null the
             // key.styleClass to prevent holding a hard reference to the
             // styleClass (and its Node)
             key.styleClass = null;
-
+            
             // If the cache is null, then we need to create a new Cache and
             // add it to the cache map
             if (cache == null) {
@@ -1083,7 +1204,7 @@
                 // then the selector impacts its children.
                 boolean impactsChildren = false;
                 
-                final List<Stylesheet> parentStylesheets = 
+                final List<ParentStylesheetContainer> parentStylesheets = 
                     hasParentStylesheets 
                         ? gatherParentStylesheets(
                             ((node instanceof Parent) ? (Parent)node : node.getParent())
@@ -1105,7 +1226,11 @@
                     
                     // scene stylesheets come first since declarations from
                     // Parent stylesheets should take precedence.
-                    stylesheetsToProcess = parentStylesheets;
+                    stylesheetsToProcess = new ArrayList<Stylesheet>(parentStylesheets.size());
+                    for (int n=0, nMax=parentStylesheets.size(); n<nMax; n++) {
+                        final ParentStylesheetContainer psc = parentStylesheets.get(n);
+                        stylesheetsToProcess.add(psc.stylesheet);
+                    }
                     stylesheetsToProcess.addAll(0,stylesheets);
                 }                    
 
@@ -1149,7 +1274,8 @@
                         // For each selector, rule, stylesheet look for whether this Node
                         // is referenced in the ancestor part of the selector, and whether or
                         // not it also has pseudoclasses specified
-                        for (int s = 0, smax = rule.selectors.size(); s < smax; s++) {
+                        final int smax = rule.selectors != null ? rule.selectors.size() : 0;
+                        for (int s = 0; s < smax; s++) {
                             final Selector selector = rule.selectors.get(s);
                             if (selector instanceof CompoundSelector) {
 
@@ -1202,14 +1328,24 @@
                 // regardless of whether or not the cache is shared, a Cache
                 // object is still needed in order to do the lookup.
                 //
-                cache = new Cache(this, rules, pseudoclassStateMask, impactsChildren);
                 final Key newKey = new Key();
                 newKey.className = className;
                 newKey.id = id;
                 newKey.styleClass = styleClass;
                 newKey.indices = hasParentStylesheets ? indicesOfParentsWithStylesheets : null;
                 
+                cache = new Cache(this, rules, pseudoclassStateMask, impactsChildren);
                 cacheMap.put(newKey, cache);
+                
+                // RT-22565: remember where this cache is used if there are 
+                // parent stylesheets involve so the cache can be cleared later
+                // from the parentStylesheetsChanged method.
+                final int nMax = parentStylesheets != null ? parentStylesheets.size() : 0;
+                for (int n=0; n<nMax; n++) {
+                    final ParentStylesheetContainer psc = parentStylesheets.get(n);
+                    psc.keys.add(newKey);
+                }
+                
             }
             // Return the style helper looked up by the cache. The cache will
             // create a style helper if necessary (and possible), so we don't
@@ -1266,6 +1402,7 @@
         private final long pseudoclassStateMask;
         private final boolean impactsChildren;
         private final Map<Long, StyleMap> cache;
+        
         Cache(StylesheetContainer owner, List<Rule> rules, long pseudoclassStateMask, boolean impactsChildren) {
             this.owner = owner;
             this.rules = rules;
--- a/javafx-ui-common/src/javafx/scene/Parent.java	Thu Jun 28 18:21:54 2012 -0400
+++ b/javafx-ui-common/src/javafx/scene/Parent.java	Fri Jun 29 13:18:52 2012 -0400
@@ -1074,7 +1074,7 @@
     private final ObservableList<String> stylesheets = new TrackableObservableList<String>() {
         @Override
         protected void onChanged(Change<String> c) {
-            StyleManager.getInstance().parentStylesheetsChanged(getScene(), c);
+            StyleManager.getInstance().parentStylesheetsChanged(Parent.this, c);
             // RT-9784 - if stylesheet is removed, reset styled properties to 
             // their initial value.
             while(c.next()) {