## javafx-ui-common/src/com/sun/javafx/css/StyleManager.java

Print this page

    rev 1582 : RT-22121: Have StyleManager for Parent, also

       8  * particular file as subject to the "Classpath" exception as provided
       9  * by Oracle in the LICENSE file that accompanied this code.
      10  *
      11  * This code is distributed in the hope that it will be useful, but WITHOUT
      12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
      13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
      14  * version 2 for more details (a copy is included in the LICENSE file that
      15  * accompanied this code).
      16  *
      17  * You should have received a copy of the GNU General Public License version
      18  * 2 along with this work; if not, write to the Free Software Foundation,
      19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
      20  *
      21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
      22  * or visit www.oracle.com if you need additional information or have any
      23  * questions.
      24  */
      25
      26 package com.sun.javafx.css;
      27
      28 import com.sun.javafx.collections.TrackableObservableList;
      29 import java.io.IOException;
      30 import java.net.MalformedURLException;
      31 import java.net.URI;
      32 import java.net.URL;
      33 import java.io.FilePermission;
      34 import java.security.AccessControlContext;
      35 import java.security.AccessController;
      36 import java.security.PermissionCollection;
      37 import java.security.ProtectionDomain;
      38 import java.util.ArrayList;
      39 import java.util.Collection;
      40 import java.util.Collections;
      41 import java.util.HashMap;
      42 import java.util.Iterator;
      43 import java.util.List;
      44 import java.util.Map;
      45 import java.util.jar.JarEntry;
      46 import java.util.jar.JarFile;
      47
      48 import javafx.collections.FXCollections;
      49 import javafx.scene.Node;
      50 import javafx.scene.Scene;
      51
      52 import com.sun.javafx.css.parser.CSSParser;
      53 import com.sun.javafx.css.StyleHelper.StyleCacheBucket;
      54 import com.sun.javafx.css.StyleHelper.StyleCacheKey;
      55 import com.sun.javafx.logging.PlatformLogger;
      56 import java.io.FileNotFoundException;
      57 import java.lang.ref.Reference;
      58 import java.lang.ref.ReferenceQueue;
      59 import java.lang.ref.WeakReference;
      60 import java.security.PrivilegedAction;
      61 import java.security.PrivilegedExceptionAction;
      62 import java.security.PrivilegedActionException;
      63 import java.util.*;
      64 import java.util.Map.Entry;
      65 import javafx.beans.InvalidationListener;
      66 import javafx.beans.value.ChangeListener;
      67 import javafx.beans.value.ObservableValue;
      68 import javafx.collections.*;
      69 import javafx.collections.ListChangeListener.Change;
      70 import javafx.scene.Parent;
      71 import javafx.stage.PopupWindow;
      72 import javafx.stage.Window;
      73
      74 /**
      75  * Contains the stylesheet state for a single scene. This includes both the
      76  * Stylesheets defined on the Scene itself as well as a map of stylesheets for
      77  * "style"s defined on the Node itself. These containers are kept in the
      78  * containerMap, key'd by the Scene to which they belong. <p> One of the key
      79  * responsibilities of the StylesheetContainer is to create and maintain an
      80  * admittedly elaborate series of caches so as to minimize the amount of time it
      81  * takes to match a Node to its eventual StyleHelper, and to reuse the
      82  * StyleHelper as much as possible. <p> Initially, the cache is empty. It is
      83  * recreated whenever the userStylesheets on the container change, or whenever
      84  * the userAgentStylesheet changes. The cache is built up as nodes are looked
      85  * for, and thus there is some overhead associated with the first lookup but
      86  * which is then not repeated for subsequent lookups. <p> The cache system used
      87  * is a two level cache. The first level cache simply maps the
      88  * classname/id/styleclass combination of the request node to a 2nd level cache.
      89  * If the node has "styles" specified then we still use this 2nd level cache,
      90  * but must combine its rules with the rules specified in "styles" and perform
      91  * more work to cascade properly. <p> The 2nd level cache contains a data
      95  * rules which may apply are any rule where the simple selector of the rule
      96  * contains a reference to the id, style class, or classname of the Node, or a
      97  * compound selector who's "descendant" part is a simple selector which contains
      98  * a reference to the id, style class, or classname of the Node. <p> During
      99  * lookup, we will iterate over all the potential rules and discover if they
     100  * apply to this particular node. If so, then we toggle a bit position in the
     101  * Long corresponding to the position of the rule that matched. This long then
     102  * becomes our key into the final map. <p> Once we have established our key, we
     103  * will visit the map and look for an existing StyleHelper. If we find a
     104  * StyleHelper, then we will return it. If not, then we will take the Rules that
     105  * matched and construct a new StyleHelper from their various parts. <p> This
     106  * system, while elaborate, also provides for numerous fast paths and sharing of
     107  * data structures which should dramatically reduce the memory and runtime
     108  * performance overhead associated with CSS by reducing the matching overhead
     109  * and caching as much as possible. We make no attempt to use weak references
     110  * here, so if memory issues result one work around would be to toggle the root
     111  * user agent stylesheet or stylesheets on the scene to cause the cache to be
     112  * flushed.
     113  */
     114
     115 public final class StyleManager {
     116
     117     private static class Holder {
     118         private static PlatformLogger LOGGER = com.sun.javafx.Logging.getCSSLogger();
     119     }
     120
     121     private static PlatformLogger logger() {
     122         return Holder.LOGGER;
     123     }
     124
     125     /**
     126      * Return the StyleManager for the given Scene, or null if the scene
     127      * is no longer referenced.
     128      */
     129     public static StyleManager getStyleManager(Scene scene) {
     130         final Reference<StyleManager> styleManagerRef =
     131                 scene != null ? managerReferenceMap.get(scene) : null;
     132         return styleManagerRef != null ? styleManagerRef.get() : null;
     133     }
     134

     135     /**
     136      *
     137      * @param scene
     138      */
     139     public StyleManager(Scene scene) {
     140
     141         if (scene == null) throw new IllegalArgumentException("scene cannot be null");
     142
     143         this.rootScene = this.scene = scene;
     144
     145         cacheMap = new HashMap<Key, Cache>();
     146
     147         referencedStylesheets = new ArrayList<Stylesheet>();
     148         if (defaultUserAgentStylesheet != null) {
     149             referencedStylesheets.add(defaultUserAgentStylesheet);
     150         }
     151
     152         styleCache = new HashMap<StyleCacheKey, StyleCacheBucket>();
     153
     154         key = new Key();
     155
     156         smapCount = 0;
     157
     158         // A Reference is needed here since Scene is both in the key and value
     159         managerReferenceMap.put(scene, new WeakReference<StyleManager>(this));
     160
     161         // We have special handling for the case of a node which is in a popup
     162         // scene. The problem is that the root scene used for the purpose of style
     163         // sheet support isn't the scene of the popup (which is a hidden API
     164         // member), but rather the main stage's scene. So we need the scene of the
     165         // root stage.
     166         Window window = scene.getWindow();
     167         while (window instanceof PopupWindow) {
     168             window = ((PopupWindow)window).getOwnerWindow();
     169         }
     170
     171         // A popup owner's window might not be set yet.
     172         if (window == null) scene.windowProperty().addListener(windowListener);
     173     }
     174
     175     private final ChangeListener<Window> windowListener = new ChangeListener<Window>() {
     176
     177         @Override
     178         public void changed(ObservableValue<? extends Window> observable, Window oldValue, Window newValue) {
     179
     180             if (newValue != null) {
     181
     182                 observable.removeListener(this);
     183
     184                 if (newValue instanceof PopupWindow) {
     185
     186                     //
     187                     // Find the owner window of this popup
     188                     //
     189                     Window window = newValue;
     190                     PopupWindow popup = null;
     191                     while (window instanceof PopupWindow) {
     192                         popup = (PopupWindow)window;
     193                         window = popup.getOwnerWindow();
     194                     }
     195
     196                     Scene root_scene = null;
     197                     if (window != null) {
     198                         root_scene = window.getScene();
     199                     }
     200
     201                     if (root_scene != null) {
     202                         setRootScene(root_scene);
     203                     } else {
     204                         //
     205                         // Still don't have the root Scene. Keep listening.
     206                         //
     207                         popup.ownerWindowProperty().addListener(this);
     208                     }
     209
     210                 } else {
     211
     212                     // if the newWindow isn't a popup window, then
     213                     // we've found the owner window
     214                     setRootScene(newValue.getScene());
     215                 }
     216             }
     217         }
     218     };
     219
     220     private void setRootScene(Scene root_scene) {
     221
     222         assert(root_scene != null);
     223
     224         if (root_scene != this.scene) {
     225
     226             // If the root Scene's stylesheets change, then this popup's styles
     227             // need to be updated.
     228             root_scene.getStylesheets().addListener(new ListChangeListener<String>() {
     229
     230                 @Override
     231                 public void onChanged(Change<? extends String> c) {
     232                     updateStylesheets();
     233                 }
     234             });
     235             this.rootScene = root_scene;
     236             scene.getRoot().impl_reapplyCSS();
     237
     238         }
     239
     240     }
     241
     242     /*
     243      * The scene that created this StyleManager.
     244      */
     245     private final Scene scene;
     246     Scene getScene() { return scene; } // for testing
     247     /*
     248      * In the case of a popup, this is the Scene of the ownerWindow. If
     249      * the Scene's window is not a PopupWindow, then the rootScene == scene.
     250      */
     251     private Scene rootScene; // TODO: potential leak?
     252     Scene getRootScene() { return rootScene; } // for testing
     253
     254     /*
     255      * Stylesheets that are referenced by this Scene. The parsed stylesheets
     256      * are held in the StylesheetContainer and could be looked up from there,
     257      * but that lookup would be frequent so we hang onto the parsed stylesheets
     258      */
     259     private List<Stylesheet> referencedStylesheets;
     260     // for testing
     261     List<Stylesheet> getReferencedStylesheets() { return referencedStylesheets; }
     262

     263     /*
     264      * Scene does not have a inline style, but an applet might. This is
     265      * the applet's stylesheet from a 'style' attribute.
     266      */
     267     private Stylesheet inlineStylesheet;
     268     // for testing
     269     Stylesheet getInlineStylesheet() { return inlineStylesheet; }
     270
     271     /**
     272      * The map of Caches, key'd by a combination of class name, style class, and
     273      * id.
     274      */
     275     private Map<Key, Cache> cacheMap;
     276     // for testing
     277     Map<Key, Cache> getCacheMap() { return cacheMap; }
     278
     279     /**
     280      * This instance is reused for each lookup in the cache so as to avoid
     281      * creating temporary objects as much as reasonable.
     282      */
     283     private final Key key;
     284
     285     /*
     286      * The StyleHelper's cached values are relevant only for a given scene.
     287      * Since a StylesheetContainer is created for a Scene with stylesheets,
     288      * it makes sense that the container should own the valueCache. This
     289      * way, each scene gets its own valueCache.
     290      */
     291     private Map<StyleCacheKey, StyleCacheBucket> styleCache;
     292     // for testing
     293     Map<StyleCacheKey, StyleCacheBucket> getStyleCache() { return styleCache; }
     294
     295     /*
     296      * A simple counter used to generate a unique id for a StyleMap.
     297      * This unique id is used by StyleHelper in figuring out which
     298      * style cache to use.
     299      */
     300     private int smapCount;
     301
     302     /**
     303      * A map from Scene => StyleManager. This provides us a way to find
     304      * the StyleManager which is owned by a given Scene without having to
     305      * add a getStyleManager public API to Scene. This map is used primarily
     306      * for getting the StyleManager of the owning Scene for a popup.
     307      */
     308     private static Map<Scene, Reference<StyleManager>> managerReferenceMap =
     309             new WeakHashMap<Scene, Reference<StyleManager>>();
     310
     311     /**
     312      * A map from String => Stylesheet for a Scene. If a stylesheet for the
     313      * given URL has already been loaded then we'll simply reuse the stylesheet
     314      * rather than loading a duplicate.
     315      */
     316     private static final Map<String,StylesheetContainer<Scene>> sceneStylesheetMap
     317         = new HashMap<String,StylesheetContainer<Scene>>();
     318
     319     /**
     320      * Another map from String => Stylesheet for a Parent. If a stylesheet for the
     321      * given URL has already been loaded then we'll simply reuse the stylesheet
     322      * rather than loading a duplicate.
     323      */
     324     private static final Map<String,StylesheetContainer<Parent>> parentStylesheetMap
     325         = new HashMap<String,StylesheetContainer<Parent>>();
     326
     327    /**
     328      * This stylesheet represents the "default" set of styles for the entire
     329      * platform. This is typically only set by the UI Controls module, and
     330      * otherwise is generally null. Whenever this variable changes (via the
     331      * setDefaultUserAgentStylesheet function call) then we will end up clearing
     332      * all of the caches.
     333      */
     334     private static Stylesheet defaultUserAgentStylesheet;
     335
     336
     337     /**
     338      * User agent stylesheets from Control.getUserAgentStylesheet.
     339      */
     340     private static final Map<String,Stylesheet> userAgentStylesheetMap =
     341             new HashMap<String,Stylesheet>();
     342
     343
     344     /*
     345      * A container for stylesheets and the Parents or Scenes that use them.
     346      * If a stylesheet is removed, then all other Parents or Scenes
     347      * that use that stylesheet should get new styles if the
     348      * stylesheet is added back in since the stylesheet may have been
     349      * removed and re-added because it was edited (typical of SceneBuilder).
     350      * This container provides the hooks to get back to those Parents or Scenes.
     351      *
     352      * StylesheetContainer<Parent> are created and added to parentStylesheetMap
     353      * in the method gatherParentStylesheets.
     354      *
     355      * StylesheetContainer<Scene> are created and added to sceneStylesheetMap in
     356      * the method updateStylesheets
     357      */
     358     private static class StylesheetContainer<T> {
     359         // the stylesheet url
     360         private final String fname;
     361         // the parsed stylesheet so we don't reparse for every parent that uses it
     362         private final Stylesheet stylesheet;
     363         // the parents or scenes that use this stylesheet. Typically, this list
     364         //  should be very small.
     365         private final RefList<T> users;
     366         // the keys for finding Cache entries that use this stylesheet.
     367         // This list should also be fairly small
     368         private final RefList<Key> keys;
     369
     370         private StylesheetContainer(String fname, Stylesheet stylesheet) {
     371             this.fname = fname;
     372             this.stylesheet = stylesheet;
     373             this.users = new RefList<T>();
     374             this.keys = new RefList<Key>();
     375         }
     376     }
     377
     378     /*
     379      * A list that holds references. Used by StylesheetContainer.
     380      * We only ever add to this list, or the whole container gets thrown
     381      * away.
     382      */
     383     private static class RefList<K> {
     384
     385         private final List<Reference<K>> list = new ArrayList<Reference<K>>();
     386
     387         private void add(K key) {
     388
     389             for (int n=list.size()-1; 0<=n; --n) {
     390                 final Reference<K> ref = list.get(n);
     391                 final K k = ref.get();
     392                 if (k == null) {
     393                     // stale reference, remove it.
     394                     list.remove(n);
     395                 } else {
     396                     // already have it, bail
     397                     if (k == key) return;
     398                 }
     399             }
     400             // not found, add it.
     401             list.add(new WeakReference<K>(key));
     402         }
     403
     404     }
     405
     406     ////////////////////////////////////////////////////////////////////////////
     407     //
     408     // Parent stylesheet handling
     409     //
     410     ////////////////////////////////////////////////////////////////////////////

     411
     412     /**
     413      * called from Parent's stylesheets property's onChanged method

     414      */
     415     public void parentStylesheetsChanged(Parent parent, Change<String> c) {
     416
     417         final Scene scene = parent.getScene();
     418         if (scene == null) return;
     419
     420         boolean wasRemoved = false;
     421
     422         while (c.next()) {
     423
     424             // RT-22565
     425             // If the String was removed, remove it from parentStylesheetMap
     426             // and remove all Caches that got styles from the stylesheet.
     427             // The stylesheet may still be referenced by some other parent,
     428             // in which case the stylesheet will be reparsed and added
     429             // back to parentStylesheetMap when the StyleHelper is recreated.
     430             // This incurs a some overhead since the stylesheet has to be
     431             // reparsed, but this keeps the other Parents that use this
     432             // in sync. For example, SceneBuilder will remove and then add
     433             // a stylesheet after it has been edited and all Parents that use
     434             // that stylesheet should get the new values.
     435             //
     436             if (c.wasRemoved()) {
     437
     438                 final List<String> list = c.getRemoved();
     439                 int nMax = list != null ? list.size() : 0;
     440                 for (int n=0; n<nMax; n++) {
     441                     final String fname = list.get(n);
     442
     443                     // remove this parent from the container and clear
     444                     // all caches used by this stylesheet
     445                     StylesheetContainer<Parent> psc = parentStylesheetMap.remove(fname);
     446                     if (psc != null) {
     447                         clearParentCache(psc);
     448                     }
     449                 }
     450             }
     451
     452             // RT-22565: only wasRemoved matters. If the logic was applied to
     453             // wasAdded, then the stylesheet would be reparsed each time a
     454             // a Parent added it.
     455
     456         }
     457         // parent uses change also
     458         c.reset();
     459     }
     460
     461     // RT-22565: Called from parentStylesheetsChanged to clear the cache entries
     462     // for parents that use the same stylesheet
     463     private void clearParentCache(StylesheetContainer<Parent> psc) {
     464
     465         final List<Reference<Parent>> parentList = psc.users.list;
     466         final List<Reference<Key>>    keyList    = psc.keys.list;
     467         for (int n=parentList.size()-1; 0<=n; --n) {
     468
     469             final Reference<Parent> ref = parentList.get(n);
     470             final Parent parent = ref.get();
     471             if (parent == null) continue;
     472
     473             final Scene scene = parent.getScene();
     474             if (scene == null) continue;
     475
     476             Reference<StyleManager> styleManagerRef = managerReferenceMap.get(scene);
     477             if (styleManagerRef == null) continue;
     478
     479             StyleManager styleManager = styleManagerRef.get();
     480             if (styleManager == null) continue;
     481
     482             styleManager.clearParentCache(keyList);
     483
     484             // tell parent it needs to reapply css
     485             parent.impl_reapplyCSS();
     486         }
     487     }
     488
     489     // RT-22565: Called from clearParentCache to clear the cache entries.
     490     private void clearParentCache(List<Reference<Key>> keyList) {
     491
     492         if (cacheMap.isEmpty()) return;
     493
     494         for (int n=keyList.size()-1; 0<=n; --n) {
     495
     496             final Reference<Key> ref = keyList.get(n);
     497             final Key key = ref.get();
     498             if (key == null) continue;
     499
     500             final Cache cache = cacheMap.remove(key);
     501         }
     502     }
     503
     504     ////////////////////////////////////////////////////////////////////////////
     505     //
     506     // Stylesheet loading
     507     //
     508     ////////////////////////////////////////////////////////////////////////////
     509     private static URL getURL(String urlStr) {
     510         try {
     511             return new URL(urlStr);
     512         } catch (MalformedURLException malf) {
     513             // This may be a relative URL, so try resolving
     514             // it using the application classloader
     515             ClassLoader cl = Thread.currentThread().getContextClassLoader();
     516             if (cl != null) {
     517                 String path = (urlStr != null && urlStr.startsWith("/"))
     518                         ? urlStr.substring(1) : urlStr;
     519                 return cl.getResource(path);
     520             }
     521             return null;
     522         }
     523     }
     524
     525     private static Stylesheet loadStylesheet(final String fname) {
     526         try {
     527             return loadStylesheetUnPrivileged(fname);
     528         } catch (java.security.AccessControlException ace) {
     529             if (logger().isLoggable(PlatformLogger.INFO)) {
     530                 logger().info("Could not load the stylesheet, trying with FilePermissions : " + fname);
     531             }
     532
     533             /*
     534             ** we got an access control exception, so
     535             ** we could be running from an applet/jnlp/or with a security manager.
     536             ** we'll allow the app to read a css file from our runtime jar,
     537             ** and give it one more chance.
     538             */
     539
     540             /*
     541             ** check that there are enough chars after the !/ to have a valid .css or .bss file name
     542             */
     543             if ((fname.length() < 7) && (fname.indexOf("!/") < fname.length()-7)) {
     544                 return null;
     545             }
     546
     547             /*
     548             **
     549             ** first check that it's actually looking for the same runtime jar
     550             ** that we're running from, and not some other file.
     692                     // fall back to the .css file.
     693                     // Note that 'parse' is toggled in the test.
     694                     url = getURL(name+".css");
     695                 }
     696             }
     697
     698             // either we failed to load the .bss file, or parse
     699             // was set to true.
     700             if ((url != null) && parse) {
     701                 stylesheet = CSSParser.getInstance().parse(url);
     702             }
     703
     704             if (stylesheet == null) {
     705                 if (errors != null) {
     706                     CssError error =
     707                         new CssError(
     708                             "Resource \""+fname+"\" not found."
     709                         );
     710                     errors.add(error);
     711                 }
     712                 if (logger().isLoggable(PlatformLogger.WARNING)) {
     713                     logger().warning(
     714                         String.format("Resource \"%s\" not found.", fname)
     715                     );
     716                 }
     717             }
     718             return stylesheet;
     719
     720         } catch (FileNotFoundException fnfe) {
     721             if (errors != null) {
     722                 CssError error =
     723                     new CssError(
     724                         "Stylesheet \""+fname+"\" not found."
     725                     );
     726                 errors.add(error);
     727             }
     728             if (logger().isLoggable(PlatformLogger.INFO)) {
     729                 logger().info("Could not find stylesheet: " + fname);//, fnfe);
     730             }
     731         } catch (IOException ioe) {
     732                 if (errors != null) {
     733                     CssError error =
     734                         new CssError(
     735                             "Could not load stylesheet: " + fname
     736                         );
     737                     errors.add(error);
     738                 }
     739             if (logger().isLoggable(PlatformLogger.INFO)) {
     740                 logger().info("Could not load stylesheet: " + fname);//, ioe);
     741             }
     742         }
     743         return null;
     744     }
     745
     746     ////////////////////////////////////////////////////////////////////////////
     747     //
     748     // User Agent stylesheet handling
     749     //
     750     ////////////////////////////////////////////////////////////////////////////
     751
     752     /**
     753      * Add a user agent stylesheet, possibly overriding styles in the default
     754      * user agent stylesheet.
     755      *
     756      * @param fname The file URL, either relative or absolute, as a String.
     757      */
     758     public static void addUserAgentStylesheet(String fname) {
     759         addUserAgentStylesheet(null, fname);
     760     }
     842     private static void userAgentStylesheetsChanged() {
     843
     844         final Iterator<Window> windows = Window.impl_getWindows();
     845         while (windows.hasNext()) {
     846             final Window window = windows.next();
     847             final Scene scene = window.getScene();
     848             if (scene == null) continue;
     849
     850             final Reference<StyleManager> styleManagerRef = managerReferenceMap.get(scene);
     851             final StyleManager styleManager = styleManagerRef.get();
     852             if (styleManager == null) continue;
     853
     854             styleManager.updateStylesheets();
     855             scene.getRoot().impl_reapplyCSS();
     856         }
     857
     858     }
     859
     860     ////////////////////////////////////////////////////////////////////////////
     861     //
     862     // Author stylesheet handling
     863     //
     864     ////////////////////////////////////////////////////////////////////////////
     865
     866     /**
     867      * Set a stylesheet for the scene from the given stylesheet string. This
     868      * stylesheet comes after user agent stylesheets but before author
     869      * stylesheets and applies only to this scene.
     870      */
     871     public static void setInlineStylesheet(Scene scene, Stylesheet stylesheet) {
     872
     873         if (scene == null) return;
     874
     875         StyleManager styleManager = StyleManager.getStyleManager(scene);
     876         if (styleManager != null) {
     877             styleManager.inlineStylesheet = stylesheet;
     878             styleManager.updateStylesheets();
     879         }
     880
     881     }
     882     /**
     883      * Creates stylesheets and associates them with the given scene.
     884      *
     885      * Called from Scene.stylesheets on replace. Attempts to reuse existing
     886      * stylesheets for duplicate urls (ie: if two stages are loaded with the same
     887      * stylesheet urls, then the same stylesheet instance will be reused).
     888      *
     889      * All applied styles and caches are deleted, and complete clean application
     890      * of styles is started with new stylesheets.
     891      *
     892      * @param scene     The scene to update stylesheets for
     893      */
     894     public void updateStylesheets() {
     895
     896         // RT-7788
     897         // If there is no stage, then we're dealing with something like a popup
     898         // or a scene that hasn't been added to a stage yet. While there may
     899         // be a stylesheet for this scene, it is possible that the user agent
     900         // stylesheet will get set some time after this call to updateStylesheets
     901         // and the containerMap will get reset. A scene with no stage won't get added
     902         // back into the containerMap unless its stylesheet changes (which
     903         // is how it got there in the first place
     904         if (scene.getWindow() == null) return;
     905
     906         //
     907         // Here we remove the Scene's container from the container map.
     908         // This ensures that the Scene's nodes will either get their
     909         // styles from the default container (if Scene.stylesheets is empty)
     910         // or that a new cachMap will be computed for the nodes from the
     911         // new set of styles.
     912         //
     913         // This should happen infrequently since adding and removing stylesheets
     914         // on the fly should be the exception rather than the rule.
     915         //
     916         // annihilate the cache
     917         referencedStylesheets.clear();
     918         clearCache();
     919
     920         // RT-20643
     921         CssError.setCurrentScene(scene);
     922
     923         if (defaultUserAgentStylesheet != null) {
     924             referencedStylesheets.add(defaultUserAgentStylesheet);
     925         }
     926
     927         if (userAgentStylesheetMap.isEmpty() == false) {
     928             // TODO: This isn't right. They should be in the same order that
     929             // they were first added.
     930             referencedStylesheets.addAll(userAgentStylesheetMap.values());
     931         }
     932
     933         // create the stylesheets, one per URL supplied
     934         List<String> stylesheetURLs = rootScene == scene
     935                 ? scene.getStylesheets()
     936                 : rootScene.getStylesheets();
     937
     938         for (int i = 0; i < stylesheetURLs.size(); i++) {
     939
     940             String url = stylesheetURLs.get(i);
     941             url = url != null ? url.trim() : null;
     942             if (url == null || url.isEmpty()) continue;
     943
     944             // Has someone already parsed this for us?
     945             StylesheetContainer<Scene> container = sceneStylesheetMap.get(url);
     946             if (container != null) {
     947
     948                 // let it be known that this Scene uses this stylesheet
     949                 container.users.add(scene);
     950
     951                 final Stylesheet stylesheet = container.stylesheet;
     952                 if (stylesheet != null) {
     953                     referencedStylesheets.add(stylesheet);
     954                 }
     955
     956             } else {
     957
     958                 Stylesheet stylesheet = null;
     959                 try {
     960
     961                     // RT-20643
     962                     CssError.setCurrentScene(scene);
     963
     964                     stylesheet = loadStylesheet(url);
     965
     966                     // RT-20643
     967                     CssError.setCurrentScene(null);
     968
     969                 } catch (Exception e) {
     970                     // If an exception occurred while loading one of the stylesheets
     971                     // then we will simply print warning into system.err and skip the
     972                     // stylesheet, allowing other stylesheets to attempt to load
     973                     if (logger().isLoggable(PlatformLogger.WARNING)) {
     974                         logger().warning("Cannot add stylesheet. %s\n", e.getLocalizedMessage());
     975                     }
     976                     // no telling what condition stylesheet is in. make it null
     977                     stylesheet = null;
     978
     979                 } finally {
     980
     981                     // stylesheet is added to the container even if it is null
     982                     // in order to prevent further attempts at parsing it.
     983                     container = new StylesheetContainer<Scene>(url, stylesheet);
     984                     container.users.add(scene);
     985                     if (stylesheet != null) {
     986                         referencedStylesheets.add(stylesheet);
     987                     }
     988
     989                 }
     990             }
     991         }
     992
     993     }
     994
     995     ////////////////////////////////////////////////////////////////////////////
     996     //
     997     // Pseudo-class state bit map handling
     998     //
     999     ////////////////////////////////////////////////////////////////////////////
    1000
    1001     private static final Map<String,Long> pseudoclassMasks = new HashMap<String,Long>();
    1002
    1003     public static long getPseudoclassMask(String pclass) {
    1004         Long mask = pseudoclassMasks.get(pclass);
    1005         if (mask == null) {
    1006             final int exp = pseudoclassMasks.size();
    1007             mask = Long.valueOf(1L << exp); // same as Math.pow(2,exp)
    1008             pseudoclassMasks.put(pclass,mask);
    1009         }
    1010         return mask.longValue();
    1011     }
    1012
    1013     static long getPseudoclassMask(List<String> pseudoclasses) {
    1014         long mask = 0;
    1015
    1016         final int max = pseudoclasses != null ? pseudoclasses.size() : -1;
    1022     }
    1023
    1024     public static List<String> getPseudoclassStrings(long mask) {
    1025         if (mask == 0) return Collections.EMPTY_LIST;
    1026
    1027         Map<Long,String> stringMap = new HashMap<Long,String>();
    1028         for (Entry<String,Long> entry : pseudoclassMasks.entrySet()) {
    1029             stringMap.put(entry.getValue(), entry.getKey());
    1030         }
    1031         List<String> strings = new ArrayList<String>();
    1032         for (long exp=0; exp < Long.SIZE; exp++) {
    1033             long key = (1L << exp) & mask;
    1034             if (key != 0) {
    1035                 String value = stringMap.get(key);
    1036                 if (value != null) strings.add(value);
    1037             }
    1038         }
    1039         return strings;
    1040     }
    1041
    1042     private void clearCache() {
    1043         styleCache.clear();
    1044         cacheMap.clear();
    1045         smapCount = 0;
    1046     }
    1047
    1048     private long nextSmapId() {
    1049         return ++smapCount;
    1050     }
    1051     //
    1052     // This int array represents the set of Parent's that have stylesheets.
    1053     // The indice is that of the Parent's hash code or -1 if
    1054     // the Parent has no stylesheet. See also the comments in the Key class.
    1055     // Note that the array is ordered from root at index zero to the
    1056     // leaf Node's parent at index length-1.
    1057     //
    1058
    1059     private static int[] getIndicesOfParentsWithStylesheets(Parent parent, int count) {
    1060         if (parent == null) {
    1061             return new int[count];
    1062         }
    1063         final int[] indices = getIndicesOfParentsWithStylesheets(parent.getParent(), ++count);
    1064         // logic elsewhere depends on indices of parents
    1065         // with no stylesheets being -1
    1066         if (parent.getStylesheets().isEmpty() == false) {
    1067             indices[indices.length - count] = parent.hashCode();
    1068         } else {
    1069             indices[indices.length - count] = -1;
    1070         }
    1071         return indices;
    1072     }
    1073
    1074     //
    1075     // recurse so that stylesheets of Parents closest to the root are
    1076     // added to the list first. The ensures that declarations for
    1077     // stylesheets further down the tree (closer to the leaf) have
    1078     // a higer ordinal in the cascade.
    1079     //
    1080     private List<StylesheetContainer<Parent>> gatherParentStylesheets(Parent parent) {
    1081
    1082         if (parent == null) {
    1083             return null;
    1084         }
    1085
    1086         final List<String> parentStylesheets = parent.impl_getAllParentStylesheets();
    1087
    1088         if (parentStylesheets == null || parentStylesheets.isEmpty()) {
    1089             return null;
    1090         }
    1091
    1092         final List<StylesheetContainer<Parent>> list = new ArrayList<StylesheetContainer<Parent>>();
    1093
    1094         // RT-20643
    1095         CssError.setCurrentScene(parent.getScene());
    1096
    1097         for (int n = 0, nMax = parentStylesheets.size(); n < nMax; n++) {
    1098             final String fname = parentStylesheets.get(n);
    1099             StylesheetContainer<Parent> container = null;
    1100             if (parentStylesheetMap.containsKey(fname)) {
    1101                 container = parentStylesheetMap.get(fname);
    1102                 // RT-22565: remember that this parent uses this stylesheet.
    1103                 // Later, if the cache is cleared, the parent is told to
    1104                 // reapply css.
    1105                 container.users.add(parent);
    1106             } else {
    1107                 final Stylesheet stylesheet = loadStylesheet(fname);
    1108                 // stylesheet may be null which would mean that some IOException
    1109                 // was thrown while trying to load it. Add it to the
    1110                 // parentStylesheetMap anyway as this will prevent further
    1111                 // attempts to parse the file
    1112                 container =
    1113                         new StylesheetContainer<Parent>(fname, stylesheet);
    1114                 // RT-22565: remember that this parent uses this stylesheet.
    1115                 // Later, if the cache is cleared, the parent is told to
    1116                 // reapply css.
    1117                 container.users.add(parent);
    1118                 parentStylesheetMap.put(fname, container);
    1119             }
    1120             if (container != null) {
    1121                 list.add(container);
    1122             }
    1123         }
    1124         // RT-20643
    1125         CssError.setCurrentScene(null);
    1126
    1127         return list;
    1128     }
    1129
    1130     /**
    1131      * Returns a StyleHelper for the given Node, or null if there are no styles
    1132      * for this node.
    1133      */
    1134     public StyleHelper getStyleHelper(Node node) {
    1135
    1136         // Populate our helper key with the class name, id, and style class
    1137         // of the node and lookup the associated Cache in the cacheMap
    1138         final String className = node.getClass().getName();
    1139         final String id = node.getId();
    1140         final List<String> styleClass = node.getStyleClass();
    1141
    1142         final int[] indicesOfParentsWithStylesheets =
    1143                 getIndicesOfParentsWithStylesheets(
    1144                 ((node instanceof Parent) ? (Parent) node : node.getParent()), 0);
    1145         //
    1146         // avoid calling gatherParentStylesheets later if we know there
    1147         // aren't any parent stylesheets. If there are parent stylesheets,
    1148         // then at least one element of indicesOfParentsWithStylesheets
    1149         // will be other than -1
    1150         //
    1151         boolean hasParentStylesheets = false;
    1152         for (int n = 0; n < indicesOfParentsWithStylesheets.length; n++) {
    1153             // assignment, not equality here!
    1154             if (hasParentStylesheets = (indicesOfParentsWithStylesheets[n] != -1)) {
    1155                 break;
    1156             }
    1157         }
    1158
    1159         key.className = className;
    1160         key.id = id;
    1161         key.styleClass = styleClass;
    1162         key.indices = hasParentStylesheets ? indicesOfParentsWithStylesheets : null;
    1163

    1164         Cache cache = cacheMap.get(key);
    1165
    1166         // the key is an instance variable and so we need to null the
    1167         // key.styleClass to prevent holding a hard reference to the
    1168         // styleClass (and its Node)
    1169         key.styleClass = null;
    1170
    1171         // If the cache is null, then we need to create a new Cache and
    1172         // add it to the cache map
    1173         if (cache == null) {
    1174             // Construct the list of Rules that could possibly apply
    1175             final List<Rule> rules = new ArrayList<Rule>();
    1176
    1177             /*
    1178              * A Set of all the pseudoclass states which, if they change, need
    1179              * to cause the Node to be set to UPDATE its CSS styles on the next
    1180              * pulse. For a full explanation, see the StyleHelper's
    1181              * mayImpactChildren variable.
    1182              */
    1183             long pseudoclassStateMask = 0;
    1184
    1185             // If the node can have children, look at the ancestors
    1186             // in a compound selector for pseudoclassBits
    1187             final boolean canHaveChildren = node instanceof Parent;
    1188
    1189             // It may be that there are no pseudoclassBits in the ancestor
    1190             // selectors, but if one of the ancestor selectors might apply,
    1191             // then the selector impacts its children.
    1192             boolean impactsChildren = false;
    1193
    1194             final List<StylesheetContainer<Parent>> parentStylesheets =
    1195                     hasParentStylesheets
    1196                     ? gatherParentStylesheets(
    1197                     ((node instanceof Parent) ? (Parent) node : node.getParent()))
    1198                     : null;
    1199
    1200             //
    1201             List<Stylesheet> stylesheetsToProcess = null;
    1202
    1203             if (parentStylesheets == null || parentStylesheets.isEmpty()) {
    1204                 stylesheetsToProcess = referencedStylesheets;
    1205             } else {
    1206
    1207                 //
    1208                 // if bypassing shared cache, then the node is a Parent
    1209                 // with stylesheets or has a Parent with stylesheets.
    1210                 // Gather up all of the Parent stylesheets for procesing.
    1211                 //

    1212
    1213                 // scene stylesheets come first since declarations from
    1214                 // Parent stylesheets should take precedence.
    1215                 stylesheetsToProcess = new ArrayList<Stylesheet>(parentStylesheets.size());
    1216                 for (int n = 0, nMax = parentStylesheets.size(); n < nMax; n++) {
    1217                     final StylesheetContainer<Parent> psc = parentStylesheets.get(n);
    1218                     stylesheetsToProcess.add(psc.stylesheet);
    1219                 }
    1220                 stylesheetsToProcess.addAll(0, referencedStylesheets);
    1221             }
    1222
    1223             for (int i = 0, imax = stylesheetsToProcess.size(); i < imax; i++) {
    1224                 final Stylesheet ss = stylesheetsToProcess.get(i);
    1225
    1226                 final List<Rule> stylesheetRules = ss != null ? ss.getRules() : null;
    1227                 if (stylesheetRules == null || stylesheetRules.isEmpty()) {
    1228                     continue;
    1229                 }
    1230
    1231                 for (int j = 0, jmax = stylesheetRules.size(); j < jmax; j++) {
    1232                     Rule rule = stylesheetRules.get(j);
    1233                     boolean mightApply = rule.mightApply(className, id, styleClass);
    1234                     if (mightApply) {
    1235                         rules.add(rule);
    1236                     }
    1237
    1238                     //
    1239                     // The following logic used to be in a separate routine,
    1240                     // but the need to flag compound selectors with no
    1241                     // pseudoclass state that might impact the node's
    1304                                         impactsChildren = true;
    1305                                     }
    1306                                 }
    1307                             }
    1308                             // Not a compound selector. If the selector might apply,
    1309                             // then save off the pseudoclass state. StyleHelper
    1310                             // checks this to see if a pseudoclass matters or not.
    1311                         } else if (mightApply) {
    1312                             SimpleSelector simple = (SimpleSelector) selector;
    1313                             pseudoclassStateMask = pseudoclassStateMask
    1314                                     | getPseudoclassMask(simple.getPseudoclasses());
    1315                         }
    1316                     }
    1317                 }
    1318             }
    1319
    1320             //
    1321             // regardless of whether or not the cache is shared, a Cache
    1322             // object is still needed in order to do the lookup.
    1323             //
    1324             final Key newKey = new Key();
    1325             newKey.className = className;
    1326             newKey.id = id;
    1327             // Copy the list.
    1328             // If the contents of the Node's styleClass changes,
    1329             // the cacheMap lookup should miss.
    1330             final int nElements = styleClass.size();
    1331             newKey.styleClass = new ArrayList<String>(nElements);
    1332             for (int n = 0; n < nElements; n++) {
    1333                 newKey.styleClass.add(styleClass.get(n));
    1334             }
    1335             newKey.indices = hasParentStylesheets ? indicesOfParentsWithStylesheets : null;
    1336
    1337             cache = new Cache(rules, pseudoclassStateMask, impactsChildren);
    1338             cacheMap.put(newKey, cache);
    1339
    1340             // RT-22565: remember where this cache is used if there are
    1341             // parent stylesheets involve so the cache can be cleared later
    1342             // from the parentStylesheetsChanged method.
    1343             final int nMax = parentStylesheets != null ? parentStylesheets.size() : 0;
    1344             for (int n = 0; n < nMax; n++) {
    1345                 final StylesheetContainer<Parent> psc = parentStylesheets.get(n);
    1346                 psc.keys.add(newKey);
    1347             }
    1348
    1349         }

    1350         // Return the style helper looked up by the cache. The cache will
    1351         // create a style helper if necessary (and possible), so we don't
    1352         // have to worry about that part.
    1353         StyleMap smap = cache.getStyleMap(this, node);
    1354         if (smap == null) {
    1355             return null;
    1356         }
    1357
    1358         StyleHelper helper =
    1359                 StyleHelper.create(
    1360                 node,
    1361                 smap.map,
    1362                 styleCache,
    1363                 cache.pseudoclassStateMask,
    1364                 smap.uniqueId);

    1365         return helper;
    1366     }
    1367
    1368     ////////////////////////////////////////////////////////////////////////////
    1369     //
    1370     // CssError reporting
    1371     //
    1372     ////////////////////////////////////////////////////////////////////////////
    1373
    1374     private static ObservableList<CssError> errors = null;
    1375     /**
    1376      * Errors that may have occurred during css processing.
    1377      * This list is null until errorsProperty() is called.
    1378      * @return
    1379      */
    1380     public static ObservableList<CssError> errorsProperty() {
    1381         if (errors == null) {
    1382             errors = FXCollections.observableArrayList();
    1383         }
    1384         return errors;
    1403     ////////////////////////////////////////////////////////////////////////////
    1404
    1405     //
    1406     // Used by StyleHelper. The key uniquely identifies this style map.
    1407     // These keys are used by StyleHelper in creating its StyleCacheKey.
    1408     // The StyleCacheKey is used to lookup calculated values in cache.
    1409     //
    1410     static class StyleMap {
    1411         final long uniqueId; // unique per container
    1412         final Map<String, List<CascadingStyle>> map;
    1413         private StyleMap(long key, Map<String, List<CascadingStyle>> map) {
    1414             this.uniqueId = key;
    1415             this.map  = map;
    1416         }
    1417
    1418     }
    1419
    1420     /**
    1421      * Creates and caches maps of styles, reusing them as often as practical.
    1422      */
    1423     private static class Cache {
    1424
    1425         // this must be initialized to the appropriate possible rules when
    1426         // the helper cache is created by the StylesheetContainer
    1427         private final List<Rule> rules;
    1428         private final long pseudoclassStateMask;
    1429         private final boolean impactsChildren;
    1430         private final Map<Long, StyleMap> cache;
    1431
    1432         Cache(List<Rule> rules, long pseudoclassStateMask, boolean impactsChildren) {
    1433             this.rules = rules;
    1434             this.pseudoclassStateMask = pseudoclassStateMask;
    1435             this.impactsChildren = impactsChildren;
    1436             this.cache = new HashMap<Long, StyleMap>();
    1437         }
    1438
    1439         private StyleMap getStyleMap(StyleManager owner, Node node) {
    1440
    1441             // If this set of rules (which may be empty) impacts children,
    1442             // then this node will get a StyleHelper.
    1443             if (!impactsChildren) {
    1546                                 // there may be more than one declaration for the
    1547                                 // same attribute within a rule or within a stylesheet
    1548                                 ordinal++
    1549                             );
    1550
    1551                         styles.add(s);
    1552                         }
    1553                     }
    1554                 }
    1555
    1556                 // return sorted styles
    1557             Collections.sort(styles);
    1558             return styles;
    1559             }
    1560
    1561         }
    1562
    1563     /**
    1564      * The key used in the cacheMap of the StylesheetContainer
    1565      */
    1566     private static class Key {

    1567         // note that the class name here is the *full* class name, such as
    1568         // javafx.scene.control.Button. We only bother parsing this down to the
    1569         // last part when doing matching against selectors, and so want to avoid
    1570         // having to do a bunch of preliminary parsing in places where it isn't
    1571         // necessary.
    1572         String className;
    1573         String id;
    1574         List<String> styleClass;
    1575
    1576         // this will be initialized if a Parent has a stylesheet and will
    1577         // hold the indices of those Parents with stylesheets (the Parent's
    1578         // hash code is used). If the parent does not have a stylesheet, the
    1579         // indice will be -1. When finding the equals Key in the cache lookup,
    1580         // we only need to check up to the last parent that has a stylesheet.
    1581         // In other words, if one node is at level n and nearest parent with
    1582         // a stylesheet is at level m, then the indices match provided
    1583         // indices[x] == other.indices[x] for 0 <= x <= m and all other
    1584         // indices are -1. Thus, [1, -1, 2] and [1, -1, 2, -1, -1] are equivalent
    1585         // whereas [1, -1, 2] and [1, -1, 2, -1, 3] are not.
    1586         int[] indices;
    1587
    1588         @Override
    1589         public boolean equals(Object o) {
    1590             if (this == o) return true;
    1591             if (o instanceof Key) {
    1592                 Key other = (Key)o;
    1593                 boolean eq =
    1594                         className.equals(other.className)
    1595                     && (   (indices == null && other.indices == null)
    1596                         || (indices != null && other.indices != null)
    1597                        )
    1598                     && (   (id == null && other.id == null)
    1599                         || (id != null && id.equals(other.id))
    1600                        )
    1601                     && (   (styleClass == null && other.styleClass == null)
    1602                         || (styleClass != null && styleClass.containsAll(other.styleClass))
    1603                        );
    1604
    1605                 if (eq && indices != null) {
    1606                     final int max = Math.min(indices.length, other.indices.length);
    1607                     for (int x = 0; eq && (x < max); x++) {
    1608                         eq = indices[x] == other.indices[x];
    1609                     }
    1610                     if (eq) {
    1611                         // ensure the remainder are -1
    1612                         for(int x=max; eq && x<indices.length; x++) {
    1613                             eq = indices[x] == -1;
    1614                         }
    1615                         for(int x=max; eq && x<other.indices.length; x++) {
    1616                             eq = other.indices[x] == -1;
    1617                         }
    1618                     }
    1619                 }
    1620                 return eq;
    1621             }
    1622             return false;
    1623         }
    1624
    1625         @Override
    1626         public int hashCode() {
    1627             int hash = className.hashCode();
    1628             hash = 31 * (hash + ((id == null || id.isEmpty()) ? 1231 : id.hashCode()));
    1629             hash = 31 * (hash + ((styleClass == null || styleClass.isEmpty()) ? 1237 : styleClass.hashCode()));
    1630             if (indices != null) hash = 31 * (hash + Arrays.hashCode(indices));
    1631             return hash;
    1632         }
    1633
    1634         @Override
    1635         public String toString() {
    1636             return "Key ["+className+", "+String.valueOf(id)+", "+String.valueOf(styleClass)+"]";
    1637         }
    1638
    1639     }
    1640
    1641 }
       8  * particular file as subject to the "Classpath" exception as provided
       9  * by Oracle in the LICENSE file that accompanied this code.
      10  *
      11  * This code is distributed in the hope that it will be useful, but WITHOUT
      12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
      13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
      14  * version 2 for more details (a copy is included in the LICENSE file that
      15  * accompanied this code).
      16  *
      17  * You should have received a copy of the GNU General Public License version
      18  * 2 along with this work; if not, write to the Free Software Foundation,
      19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
      20  *
      21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
      22  * or visit www.oracle.com if you need additional information or have any
      23  * questions.
      24  */
      25
      26 package com.sun.javafx.css;
      27

      28 import java.io.IOException;
      29 import java.net.MalformedURLException;
      30 import java.net.URI;
      31 import java.net.URL;
      32 import java.io.FilePermission;
      33 import java.security.AccessControlContext;
      34 import java.security.AccessController;
      35 import java.security.PermissionCollection;
      36 import java.security.ProtectionDomain;
      37 import java.util.ArrayList;

      38 import java.util.Collections;
      39 import java.util.HashMap;
      40 import java.util.Iterator;
      41 import java.util.List;
      42 import java.util.Map;
      43 import java.util.jar.JarEntry;
      44 import java.util.jar.JarFile;
      45
      46 import javafx.collections.FXCollections;
      47 import javafx.scene.Node;
      48 import javafx.scene.Scene;
      49
      50 import com.sun.javafx.css.parser.CSSParser;
      51 import com.sun.javafx.css.StyleHelper.StyleCacheBucket;
      52 import com.sun.javafx.css.StyleHelper.StyleCacheKey;
      53 import com.sun.javafx.logging.PlatformLogger;
      54 import java.io.FileNotFoundException;
      55 import java.lang.ref.Reference;

      56 import java.lang.ref.WeakReference;
      57 import java.security.PrivilegedAction;
      58 import java.security.PrivilegedExceptionAction;
      59 import java.security.PrivilegedActionException;
      60 import java.util.*;
      61 import java.util.Map.Entry;

      62 import javafx.collections.*;

      63 import javafx.scene.Parent;

      64 import javafx.stage.Window;
      65
      66 /**
      67  * Contains the stylesheet state for a single scene. This includes both the
      68  * Stylesheets defined on the Scene itself as well as a map of stylesheets for
      69  * "style"s defined on the Node itself. These containers are kept in the
      70  * containerMap, key'd by the Scene to which they belong. <p> One of the key
      71  * responsibilities of the StylesheetContainer is to create and maintain an
      72  * admittedly elaborate series of caches so as to minimize the amount of time it
      73  * takes to match a Node to its eventual StyleHelper, and to reuse the
      74  * StyleHelper as much as possible. <p> Initially, the cache is empty. It is
      75  * recreated whenever the userStylesheets on the container change, or whenever
      76  * the userAgentStylesheet changes. The cache is built up as nodes are looked
      77  * for, and thus there is some overhead associated with the first lookup but
      78  * which is then not repeated for subsequent lookups. <p> The cache system used
      79  * is a two level cache. The first level cache simply maps the
      80  * classname/id/styleclass combination of the request node to a 2nd level cache.
      81  * If the node has "styles" specified then we still use this 2nd level cache,
      82  * but must combine its rules with the rules specified in "styles" and perform
      83  * more work to cascade properly. <p> The 2nd level cache contains a data
      87  * rules which may apply are any rule where the simple selector of the rule
      88  * contains a reference to the id, style class, or classname of the Node, or a
      89  * compound selector who's "descendant" part is a simple selector which contains
      90  * a reference to the id, style class, or classname of the Node. <p> During
      91  * lookup, we will iterate over all the potential rules and discover if they
      92  * apply to this particular node. If so, then we toggle a bit position in the
      93  * Long corresponding to the position of the rule that matched. This long then
      94  * becomes our key into the final map. <p> Once we have established our key, we
      95  * will visit the map and look for an existing StyleHelper. If we find a
      96  * StyleHelper, then we will return it. If not, then we will take the Rules that
      97  * matched and construct a new StyleHelper from their various parts. <p> This
      98  * system, while elaborate, also provides for numerous fast paths and sharing of
      99  * data structures which should dramatically reduce the memory and runtime
     100  * performance overhead associated with CSS by reducing the matching overhead
     101  * and caching as much as possible. We make no attempt to use weak references
     102  * here, so if memory issues result one work around would be to toggle the root
     103  * user agent stylesheet or stylesheets on the scene to cause the cache to be
     104  * flushed.
     105  */
     106
     107 abstract public class StyleManager<T> {
     108
     109     protected static PlatformLogger LOGGER = com.sun.javafx.Logging.getCSSLogger();

     110
     111     /**
     112      * Return the StyleManager for the given Scene, or null if the scene
     113      * is no longer referenced.
     114      */
     115     public static <T> StyleManager getStyleManager(T key) {
     116         final Reference<StyleManager> styleManagerRef =
     117                 key != null ? managerReferenceMap.get(key) : null;
     118         return styleManagerRef != null ? styleManagerRef.get() : null;
     119     }
     120
     121     public static StyleManager createStyleManager(Scene scene) {
     122         return new SceneStyleManager(scene);
     123     }
     124
     125     public static StyleManager createStyleManager(Parent parent, StyleManager ancestorStyleManager) {
     126         return new ParentStyleManager(parent, ancestorStyleManager);
     127     }
     128
     129     /**
     130      *
     131      * @param owner
     132      */
     133     protected StyleManager(T owner) {
     134
     135         if (owner == null) throw new IllegalArgumentException("scene cannot be null");
     136
     137         this.owner = owner;
     138
     139         cacheMap = new HashMap<Key, Cache>();
     140
     141         referencedStylesheets = new ArrayList<Stylesheet>();
     142         if (defaultUserAgentStylesheet != null) {
     143             referencedStylesheets.add(defaultUserAgentStylesheet);
     144         }
     145
     146         styleCache = new HashMap<StyleCacheKey, StyleCacheBucket>();
     147

     148         smapCount = 0;
     149
     150         // A Reference is needed here since Scene is both in the key and value
     151         managerReferenceMap.put(owner, new WeakReference<StyleManager>(this));

     152
     153     }
     154
     155     /*
     156      * The scene or parent that created this StyleManager.

     157      */
     158     protected final T owner;
     159     T getOwner() { return owner; } // for testing
     160
     161     /*
     162      * Stylesheets that are referenced by this Scene. The parsed stylesheets
     163      * are held in the StylesheetContainer and could be looked up from there,
     164      * but that lookup would be frequent so we hang onto the parsed stylesheets
     165      */
     166     protected final List<Stylesheet> referencedStylesheets;
     167     // for testing
     168     List<Stylesheet> getReferencedStylesheets() { return referencedStylesheets; }
     169
     170
     171     /*
     172      * Scene does not have a inline style, but an applet might. This is
     173      * the applet's stylesheet from a 'style' attribute.
     174      */
     175     protected Stylesheet inlineStylesheet;
     176     // for testing
     177     Stylesheet getInlineStylesheet() { return inlineStylesheet; }
     178
     179     /**
     180      * The map of Caches, key'd by a combination of class name, style class, and
     181      * id.
     182      */
     183     protected final Map<Key, Cache> cacheMap;
     184     // for testing
     185     Map<Key, Cache> getCacheMap() { return cacheMap; }
     186

     187     /*
     188      * The StyleHelper's cached values are relevant only for a given scene.
     189      * Since a StylesheetContainer is created for a Scene with stylesheets,
     190      * it makes sense that the container should own the valueCache. This
     191      * way, each scene gets its own valueCache.
     192      */
     193     private final Map<StyleCacheKey, StyleCacheBucket> styleCache;
     194     // for testing
     195     Map<StyleCacheKey, StyleCacheBucket> getStyleCache() { return styleCache; }
     196
     197     /*
     198      * A simple counter used to generate a unique id for a StyleMap.
     199      * This unique id is used by StyleHelper in figuring out which
     200      * style cache to use.
     201      */
     202     private int smapCount;
     203
     204     /**
     205      * A map from Scene => StyleManager. This provides us a way to find
     206      * the StyleManager which is owned by a given Scene without having to
     207      * add a getStyleManager public API to Scene. This map is used primarily
     208      * for getting the StyleManager of the owning Scene for a popup.
     209      */
     210     protected static final Map<Object, Reference<StyleManager>> managerReferenceMap =
     211             new WeakHashMap<Object, Reference<StyleManager>>();

     212
     213    /**
     214      * This stylesheet represents the "default" set of styles for the entire
     215      * platform. This is typically only set by the UI Controls module, and
     216      * otherwise is generally null. Whenever this variable changes (via the
     217      * setDefaultUserAgentStylesheet function call) then we will end up clearing
     218      * all of the caches.
     219      */
     220     protected static Stylesheet defaultUserAgentStylesheet;
     221
     222
     223     /**
     224      * User agent stylesheets from Control.getUserAgentStylesheet.
     225      */
     226     protected static final Map<String,Stylesheet> userAgentStylesheetMap =
     227             new HashMap<String,Stylesheet>();
     228
     229
     230     /*
     231      * A container for stylesheets and the Parents or Scenes that use them.
     232      * If a stylesheet is removed, then all other Parents or Scenes
     233      * that use that stylesheet should get new styles if the
     234      * stylesheet is added back in since the stylesheet may have been
     235      * removed and re-added because it was edited (typical of SceneBuilder).
     236      * This container provides the hooks to get back to those Parents or Scenes.
     237      *
     238      * StylesheetContainer<Parent> are created and added to parentStylesheetMap
     239      * in the method gatherParentStylesheets.
     240      *
     241      * StylesheetContainer<Scene> are created and added to sceneStylesheetMap in
     242      * the method updateStylesheets
     243      */
     244     protected static class StylesheetContainer<T> {
     245         // the stylesheet url
     246         final String fname;
     247         // the parsed stylesheet so we don't reparse for every parent that uses it
     248         final Stylesheet stylesheet;
     249         // the parents or scenes that use this stylesheet. Typically, this list
     250         //  should be very small.
     251         final RefList<T> users;
     252         // the keys for finding Cache entries that use this stylesheet.
     253         // This list should also be fairly small
     254         final RefList<Key> keys;
     255
     256         StylesheetContainer(String fname, Stylesheet stylesheet) {
     257             this.fname = fname;
     258             this.stylesheet = stylesheet;
     259             this.users = new RefList<T>();
     260             this.keys = new RefList<Key>();
     261         }
     262     }
     263
     264     /*
     265      * A list that holds references. Used by StylesheetContainer.
     266      * We only ever add to this list, or the whole container gets thrown
     267      * away.
     268      */
     269     protected static class RefList<K> {
     270
     271         final List<Reference<K>> list = new ArrayList<Reference<K>>();
     272
     273         void add(K key) {
     274
     275             for (int n=list.size()-1; 0<=n; --n) {
     276                 final Reference<K> ref = list.get(n);
     277                 final K k = ref.get();
     278                 if (k == null) {
     279                     // stale reference, remove it.
     280                     list.remove(n);
     281                 } else {
     282                     // already have it, bail
     283                     if (k == key) return;
     284                 }
     285             }
     286             // not found, add it.
     287             list.add(new WeakReference<K>(key));
     288         }
     289
     290     }
     291
     292     public abstract void updateStylesheets();
     293
     294     public void stylesheetsChanged(ListChangeListener.Change<String> c) {
     295         // for now, just call updateStylesheets
     296         updateStylesheets();
     297     }
     298
     299     /**
     300      * Set a stylesheet for the scene from the given stylesheet string. This
     301      * stylesheet comes after user agent stylesheets but before author
     302      * stylesheets and applies only to this scene.
     303      */
     304     public static void setInlineStylesheet(Scene scene, Stylesheet stylesheet) {
     305

     306         if (scene == null) return;
     307
     308         StyleManager styleManager = StyleManager.getStyleManager(scene);
     309         if (styleManager != null) {
     310             styleManager.inlineStylesheet = stylesheet;
     311             styleManager.updateStylesheets();

     312         }
     313

     314     }

     315

     316
     317     ////////////////////////////////////////////////////////////////////////////
     318     //
     319     // Stylesheet loading
     320     //
     321     ////////////////////////////////////////////////////////////////////////////
     322     private static URL getURL(String urlStr) {
     323         try {
     324             return new URL(urlStr);
     325         } catch (MalformedURLException malf) {
     326             // This may be a relative URL, so try resolving
     327             // it using the application classloader
     328             ClassLoader cl = Thread.currentThread().getContextClassLoader();
     329             if (cl != null) {
     330                 String path = (urlStr != null && urlStr.startsWith("/"))
     331                         ? urlStr.substring(1) : urlStr;
     332                 return cl.getResource(path);
     333             }
     334             return null;
     335         }
     336     }
     337
     338     protected static Stylesheet loadStylesheet(final String fname) {
     339         try {
     340             return loadStylesheetUnPrivileged(fname);
     341         } catch (java.security.AccessControlException ace) {
     342             if (LOGGER.isLoggable(PlatformLogger.INFO)) {
     343                 LOGGER.info("Could not load the stylesheet, trying with FilePermissions : " + fname);
     344             }
     345
     346             /*
     347             ** we got an access control exception, so
     348             ** we could be running from an applet/jnlp/or with a security manager.
     349             ** we'll allow the app to read a css file from our runtime jar,
     350             ** and give it one more chance.
     351             */
     352
     353             /*
     354             ** check that there are enough chars after the !/ to have a valid .css or .bss file name
     355             */
     356             if ((fname.length() < 7) && (fname.indexOf("!/") < fname.length()-7)) {
     357                 return null;
     358             }
     359
     360             /*
     361             **
     362             ** first check that it's actually looking for the same runtime jar
     363             ** that we're running from, and not some other file.
     505                     // fall back to the .css file.
     506                     // Note that 'parse' is toggled in the test.
     507                     url = getURL(name+".css");
     508                 }
     509             }
     510
     511             // either we failed to load the .bss file, or parse
     512             // was set to true.
     513             if ((url != null) && parse) {
     514                 stylesheet = CSSParser.getInstance().parse(url);
     515             }
     516
     517             if (stylesheet == null) {
     518                 if (errors != null) {
     519                     CssError error =
     520                         new CssError(
     521                             "Resource \""+fname+"\" not found."
     522                         );
     523                     errors.add(error);
     524                 }
     525                 if (LOGGER.isLoggable(PlatformLogger.WARNING)) {
     526                     LOGGER.warning(
     527                         String.format("Resource \"%s\" not found.", fname)
     528                     );
     529                 }
     530             }
     531             return stylesheet;
     532
     533         } catch (FileNotFoundException fnfe) {
     534             if (errors != null) {
     535                 CssError error =
     536                     new CssError(
     537                         "Stylesheet \""+fname+"\" not found."
     538                     );
     539                 errors.add(error);
     540             }
     541             if (LOGGER.isLoggable(PlatformLogger.INFO)) {
     542                 LOGGER.info("Could not find stylesheet: " + fname);//, fnfe);
     543             }
     544         } catch (IOException ioe) {
     545                 if (errors != null) {
     546                     CssError error =
     547                         new CssError(
     548                             "Could not load stylesheet: " + fname
     549                         );
     550                     errors.add(error);
     551                 }
     552             if (LOGGER.isLoggable(PlatformLogger.INFO)) {
     553                 LOGGER.info("Could not load stylesheet: " + fname);//, ioe);
     554             }
     555         }
     556         return null;
     557     }
     558
     559     ////////////////////////////////////////////////////////////////////////////
     560     //
     561     // User Agent stylesheet handling
     562     //
     563     ////////////////////////////////////////////////////////////////////////////
     564
     565     /**
     566      * Add a user agent stylesheet, possibly overriding styles in the default
     567      * user agent stylesheet.
     568      *
     569      * @param fname The file URL, either relative or absolute, as a String.
     570      */
     571     public static void addUserAgentStylesheet(String fname) {
     572         addUserAgentStylesheet(null, fname);
     573     }
     655     private static void userAgentStylesheetsChanged() {
     656
     657         final Iterator<Window> windows = Window.impl_getWindows();
     658         while (windows.hasNext()) {
     659             final Window window = windows.next();
     660             final Scene scene = window.getScene();
     661             if (scene == null) continue;
     662
     663             final Reference<StyleManager> styleManagerRef = managerReferenceMap.get(scene);
     664             final StyleManager styleManager = styleManagerRef.get();
     665             if (styleManager == null) continue;
     666
     667             styleManager.updateStylesheets();
     668             scene.getRoot().impl_reapplyCSS();
     669         }
     670
     671     }
     672
     673     ////////////////////////////////////////////////////////////////////////////
     674     //

     675     // Pseudo-class state bit map handling
     676     //
     677     ////////////////////////////////////////////////////////////////////////////
     678
     679     private static final Map<String,Long> pseudoclassMasks = new HashMap<String,Long>();
     680
     681     public static long getPseudoclassMask(String pclass) {
     682         Long mask = pseudoclassMasks.get(pclass);
     683         if (mask == null) {
     684             final int exp = pseudoclassMasks.size();
     685             mask = Long.valueOf(1L << exp); // same as Math.pow(2,exp)
     686             pseudoclassMasks.put(pclass,mask);
     687         }
     688         return mask.longValue();
     689     }
     690
     691     static long getPseudoclassMask(List<String> pseudoclasses) {
     692         long mask = 0;
     693
     694         final int max = pseudoclasses != null ? pseudoclasses.size() : -1;
     700     }
     701
     702     public static List<String> getPseudoclassStrings(long mask) {
     703         if (mask == 0) return Collections.EMPTY_LIST;
     704
     705         Map<Long,String> stringMap = new HashMap<Long,String>();
     706         for (Entry<String,Long> entry : pseudoclassMasks.entrySet()) {
     707             stringMap.put(entry.getValue(), entry.getKey());
     708         }
     709         List<String> strings = new ArrayList<String>();
     710         for (long exp=0; exp < Long.SIZE; exp++) {
     711             long key = (1L << exp) & mask;
     712             if (key != 0) {
     713                 String value = stringMap.get(key);
     714                 if (value != null) strings.add(value);
     715             }
     716         }
     717         return strings;
     718     }
     719
     720     protected final void clearCache() {
     721         styleCache.clear();
     722         cacheMap.clear();
     723         smapCount = 0;
     724     }
     725
     726     private long nextSmapId() {
     727         return ++smapCount;
     728     }

     729
     730     abstract protected Key getKey(Node node);

     731
     732     /**
     733      * Returns a StyleHelper for the given Node, or null if there are no styles
     734      * for this node.
     735      */
     736     public final StyleHelper getStyleHelper(Node node) {

     737
     738         final Key key = getKey(node);
     739         Cache cache = cacheMap.get(key);
     740

     741         // If the cache is null, then we need to create a new Cache and
     742         // add it to the cache map
     743         if (cache == null) {
     744             // Construct the list of Rules that could possibly apply
     745             final List<Rule> rules = new ArrayList<Rule>();
     746
     747             /*
     748              * A Set of all the pseudoclass states which, if they change, need
     749              * to cause the Node to be set to UPDATE its CSS styles on the next
     750              * pulse. For a full explanation, see the StyleHelper's
     751              * mayImpactChildren variable.
     752              */
     753             long pseudoclassStateMask = 0;
     754
     755             // If the node can have children, look at the ancestors
     756             // in a compound selector for pseudoclassBits
     757             final boolean canHaveChildren = node instanceof Parent;
     758
     759             // It may be that there are no pseudoclassBits in the ancestor
     760             // selectors, but if one of the ancestor selectors might apply,
     761             // then the selector impacts its children.
     762             boolean impactsChildren = false;
     763

     764             //
     765             final String className = node.getClass().getName();
     766             final String id = node.getId();
     767             final List<String> styleClass = node.getStyleClass();
     768
     769             List<Stylesheet> stylesheetsToProcess = referencedStylesheets;

     770
     771             for (int i = 0, imax = stylesheetsToProcess.size(); i < imax; i++) {
     772                 final Stylesheet ss = stylesheetsToProcess.get(i);
     773
     774                 final List<Rule> stylesheetRules = ss != null ? ss.getRules() : null;
     775                 if (stylesheetRules == null || stylesheetRules.isEmpty()) {
     776                     continue;
     777                 }
     778
     779                 for (int j = 0, jmax = stylesheetRules.size(); j < jmax; j++) {
     780                     Rule rule = stylesheetRules.get(j);
     781                     boolean mightApply = rule.mightApply(className, id, styleClass);
     782                     if (mightApply) {
     783                         rules.add(rule);
     784                     }
     785
     786                     //
     787                     // The following logic used to be in a separate routine,
     788                     // but the need to flag compound selectors with no
     789                     // pseudoclass state that might impact the node's
     852                                         impactsChildren = true;
     853                                     }
     854                                 }
     855                             }
     856                             // Not a compound selector. If the selector might apply,
     857                             // then save off the pseudoclass state. StyleHelper
     858                             // checks this to see if a pseudoclass matters or not.
     859                         } else if (mightApply) {
     860                             SimpleSelector simple = (SimpleSelector) selector;
     861                             pseudoclassStateMask = pseudoclassStateMask
     862                                     | getPseudoclassMask(simple.getPseudoclasses());
     863                         }
     864                     }
     865                 }
     866             }
     867
     868             //
     869             // regardless of whether or not the cache is shared, a Cache
     870             // object is still needed in order to do the lookup.
     871             //
     872             final Key newKey = key.dup();

     873
     874             cache = new Cache(rules, pseudoclassStateMask, impactsChildren);
     875             cacheMap.put(newKey, cache);
     876

     877         }
     878
     879         // the key is an instance variable and so we need to null the
     880         // key.styleClass to prevent holding a hard reference to the
     881         // styleClass (and its Node)
     882         key.styleClass = null;
     883
     884         // Return the style helper looked up by the cache. The cache will
     885         // create a style helper if necessary (and possible), so we don't
     886         // have to worry about that part.
     887         StyleMap smap = cache.getStyleMap(this, node);
     888         if (smap == null) {
     889             return null;
     890         }
     891
     892         StyleHelper helper =
     893                 StyleHelper.create(
     894                 node,
     895                 smap.map,
     896                 styleCache,
     897                 cache.pseudoclassStateMask,
     898                 smap.uniqueId);
     899
     900         return helper;
     901     }
     902
     903     ////////////////////////////////////////////////////////////////////////////
     904     //
     905     // CssError reporting
     906     //
     907     ////////////////////////////////////////////////////////////////////////////
     908
     909     private static ObservableList<CssError> errors = null;
     910     /**
     911      * Errors that may have occurred during css processing.
     912      * This list is null until errorsProperty() is called.
     913      * @return
     914      */
     915     public static ObservableList<CssError> errorsProperty() {
     916         if (errors == null) {
     917             errors = FXCollections.observableArrayList();
     918         }
     919         return errors;
     938     ////////////////////////////////////////////////////////////////////////////
     939
     940     //
     941     // Used by StyleHelper. The key uniquely identifies this style map.
     942     // These keys are used by StyleHelper in creating its StyleCacheKey.
     943     // The StyleCacheKey is used to lookup calculated values in cache.
     944     //
     945     static class StyleMap {
     946         final long uniqueId; // unique per container
     947         final Map<String, List<CascadingStyle>> map;
     948         private StyleMap(long key, Map<String, List<CascadingStyle>> map) {
     949             this.uniqueId = key;
     950             this.map  = map;
     951         }
     952
     953     }
     954
     955     /**
     956      * Creates and caches maps of styles, reusing them as often as practical.
     957      */
     958     protected static class Cache {
     959
     960         // this must be initialized to the appropriate possible rules when
     961         // the helper cache is created by the StylesheetContainer
     962         private final List<Rule> rules;
     963         private final long pseudoclassStateMask;
     964         private final boolean impactsChildren;
     965         private final Map<Long, StyleMap> cache;
     966
     967         Cache(List<Rule> rules, long pseudoclassStateMask, boolean impactsChildren) {
     968             this.rules = rules;
     969             this.pseudoclassStateMask = pseudoclassStateMask;
     970             this.impactsChildren = impactsChildren;
     971             this.cache = new HashMap<Long, StyleMap>();
     972         }
     973
     974         private StyleMap getStyleMap(StyleManager owner, Node node) {
     975
     976             // If this set of rules (which may be empty) impacts children,
     977             // then this node will get a StyleHelper.
     978             if (!impactsChildren) {
    1081                                 // there may be more than one declaration for the
    1082                                 // same attribute within a rule or within a stylesheet
    1083                                 ordinal++
    1084                             );
    1085
    1086                         styles.add(s);
    1087                         }
    1088                     }
    1089                 }
    1090
    1091                 // return sorted styles
    1092             Collections.sort(styles);
    1093             return styles;
    1094             }
    1095
    1096         }
    1097
    1098     /**
    1099      * The key used in the cacheMap of the StylesheetContainer
    1100      */
    1101     static class Key {
    1102
    1103         public Key() {
    1104         }
    1105
    1106         protected Key dup() {
    1107
    1108             Key key = new Key();
    1109             key.className  = className;
    1110             key.id = id;
    1111             final int nElements = styleClass.size();
    1112             key.styleClass = new ArrayList<String>(nElements);
    1113             for (int n = 0; n < nElements; n++) {
    1114                 key.styleClass.add(styleClass.get(n));
    1115             }
    1116
    1117             return key;
    1118         }
    1119
    1120         // note that the class name here is the *full* class name, such as
    1121         // javafx.scene.control.Button. We only bother parsing this down to the
    1122         // last part when doing matching against selectors, and so want to avoid
    1123         // having to do a bunch of preliminary parsing in places where it isn't
    1124         // necessary.
    1125         protected String className;
    1126         protected String id;
    1127         protected List<String> styleClass;

    1128
    1129         @Override
    1130         public boolean equals(Object o) {
    1131             if (this == o) return true;
    1132             if (o instanceof Key) {
    1133                 Key other = (Key)o;
    1134                 boolean eq =
    1135                         className.equals(other.className)

    1136                     && (   (id == null && other.id == null)
    1137                         || (id != null && id.equals(other.id))
    1138                        )
    1139                     && (   (styleClass == null && other.styleClass == null)
    1140                         || (styleClass != null && styleClass.containsAll(other.styleClass))
    1141                        );

    1142                 return eq;
    1143             }
    1144             return false;
    1145         }
    1146
    1147         @Override
    1148         public int hashCode() {
    1149             int hash = className.hashCode();
    1150             hash = 31 * (hash + ((id == null || id.isEmpty()) ? 1231 : id.hashCode()));
    1151             hash = 31 * (hash + ((styleClass == null || styleClass.isEmpty()) ? 1237 : styleClass.hashCode()));

    1152             return hash;
    1153         }
    1154
    1155         @Override
    1156         public String toString() {
    1157             return "Key ["+className+", "+String.valueOf(id)+", "+String.valueOf(styleClass)+"]";
    1158         }
    1159
    1160     }
    1161
    1162 }