1 /*
   2  * Copyright (c) 2010, 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   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 javafx.css.Styleable;
  29 import java.io.FileNotFoundException;
  30 import java.io.FilePermission;
  31 import java.io.IOException;
  32 import java.io.InputStream;
  33 import java.lang.ref.Reference;
  34 import java.lang.ref.WeakReference;
  35 import java.net.MalformedURLException;
  36 import java.net.URI;
  37 import java.net.URISyntaxException;
  38 import java.net.URL;
  39 import java.security.AccessControlContext;
  40 import java.security.AccessController;
  41 import java.security.DigestInputStream;
  42 import java.security.MessageDigest;
  43 import java.security.NoSuchAlgorithmException;
  44 import java.security.PermissionCollection;
  45 import java.security.PrivilegedAction;
  46 import java.security.PrivilegedActionException;
  47 import java.security.PrivilegedExceptionAction;
  48 import java.security.ProtectionDomain;
  49 import java.util.*;
  50 import java.util.jar.JarEntry;
  51 import java.util.jar.JarFile;
  52 import javafx.collections.FXCollections;
  53 import javafx.collections.ListChangeListener.Change;
  54 import javafx.collections.ObservableList;
  55 import javafx.scene.Node;
  56 import javafx.scene.Parent;
  57 import javafx.scene.Scene;
  58 import javafx.scene.SubScene;
  59 import javafx.scene.text.Font;
  60 import javafx.stage.Window;
  61 import com.sun.javafx.css.parser.CSSParser;
  62 import java.util.Map.Entry;
  63 
  64 import javafx.css.CssMetaData;
  65 import javafx.css.PseudoClass;
  66 import javafx.css.StyleOrigin;
  67 import javafx.scene.image.Image;
  68 import sun.util.logging.PlatformLogger;
  69 import sun.util.logging.PlatformLogger.Level;
  70 
  71 /**
  72  * Contains the stylesheet state for a single scene. This includes both the
  73  * Stylesheets defined on the Scene itself as well as a map of stylesheets for
  74  * "style"s defined on the Node itself. These containers are kept in the
  75  * containerMap, key'd by the Scene to which they belong. <p> One of the key
  76  * responsibilities of the StylesheetContainer is to create and maintain an
  77  * admittedly elaborate series of caches so as to minimize the amount of time it
  78  * takes to match a Node to its eventual StyleHelper, and to reuse the
  79  * StyleHelper as much as possible. <p> Initially, the cache is empty. It is
  80  * recreated whenever the userStylesheets on the container change, or whenever
  81  * the userAgentStylesheet changes. The cache is built up as nodes are looked
  82  * for, and thus there is some overhead associated with the first lookup but
  83  * which is then not repeated for subsequent lookups. <p> The cache system used
  84  * is a two level cache. The first level cache simply maps the
  85  * classname/id/styleclass combination of the request node to a 2nd level cache.
  86  * If the node has "styles" specified then we still use this 2nd level cache,
  87  * but must combine its selectors with the selectors specified in "styles" and perform
  88  * more work to cascade properly. <p> The 2nd level cache contains a data
  89  * structure called the Cache. The Cache contains an ordered sequence of Rules,
  90  * a Long, and a Map. The ordered sequence of selectors are the selectors that *may*
  91  * match a node with the given classname, id, and style class. For example,
  92  * selectors which may apply are any selector where the simple selector of the selector
  93  * contains a reference to the id, style class, or classname of the Node, or a
  94  * compound selector who's "descendant" part is a simple selector which contains
  95  * a reference to the id, style class, or classname of the Node. <p> During
  96  * lookup, we will iterate over all the potential selectors and discover if they
  97  * apply to this particular node. If so, then we toggle a bit position in the
  98  * Long corresponding to the position of the selector that matched. This long then
  99  * becomes our key into the final map. <p> Once we have established our key, we
 100  * will visit the map and look for an existing StyleHelper. If we find a
 101  * StyleHelper, then we will return it. If not, then we will take the Rules that
 102  * matched and construct a new StyleHelper from their various parts. <p> This
 103  * system, while elaborate, also provides for numerous fast paths and sharing of
 104  * data structures which should dramatically reduce the memory and runtime
 105  * performance overhead associated with CSS by reducing the matching overhead
 106  * and caching as much as possible. We make no attempt to use weak references
 107  * here, so if memory issues result one work around would be to toggle the root
 108  * user agent stylesheet or stylesheets on the scene to cause the cache to be
 109  * flushed.
 110  */
 111 
 112 final public class StyleManager {
 113 
 114     private static PlatformLogger LOGGER;
 115     private static PlatformLogger getLogger() {
 116         if (LOGGER == null) {
 117             LOGGER = com.sun.javafx.Logging.getCSSLogger();
 118         }
 119         return LOGGER;
 120     }
 121 
 122     private static class InstanceHolder {
 123         final static StyleManager INSTANCE = new StyleManager();
 124     }
 125     /**
 126      * Return the StyleManager instance.
 127      */
 128     public static StyleManager getInstance() {
 129         return InstanceHolder.INSTANCE;
 130     }
 131 
 132     /**
 133      *
 134      * @param styleable
 135      * @return
 136      * @deprecated Use {@link javafx.css.Styleable#getCssMetaData()}
 137      */
 138     // TODO: is this used anywhere?
 139     @Deprecated public static List<CssMetaData<? extends Styleable, ?>> getStyleables(final Styleable styleable) {
 140 
 141         return styleable != null
 142             ? styleable.getCssMetaData()
 143             : Collections.<CssMetaData<? extends Styleable, ?>>emptyList();
 144     }
 145 
 146     /**
 147      *
 148      * @param node
 149      * @return
 150      * @deprecated Use {@link javafx.scene.Node#getCssMetaData()}
 151      */
 152     // TODO: is this used anywhere?
 153     @Deprecated public static List<CssMetaData<? extends Styleable, ?>> getStyleables(final Node node) {
 154 
 155         return node != null
 156             ? node.getCssMetaData()
 157             : Collections.<CssMetaData<? extends Styleable, ?>>emptyList();
 158     }
 159 
 160     private StyleManager() {
 161     }
 162 
 163     /**
 164      * A map from a parent to its style cache. The parent is either a Scene root, or a
 165      * Parent with author stylesheets. If a Scene or Parent is removed from the scene,
 166      * it's cache is annihilated.
 167      */
 168     // package for testing
 169     static final Map<Parent, CacheContainer> cacheContainerMap = new WeakHashMap<>();
 170 
 171     // package for testing
 172     CacheContainer getCacheContainer(Styleable styleable, SubScene subScene) {
 173 
 174         if (styleable == null && subScene == null) return null;
 175 
 176         Parent root = null;
 177 
 178         if (subScene != null) {
 179             root = subScene.getRoot();
 180 
 181         } else if (styleable instanceof Node) {
 182 
 183             Node node = (Node)styleable;
 184             Scene scene = node.getScene();
 185             if (scene != null) root = scene.getRoot();
 186 
 187         } else if (styleable instanceof Window) {
 188             // this catches the PopupWindow case
 189             Scene scene = ((Window)styleable).getScene();
 190             if (scene != null) root = scene.getRoot();
 191         }
 192         // todo: what other Styleables need to be handled here?
 193 
 194         if (root == null) return null;
 195 
 196         CacheContainer container = cacheContainerMap.computeIfAbsent(
 197                 root,
 198                 (key) -> {
 199                     return new CacheContainer();
 200                 }
 201         );
 202 
 203         return container;
 204 
 205     }
 206     /** 
 207      * StyleHelper uses this cache but it lives here so it can be cleared
 208      * when style-sheets change.
 209      */
 210     public StyleCache getSharedCache(Styleable styleable, SubScene subScene, StyleCache.Key key) {
 211 
 212         CacheContainer container = getCacheContainer(styleable, subScene);
 213         if (container == null) return null;
 214 
 215         Map<StyleCache.Key,StyleCache> styleCache = container.getStyleCache();
 216         if (styleCache == null) return null;
 217 
 218         StyleCache sharedCache = styleCache.get(key);
 219         if (sharedCache == null) {
 220             sharedCache = new StyleCache();
 221             styleCache.put(new StyleCache.Key(key), sharedCache);
 222         }
 223 
 224         return sharedCache;
 225     }
 226     
 227     public StyleMap getStyleMap(Styleable styleable, SubScene subScene, int smapId) {
 228 
 229         if (smapId == -1) return StyleMap.EMPTY_MAP;
 230         
 231         CacheContainer container = getCacheContainer(styleable, subScene);
 232         if (container == null) return StyleMap.EMPTY_MAP;
 233 
 234         return container.getStyleMap(smapId);
 235     }
 236 
 237     /**
 238      * A list of user-agent stylesheets from Scene or SubScene.
 239      * The order of the entries in this list does not matter since a Scene or
 240      * SubScene will only have zero or one user-agent stylesheets.
 241      */
 242     // package for testing
 243     final List<StylesheetContainer> userAgentStylesheetContainers = new ArrayList<>();
 244     /**
 245      * A list of user-agent stylesheet urls from calling setDefaultUserAgentStylesheet and
 246      * addUserAgentStylesheet. The order of entries this list matters. The zeroth element is
 247      * _the_ platform default.
 248      */
 249     // package for testing
 250     final List<StylesheetContainer> platformUserAgentStylesheetContainers = new ArrayList<>();
 251     // package for testing
 252     boolean hasDefaultUserAgentStylesheet = false;
 253 
 254     ////////////////////////////////////////////////////////////////////////////
 255     //
 256     // stylesheet handling
 257     //
 258     ////////////////////////////////////////////////////////////////////////////
 259 
 260     /*
 261      * A container for stylesheets and the Parents or Scenes that use them.
 262      * If a stylesheet is removed, then all other Parents or Scenes
 263      * that use that stylesheet should get new styles if the
 264      * stylesheet is added back in since the stylesheet may have been
 265      * removed and re-added because it was edited (typical of SceneBuilder).
 266      * This container provides the hooks to get back to those Parents or Scenes.
 267      *
 268      * StylesheetContainer<Parent> are created and added to stylesheetContainerMap
 269      * in the method gatherParentStylesheets.
 270      *
 271      * StylesheetContainer<Scene> are created and added to sceneStylesheetMap in
 272      * the method updateStylesheets
 273      */
 274     // package for testing
 275     static class StylesheetContainer {
 276 
 277         // the stylesheet uri
 278         final String fname;
 279         // the parsed stylesheet so we don't reparse for every parent that uses it
 280         final Stylesheet stylesheet;
 281         // the parents or scenes that use this stylesheet. Typically, this list
 282         //  should be very small.
 283         final SelectorPartitioning selectorPartitioning;
 284 
 285         // who uses this stylesheet?
 286         final RefList<Parent> parentUsers;
 287 
 288         // RT-24516 -- cache images coming from this stylesheet.
 289         // This just holds a hard reference to the image.
 290         final List<Image> imageCache;
 291 
 292         final int hash;
 293         final byte[] checksum;
 294         boolean checksumInvalid = false;
 295 
 296         StylesheetContainer(String fname, Stylesheet stylesheet) {
 297             this(fname, stylesheet, stylesheet != null ? calculateCheckSum(stylesheet.getUrl()) : new byte[0]);
 298         }
 299 
 300         StylesheetContainer(String fname, Stylesheet stylesheet, byte[] checksum) {
 301 
 302             this.fname = fname;
 303             hash = (fname != null) ? fname.hashCode() : 127;
 304 
 305             this.stylesheet = stylesheet;
 306             if (stylesheet != null) {
 307                 selectorPartitioning = new SelectorPartitioning();
 308                 final List<Rule> rules = stylesheet.getRules();
 309                 final int rMax = rules == null || rules.isEmpty() ? 0 : rules.size();
 310                 for (int r=0; r<rMax; r++) {
 311 
 312                     final Rule rule = rules.get(r);
 313                     final List<Selector> selectors = rule.getUnobservedSelectorList();
 314                     final int sMax = selectors == null || selectors.isEmpty() ? 0 : selectors.size();
 315                     for (int s=0; s < sMax; s++) {
 316 
 317                         final Selector selector = selectors.get(s);
 318                         selectorPartitioning.partition(selector);
 319 
 320                     }
 321                 }
 322 
 323             } else {
 324                 selectorPartitioning = null;
 325             }
 326 
 327             this.parentUsers = new RefList<Parent>();
 328 
 329             // this just holds a hard reference to the image
 330             this.imageCache = new ArrayList<Image>();
 331 
 332             this.checksum = checksum;
 333         }
 334 
 335         void invalidateChecksum() {
 336             // if checksum is byte[0], then it is forever valid.
 337             checksumInvalid = checksum.length > 0 ? true : false;
 338         }
 339         @Override
 340         public int hashCode() {
 341             return hash;
 342         }
 343 
 344         @Override
 345         public boolean equals(Object obj) {
 346             if (obj == null) {
 347                 return false;
 348             }
 349             if (getClass() != obj.getClass()) {
 350                 return false;
 351             }
 352             final StylesheetContainer other = (StylesheetContainer) obj;
 353             if ((this.fname == null) ? (other.fname != null) : !this.fname.equals(other.fname)) {
 354                 return false;
 355             }
 356             return true;
 357         }
 358 
 359         @Override public String toString() {
 360             return fname;
 361         }
 362 
 363     }
 364 
 365     /*
 366      * A list that holds references. Used by StylesheetContainer.
 367      */
 368     // package for testing
 369     static class RefList<K> {
 370 
 371         final List<Reference<K>> list = new ArrayList<Reference<K>>();
 372 
 373         void add(K key) {
 374 
 375             for (int n=list.size()-1; 0<=n; --n) {
 376                 final Reference<K> ref = list.get(n);
 377                 final K k = ref.get();
 378                 if (k == null) {
 379                     // stale reference, remove it.
 380                     list.remove(n);
 381                 } else {
 382                     // already have it, bail
 383                     if (k == key) {
 384                         return;
 385                     }
 386                 }
 387             }
 388             // not found, add it.
 389             list.add(new WeakReference<K>(key));
 390         }
 391 
 392         void remove(K key) {
 393 
 394             for (int n=list.size()-1; 0<=n; --n) {
 395                 final Reference<K> ref = list.get(n);
 396                 final K k = ref.get();
 397                 if (k == null) {
 398                     // stale reference, remove it.
 399                     list.remove(n);
 400                 } else {
 401                     // already have it, bail
 402                     if (k == key) {
 403                         list.remove(n);
 404                         return;
 405                     }
 406                 }
 407             }
 408         }
 409 
 410         // for unit testing
 411         boolean contains(K key) {
 412             for (int n=list.size()-1; 0<=n; --n) {
 413                 final Reference<K> ref = list.get(n);
 414                 final K k = ref.get();
 415                 if (k == key) {
 416                     return true;
 417                 }
 418             }
 419             return false;
 420         }
 421     }
 422 
 423     /**
 424      * A map from String => Stylesheet. If a stylesheet for the
 425      * given URL has already been loaded then we'll simply reuse the stylesheet
 426      * rather than loading a duplicate.
 427      * This list is for author stylesheets and not for user-agent stylesheets. User-agent
 428      * stylesheets are either platformUserAgentStylesheetContainers or userAgentStylesheetContainers
 429      */
 430     // package for unit testing
 431     final Map<String,StylesheetContainer> stylesheetContainerMap = new HashMap<>();
 432 
 433 
 434     /**
 435      * called from Window when the scene is closed.
 436      */
 437     public void forget(final Scene scene) {
 438 
 439         if (scene == null) return;
 440 
 441         forget(scene.getRoot());
 442 
 443         //
 444         // if this scene has user-agent stylesheets, clean up the userAgentStylesheetContainers list
 445         //
 446         String sceneUserAgentStylesheet = null;
 447         if ((scene.getUserAgentStylesheet() != null) &&
 448                 (!(sceneUserAgentStylesheet = scene.getUserAgentStylesheet().trim()).isEmpty())) {
 449 
 450             for(int n=0,nMax=userAgentStylesheetContainers.size(); n<nMax; n++) {
 451                 StylesheetContainer container = userAgentStylesheetContainers.get(n);
 452                 if (sceneUserAgentStylesheet.equals(container.fname)) {
 453                     container.parentUsers.remove(scene.getRoot());
 454                     if (container.parentUsers.list.size() == 0) {
 455                         userAgentStylesheetContainers.remove(n);
 456                     }
 457                 }
 458             }
 459         }
 460 
 461         //
 462         // remove any parents belonging to this scene from the stylesheetContainerMap
 463         //
 464         Set<Entry<String,StylesheetContainer>> stylesheetContainers = stylesheetContainerMap.entrySet();
 465         Iterator<Entry<String,StylesheetContainer>> iter = stylesheetContainers.iterator();
 466 
 467         while(iter.hasNext()) {
 468 
 469             Entry<String,StylesheetContainer> entry = iter.next();
 470             StylesheetContainer container = entry.getValue();
 471 
 472             Iterator<Reference<Parent>> parentIter = container.parentUsers.list.iterator();
 473             while (parentIter.hasNext()) {
 474 
 475                 Reference<Parent> ref = parentIter.next();
 476                 Parent _parent = ref.get();
 477 
 478                 if (_parent == null || _parent.getScene() == scene || _parent.getScene() == null) {
 479                     ref.clear();
 480                     parentIter.remove();
 481                 }
 482             }
 483 
 484             if (container.parentUsers.list.isEmpty()) {
 485                 iter.remove();
 486             }
 487         }
 488 
 489     }
 490 
 491     /**
 492      * called from Scene's stylesheets property's onChanged method
 493      */
 494     public void stylesheetsChanged(Scene scene, Change<String> c) {
 495 
 496         // Clear the cache so the cache will be rebuilt.
 497         Set<Entry<Parent,CacheContainer>> entrySet = cacheContainerMap.entrySet();
 498         for(Entry<Parent,CacheContainer> entry : entrySet) {
 499             Parent parent = entry.getKey();
 500             CacheContainer container = entry.getValue();
 501             if (parent.getScene() == scene) {
 502                 container.clearCache();
 503             }
 504 
 505         }
 506 
 507         c.reset();
 508         while(c.next()) {
 509             if (c.wasRemoved()) {
 510                 for (String fname : c.getRemoved()) {
 511                     stylesheetRemoved(scene, fname);
 512 
 513                     StylesheetContainer stylesheetContainer = stylesheetContainerMap.get(fname);
 514                     if (stylesheetContainer != null) {
 515                         stylesheetContainer.invalidateChecksum();
 516                     }
 517 
 518                 }
 519             }
 520         }
 521 
 522     }
 523 
 524     private void stylesheetRemoved(Scene scene, String fname) {
 525         stylesheetRemoved(scene.getRoot(), fname);
 526     }
 527 
 528     /**
 529      * Called from Parent's scenesChanged method when the Parent's scene is set to null.
 530      * @param parent The Parent being removed from the scene-graph
 531      */
 532     public void forget(Parent parent) {
 533 
 534         if (parent == null) return;
 535 
 536         // RT-34863 - clean up CSS cache when Parent is removed from scene-graph
 537         Set<Entry<Parent, CacheContainer>> entrySet = cacheContainerMap.entrySet();
 538         Iterator<Entry<Parent, CacheContainer>> iterator = entrySet.iterator();
 539         while (iterator.hasNext()) {
 540             Entry<Parent, CacheContainer> entry = iterator.next();
 541             Parent key = entry.getKey();
 542             CacheContainer container = entry.getValue();
 543             if (parent == key) {
 544                 iterator.remove();
 545                 container.clearCache();
 546             }
 547         }
 548 
 549         final List<String> stylesheets = parent.getStylesheets();
 550         if (stylesheets != null && !stylesheets.isEmpty()) {
 551             for (String fname : stylesheets) {
 552                 stylesheetRemoved(parent, fname);
 553             }
 554         }
 555 
 556         Iterator<StylesheetContainer> containerIterator = stylesheetContainerMap.values().iterator();
 557         while (containerIterator.hasNext()) {
 558             StylesheetContainer container = containerIterator.next();
 559             container.parentUsers.remove(parent);
 560             if (container.parentUsers.list.isEmpty()) {
 561 
 562                 containerIterator.remove();
 563 
 564                 if (container.selectorPartitioning != null) {
 565                     container.selectorPartitioning.reset();
 566                 }
 567 
 568 
 569                 // clean up image cache by removing images from the cache that
 570                 // might have come from this stylesheet
 571                 final String fname = container.fname;
 572                 cleanUpImageCache(fname);
 573             }
 574         }
 575 
 576         // Do not iterate over children since this method will be called on each from Parent#scenesChanged
 577     }
 578 
 579     /**
 580      * called from Parent's stylesheets property's onChanged method
 581      */
 582     public void stylesheetsChanged(Parent parent, Change<String> c) {
 583         c.reset();
 584         while(c.next()) {
 585             if (c.wasRemoved()) {
 586                 for (String fname : c.getRemoved()) {
 587                     stylesheetRemoved(parent, fname);
 588 
 589                     StylesheetContainer stylesheetContainer = stylesheetContainerMap.get(fname);
 590                     if (stylesheetContainer != null) {
 591                         stylesheetContainer.invalidateChecksum();
 592                     }
 593                 }
 594             }
 595         }
 596     }
 597 
 598     private void stylesheetRemoved(Parent parent, String fname) {
 599 
 600         StylesheetContainer stylesheetContainer = stylesheetContainerMap.get(fname);
 601 
 602         if (stylesheetContainer == null) return;
 603 
 604         stylesheetContainer.parentUsers.remove(parent);
 605 
 606         if (stylesheetContainer.parentUsers.list.isEmpty()) {
 607             removeStylesheetContainer(stylesheetContainer);
 608         }
 609     }
 610 
 611     /**
 612      * called from Window when the scene is closed.
 613      */
 614     public void forget(final SubScene subScene) {
 615 
 616         if (subScene == null) return;
 617         final Parent subSceneRoot = subScene.getRoot();
 618 
 619         if (subSceneRoot == null) return;
 620         forget(subSceneRoot);
 621 
 622         //
 623         // if this scene has user-agent stylesheets, clean up the userAgentStylesheetContainers list
 624         //
 625         String sceneUserAgentStylesheet = null;
 626         if ((subScene.getUserAgentStylesheet() != null) &&
 627                 (!(sceneUserAgentStylesheet = subScene.getUserAgentStylesheet().trim()).isEmpty())) {
 628 
 629             for(int n=0,nMax=userAgentStylesheetContainers.size(); n<nMax; n++) {
 630                 StylesheetContainer container = userAgentStylesheetContainers.get(n);
 631                 if (sceneUserAgentStylesheet.equals(container.fname)) {
 632                     container.parentUsers.remove(subScene.getRoot());
 633                     if (container.parentUsers.list.size() == 0) {
 634                         userAgentStylesheetContainers.remove(n);
 635                     }
 636                 }
 637             }
 638         }
 639 
 640         //
 641         // remove any parents belonging to this SubScene from the stylesheetContainerMap
 642         //
 643         Set<Entry<String,StylesheetContainer>> stylesheetContainers = stylesheetContainerMap.entrySet();
 644         Iterator<Entry<String,StylesheetContainer>> iter = stylesheetContainers.iterator();
 645 
 646         while(iter.hasNext()) {
 647 
 648             Entry<String,StylesheetContainer> entry = iter.next();
 649             StylesheetContainer container = entry.getValue();
 650 
 651             Iterator<Reference<Parent>> parentIter = container.parentUsers.list.iterator();
 652             while (parentIter.hasNext()) {
 653 
 654                 final Reference<Parent> ref = parentIter.next();
 655                 final Parent _parent = ref.get();
 656 
 657                 if (_parent != null) {
 658                     // if this stylesheet refererent is a child of this subscene, nuke it.
 659                     Parent p = _parent;
 660                     while (p != null) {
 661                         if (subSceneRoot == p.getParent()) {
 662                             ref.clear();
 663                             parentIter.remove();
 664                             forget(_parent); // _parent, not p!
 665                             break;
 666                         }
 667                         p = p.getParent();
 668                     }
 669                 }
 670             }
 671 
 672             // forget(_parent) will remove the container if the parentUser's list is empty
 673             // if (container.parentUsers.list.isEmpty()) {
 674             //    iter.remove();
 675             // }
 676         }
 677 
 678     }
 679 
 680     private void removeStylesheetContainer(StylesheetContainer stylesheetContainer) {
 681 
 682         if (stylesheetContainer == null) return;
 683 
 684         final String fname = stylesheetContainer.fname;
 685 
 686         stylesheetContainerMap.remove(fname);
 687 
 688         if (stylesheetContainer.selectorPartitioning != null) {
 689             stylesheetContainer.selectorPartitioning.reset();
 690         }
 691 
 692         // if container has no references, then remove it
 693         for(Entry<Parent,CacheContainer> entry : cacheContainerMap.entrySet()) {
 694 
 695             CacheContainer container = entry.getValue();
 696             List<List<String>> entriesToRemove = new ArrayList<>();
 697 
 698             for (Entry<List<String>, Map<Key,Cache>> cacheMapEntry : container.cacheMap.entrySet()) {
 699                 List<String> cacheMapKey = cacheMapEntry.getKey();
 700                 if (cacheMapKey != null ? cacheMapKey.contains(fname) : fname == null) {
 701                     entriesToRemove.add(cacheMapKey);
 702                 }
 703             }
 704 
 705             if (!entriesToRemove.isEmpty()) {
 706                 for (List<String> cacheMapKey : entriesToRemove) {
 707                     Map<Key,Cache> cacheEntry = container.cacheMap.remove(cacheMapKey);
 708                     if (cacheEntry != null) {
 709                         cacheEntry.clear();
 710                     }
 711                 }
 712 
 713                 if (container.cacheMap.isEmpty()) {
 714                     // TODO:
 715                     System.out.println("container.cacheMap.isEmpty");
 716                 }
 717             }
 718         }
 719 
 720         // clean up image cache by removing images from the cache that
 721         // might have come from this stylesheet
 722         cleanUpImageCache(fname);
 723 
 724         final List<Reference<Parent>> parentList = stylesheetContainer.parentUsers.list;
 725 
 726         for (int n=parentList.size()-1; 0<=n; --n) {
 727 
 728             final Reference<Parent> ref = parentList.remove(n);
 729             final Parent parent = ref.get();
 730             ref.clear();
 731             if (parent == null || parent.getScene() == null) {
 732                 continue;
 733             }
 734 
 735             //
 736             // tell parent it needs to reapply css
 737             // No harm is done if parent is in a scene that has had
 738             // impl_reapplyCSS called on the root.
 739             //
 740             parent.impl_reapplyCSS();
 741         }
 742 
 743     }
 744 
 745     ////////////////////////////////////////////////////////////////////////////
 746     //
 747     // Image caching
 748     //
 749     ////////////////////////////////////////////////////////////////////////////
 750 
 751     Map<String,Image> imageCache = new HashMap<String,Image>();
 752 
 753     public Image getCachedImage(String url) {
 754 
 755         Image image = null;
 756         if (imageCache.containsKey(url)) {
 757 
 758             image = imageCache.get(url);
 759 
 760         } else {
 761 
 762             try {
 763 
 764                 image = new Image(url);
 765 
 766                 // RT-31865
 767                 if (image.isError()) {
 768 
 769                     final PlatformLogger logger = getLogger();
 770                     if (logger != null && logger.isLoggable(Level.WARNING)) {
 771                         logger.warning("Error loading image: " + url);
 772                     }
 773 
 774                     image = null;
 775                 }
 776 
 777                 imageCache.put(url, image);
 778 
 779             } catch (IllegalArgumentException iae) {
 780                 // url was empty!
 781                 final PlatformLogger logger = getLogger();
 782                 if (logger != null && logger.isLoggable(Level.WARNING)) {
 783                     logger.warning(iae.getLocalizedMessage());
 784                 }
 785 
 786             } catch (NullPointerException npe) {
 787                 // url was null!
 788                 final PlatformLogger logger = getLogger();
 789                 if (logger != null && logger.isLoggable(Level.WARNING)) {
 790                     logger.warning(npe.getLocalizedMessage());
 791                 }
 792             }
 793         }
 794 
 795         return image;
 796     }
 797 
 798     private void cleanUpImageCache(String fname) {
 799 
 800         if (fname == null && imageCache.isEmpty()) return;
 801         if (fname.trim().isEmpty()) return;
 802 
 803         int len = fname.lastIndexOf('/');
 804         final String path = (len > 0) ? fname.substring(0,len) : fname;
 805         final int plen = path.length();
 806 
 807         final String[] entriesToRemove = new String[imageCache.size()];
 808         int count = 0;
 809 
 810         final Set<Entry<String, Image>> entrySet = imageCache.entrySet();
 811         for(Entry<String, Image> entry : entrySet) {
 812 
 813             final String key = entry.getKey();
 814             len = key.lastIndexOf('/');
 815             final String kpath = (len > 0) ? key.substring(0, len) : key;
 816             final int klen = kpath.length();
 817 
 818             // if the longer path begins with the shorter path,
 819             // then assume the image came from this path.
 820             boolean match = (klen > plen) ? kpath.startsWith(path) : path.startsWith(kpath);
 821             if (match) entriesToRemove[count++] = key;
 822         }
 823 
 824         for (int n=0; n<count; n++) {
 825            Image img = imageCache.remove(entriesToRemove[n]);
 826        }
 827     }
 828 
 829     ////////////////////////////////////////////////////////////////////////////
 830     //
 831     // Stylesheet loading
 832     //
 833     ////////////////////////////////////////////////////////////////////////////
 834 
 835     private static URL getURL(final String str) {
 836 
 837         // Note: this code is duplicated, more or less, in URLConverter
 838 
 839         if (str == null || str.trim().isEmpty()) return null;
 840 
 841         try {
 842 
 843             URI uri =  new URI(str.trim());
 844 
 845             // if url doesn't have a scheme
 846             if (uri.isAbsolute() == false) {
 847 
 848                 final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
 849                 final String path = uri.getPath();
 850 
 851                 URL resource = null;
 852 
 853                 if (path.startsWith("/")) {
 854                     resource = contextClassLoader.getResource(path.substring(1));
 855                 } else {
 856                     resource = contextClassLoader.getResource(path);
 857                 }
 858 
 859                 return resource;
 860             }
 861 
 862             // else, url does have a scheme
 863             return uri.toURL();
 864 
 865         } catch (MalformedURLException malf) {
 866             // Do not log exception here - caller will handle null return.
 867             // For example, we might be looking for a .bss that doesn't exist
 868             return null;
 869         } catch (URISyntaxException urise) {
 870             return null;
 871         }
 872     }
 873 
 874     // Calculate checksum for stylesheet file. Return byte[0] if checksum could not be calculated.
 875     static byte[] calculateCheckSum(String fname) {
 876 
 877         if (fname == null || fname.isEmpty()) return new byte[0];
 878 
 879         try {
 880             URL url = getURL(fname);
 881 
 882             // We only care about stylesheets from file: URLs.
 883             if (url != null && "file".equals(url.getProtocol())) {
 884 
 885                 try (InputStream stream = url.openStream()) {
 886 
 887                     // not looking for security, just a checksum. MD5 should be faster than SHA
 888                     final DigestInputStream dis = new DigestInputStream(stream, MessageDigest.getInstance("MD5"));
 889                     while (dis.read() != -1) { /* empty loop body is intentional */ }
 890                     return dis.getMessageDigest().digest();
 891                 }
 892 
 893             }
 894 
 895         } catch (IllegalArgumentException | NoSuchAlgorithmException | IOException | SecurityException e) {
 896             // IOException also covers MalformedURLException
 897             // SecurityException means some untrusted applet
 898 
 899             // Fall through...
 900         }
 901         return new byte[0];
 902     }
 903 
 904     private static Stylesheet loadStylesheet(final String fname) {
 905         try {
 906             return loadStylesheetUnPrivileged(fname);
 907         } catch (java.security.AccessControlException ace) {
 908             if (getLogger().isLoggable(Level.INFO)) {
 909                 getLogger().info("Could not load the stylesheet, trying with FilePermissions : " + fname);
 910             }
 911 
 912             /*
 913             ** we got an access control exception, so
 914             ** we could be running from an applet/jnlp/or with a security manager.
 915             ** we'll allow the app to read a css file from our runtime jar,
 916             ** and give it one more chance.
 917             */
 918 
 919             /*
 920             ** check that there are enough chars after the !/ to have a valid .css or .bss file name
 921             */
 922             if ((fname.length() < 7) && (fname.indexOf("!/") < fname.length()-7)) {
 923                 return null;
 924             }
 925 
 926             /*
 927             **
 928             ** first check that it's actually looking for the same runtime jar
 929             ** that we're running from, and not some other file.
 930             */
 931             try {
 932                 URI requestedFileUrI = new URI(fname);
 933 
 934                 /*
 935                 ** is the requested file in a jar
 936                 */
 937                 if ("jar".equals(requestedFileUrI.getScheme())) {
 938                     /*
 939                     ** let's check that the css file is being requested from our
 940                     ** runtime jar
 941                     */
 942                     URI styleManagerJarURI = AccessController.doPrivileged((PrivilegedExceptionAction<URI>) () -> StyleManager.class.getProtectionDomain().getCodeSource().getLocation().toURI());
 943 
 944                     final String styleManagerJarPath = styleManagerJarURI.getSchemeSpecificPart();
 945                     String requestedFilePath = requestedFileUrI.getSchemeSpecificPart();
 946                     String requestedFileJarPart = requestedFilePath.substring(requestedFilePath.indexOf('/'), requestedFilePath.indexOf("!/"));
 947                     /*
 948                     ** it's the correct jar, check it's a file access
 949                     ** strip off the leading jar
 950                     */
 951                     if (styleManagerJarPath.equals(requestedFileJarPart)) {
 952                         /*
 953                         ** strip off the leading "jar",
 954                         ** the css file name is past the last '!'
 955                         */
 956                         String requestedFileJarPathNoLeadingSlash = fname.substring(fname.indexOf("!/")+2);
 957                         /*
 958                         ** check that it's looking for a css file in the runtime jar
 959                         */
 960                         if (fname.endsWith(".css") || fname.endsWith(".bss")) {
 961                             /*
 962                             ** set up a read permission for the jar
 963                             */
 964                             FilePermission perm = new FilePermission(styleManagerJarPath, "read");
 965 
 966                             PermissionCollection perms = perm.newPermissionCollection();
 967                             perms.add(perm);
 968                             AccessControlContext permsAcc = new AccessControlContext(
 969                                 new ProtectionDomain[] {
 970                                     new ProtectionDomain(null, perms)
 971                                 });
 972                             /*
 973                             ** check that the jar file exists, and that we're allowed to
 974                             ** read it.
 975                             */
 976                             JarFile jar = null;
 977                             try {
 978                                 jar = AccessController.doPrivileged((PrivilegedExceptionAction<JarFile>) () -> new JarFile(styleManagerJarPath), permsAcc);
 979                             } catch (PrivilegedActionException pae) {
 980                                 /*
 981                                 ** we got either a FileNotFoundException or an IOException
 982                                 ** in the privileged read. Return the same error as we
 983                                 ** would have returned if the css file hadn't of existed.
 984                                 */
 985                                 return null;
 986                             }
 987                             if (jar != null) {
 988                                 /*
 989                                 ** check that the file is in the jar
 990                                 */
 991                                 JarEntry entry = jar.getJarEntry(requestedFileJarPathNoLeadingSlash);
 992                                 if (entry != null) {
 993                                     /*
 994                                     ** allow read access to the jar
 995                                     */
 996                                     return AccessController.doPrivileged(
 997                                             (PrivilegedAction<Stylesheet>) () -> loadStylesheetUnPrivileged(fname), permsAcc);
 998                                 }
 999                             }
1000                         }
1001                     }
1002                 }
1003                 /*
1004                 ** no matter what happen, we return the same error that would
1005                 ** be returned if the css file hadn't of existed.
1006                 ** That way there in no information leaked.
1007                 */
1008                 return null;
1009             }
1010             /*
1011             ** no matter what happen, we return the same error that would
1012             ** be returned if the css file hadn't of existed.
1013             ** That way there in no information leaked.
1014             */
1015             catch (java.net.URISyntaxException e) {
1016                 return null;
1017             }
1018             catch (java.security.PrivilegedActionException e) {
1019                 return null;
1020             }
1021        }
1022     }
1023 
1024 
1025     private static Stylesheet loadStylesheetUnPrivileged(final String fname) {
1026 
1027         Boolean parse = AccessController.doPrivileged((PrivilegedAction<Boolean>) () -> {
1028 
1029             final String bss = System.getProperty("binary.css");
1030             // binary.css is true by default.
1031             // parse only if the file is not a .bss
1032             // and binary.css is set to false
1033             return (!fname.endsWith(".bss") && bss != null) ?
1034                 !Boolean.valueOf(bss) : Boolean.FALSE;
1035         });
1036 
1037         try {
1038             final String ext = (parse) ? (".css") : (".bss");
1039             java.net.URL url = null;
1040             Stylesheet stylesheet = null;
1041             // check if url has extension, if not then just url as is and always parse as css text
1042             if (!(fname.endsWith(".css") || fname.endsWith(".bss"))) {
1043                 url = getURL(fname);
1044                 parse = true;
1045             } else {
1046                 final String name = fname.substring(0, fname.length() - 4);
1047 
1048                 url = getURL(name+ext);
1049                 if (url == null && (parse = !parse)) {
1050                     // If we failed to get the URL for the .bss file,
1051                     // fall back to the .css file.
1052                     // Note that 'parse' is toggled in the test.
1053                     url = getURL(name+".css");
1054                 }
1055 
1056                 if ((url != null) && !parse) {
1057 
1058                     try {
1059                         // RT-36332: if loadBinary throws an IOException, make sure to try .css
1060                         stylesheet = Stylesheet.loadBinary(url);
1061                     } catch (IOException ioe) {
1062                         stylesheet = null;
1063                     }
1064 
1065                     if (stylesheet == null && (parse = !parse)) {
1066                         // If we failed to load the .bss file,
1067                         // fall back to the .css file.
1068                         // Note that 'parse' is toggled in the test.
1069                         url = getURL(fname);
1070                     }
1071                 }
1072             }
1073 
1074             // either we failed to load the .bss file, or parse
1075             // was set to true.
1076             if ((url != null) && parse) {
1077                 stylesheet = CSSParser.getInstance().parse(url);
1078             }
1079 
1080             if (stylesheet == null) {
1081                 if (errors != null) {
1082                     CssError error =
1083                         new CssError(
1084                             "Resource \""+fname+"\" not found."
1085                         );
1086                     errors.add(error);
1087                 }
1088                 if (getLogger().isLoggable(Level.WARNING)) {
1089                     getLogger().warning(
1090                         String.format("Resource \"%s\" not found.", fname)
1091                     );
1092                 }
1093             }
1094 
1095             // load any fonts from @font-face
1096             if (stylesheet != null) {
1097                 faceLoop: for(FontFace fontFace: stylesheet.getFontFaces()) {
1098                     for(FontFace.FontFaceSrc src: fontFace.getSources()) {
1099                         if (src.getType() == FontFace.FontFaceSrcType.URL) {
1100                             Font loadedFont = Font.loadFont(src.getSrc(),10);
1101                             if (loadedFont == null) {
1102                                 getLogger().info("Could not load @font-face font [" + src.getSrc() + "]");
1103                             }
1104                             continue faceLoop;
1105                         }
1106                     }
1107                 }
1108             }
1109 
1110             return stylesheet;
1111 
1112         } catch (FileNotFoundException fnfe) {
1113             if (errors != null) {
1114                 CssError error =
1115                     new CssError(
1116                         "Stylesheet \""+fname+"\" not found."
1117                     );
1118                 errors.add(error);
1119             }
1120             if (getLogger().isLoggable(Level.INFO)) {
1121                 getLogger().info("Could not find stylesheet: " + fname);//, fnfe);
1122             }
1123         } catch (IOException ioe) {
1124                 if (errors != null) {
1125                     CssError error =
1126                         new CssError(
1127                             "Could not load stylesheet: " + fname
1128                         );
1129                     errors.add(error);
1130                 }
1131             if (getLogger().isLoggable(Level.INFO)) {
1132                 getLogger().info("Could not load stylesheet: " + fname);//, ioe);
1133             }
1134         }
1135         return null;
1136     }
1137 
1138     ////////////////////////////////////////////////////////////////////////////
1139     //
1140     // User Agent stylesheet handling
1141     //
1142     ////////////////////////////////////////////////////////////////////////////
1143 
1144     /**
1145      * Add a user agent stylesheet, possibly overriding styles in the default
1146      * user agent stylesheet.
1147      *
1148      * @param fname The file URL, either relative or absolute, as a String.
1149      */
1150     public void addUserAgentStylesheet(String fname) {
1151         addUserAgentStylesheet(null, fname);
1152     }
1153 
1154     /**
1155      * Add a user agent stylesheet, possibly overriding styles in the default
1156      * user agent stylesheet.
1157      * @param scene Only used in CssError for tracking back to the scene that loaded the stylesheet
1158      * @param url  The file URL, either relative or absolute, as a String.
1159      */
1160     // For RT-20643
1161     public void addUserAgentStylesheet(Scene scene, String url) {
1162 
1163         if (url == null ) {
1164             throw new IllegalArgumentException("null arg url");
1165         }
1166 
1167         final String fname = url.trim();
1168         if (fname.isEmpty()) {
1169             return;
1170         }
1171 
1172         // if we already have this stylesheet, bail
1173         for (int n=0, nMax= platformUserAgentStylesheetContainers.size(); n < nMax; n++) {
1174             StylesheetContainer container = platformUserAgentStylesheetContainers.get(n);
1175             if (fname.equals(container.fname)) {
1176                 return;
1177             }
1178         }
1179 
1180         // RT-20643
1181         CssError.setCurrentScene(scene);
1182 
1183         final Stylesheet ua_stylesheet = loadStylesheet(fname);
1184         platformUserAgentStylesheetContainers.add(new StylesheetContainer(fname, ua_stylesheet));
1185 
1186         if (ua_stylesheet != null) {
1187             ua_stylesheet.setOrigin(StyleOrigin.USER_AGENT);
1188         }
1189         userAgentStylesheetsChanged();
1190 
1191         // RT-20643
1192         CssError.setCurrentScene(null);
1193 
1194     }
1195 
1196     /**
1197      * Add a user agent stylesheet, possibly overriding styles in the default
1198      * user agent stylesheet.
1199      * @param scene Only used in CssError for tracking back to the scene that loaded the stylesheet
1200      * @param ua_stylesheet  The stylesheet to add as a user-agent stylesheet
1201      */
1202     public void addUserAgentStylesheet(Scene scene, Stylesheet ua_stylesheet) {
1203 
1204         if (ua_stylesheet == null ) {
1205             throw new IllegalArgumentException("null arg ua_stylesheet");
1206         }
1207 
1208         // null url is ok, just means that it is a stylesheet not loaded from a file
1209         String url = ua_stylesheet.getUrl();
1210         final String fname = url != null ? url.trim() : "";
1211 
1212         // if we already have this stylesheet, bail
1213         for (int n=0, nMax= platformUserAgentStylesheetContainers.size(); n < nMax; n++) {
1214             StylesheetContainer container = platformUserAgentStylesheetContainers.get(n);
1215             if (fname.equals(container.fname)) {
1216                 return;
1217             }
1218         }
1219 
1220         // RT-20643
1221         CssError.setCurrentScene(scene);
1222 
1223         platformUserAgentStylesheetContainers.add(new StylesheetContainer(fname, ua_stylesheet));
1224 
1225         if (ua_stylesheet != null) {
1226             ua_stylesheet.setOrigin(StyleOrigin.USER_AGENT);
1227         }
1228         userAgentStylesheetsChanged();
1229 
1230         // RT-20643
1231         CssError.setCurrentScene(null);
1232 
1233     }
1234 
1235     /**
1236      * Set the default user agent stylesheet.
1237      *
1238      * @param fname The file URL, either relative or absolute, as a String.
1239      */
1240     public void setDefaultUserAgentStylesheet(String fname) {
1241         setDefaultUserAgentStylesheet(null, fname);
1242     }
1243 
1244     /**
1245      * Set the default user agent stylesheet
1246      * @param scene Only used in CssError for tracking back to the scene that loaded the stylesheet
1247      * @param url  The file URL, either relative or absolute, as a String.
1248      */
1249     // For RT-20643
1250     public void setDefaultUserAgentStylesheet(Scene scene, String url) {
1251 
1252         final String fname = (url != null) ? url.trim() : null;
1253         if (fname == null || fname.isEmpty()) {
1254             throw new IllegalArgumentException("null arg url");
1255         }
1256 
1257         // if we already have this stylesheet, make sure it is the first element
1258         for (int n=0, nMax= platformUserAgentStylesheetContainers.size(); n < nMax; n++) {
1259             StylesheetContainer container = platformUserAgentStylesheetContainers.get(n);
1260             if (fname.equals(container.fname)) {
1261                 if (n > 0) {
1262                     platformUserAgentStylesheetContainers.remove(n);
1263                     if (hasDefaultUserAgentStylesheet) {
1264                         platformUserAgentStylesheetContainers.set(0, container);
1265                     } else {
1266                         platformUserAgentStylesheetContainers.add(0, container);
1267                     }
1268                 }
1269                 return;
1270             }
1271         }
1272 
1273         // RT-20643
1274         CssError.setCurrentScene(scene);
1275 
1276         final Stylesheet ua_stylesheet = loadStylesheet(fname);
1277         final StylesheetContainer sc = new StylesheetContainer(fname, ua_stylesheet);
1278         if (platformUserAgentStylesheetContainers.size() == 0) {
1279             platformUserAgentStylesheetContainers.add(sc);
1280         } else if (hasDefaultUserAgentStylesheet) {
1281             platformUserAgentStylesheetContainers.set(0,sc);
1282         } else {
1283             platformUserAgentStylesheetContainers.add(0,sc);
1284         }
1285         hasDefaultUserAgentStylesheet = true;
1286 
1287         if (ua_stylesheet != null) {
1288             ua_stylesheet.setOrigin(StyleOrigin.USER_AGENT);
1289         }
1290         userAgentStylesheetsChanged();
1291 
1292         // RT-20643
1293         CssError.setCurrentScene(null);
1294 
1295     }
1296 
1297     /**
1298      * Set the user agent stylesheet. This is the base default stylesheet for
1299      * the platform
1300      */
1301     public void setDefaultUserAgentStylesheet(Stylesheet ua_stylesheet) {
1302 
1303         if (ua_stylesheet == null ) {
1304             throw new IllegalArgumentException("null arg ua_stylesheet");
1305         }
1306 
1307         // null url is ok, just means that it is a stylesheet not loaded from a file
1308         String url = ua_stylesheet.getUrl();
1309         final String fname = url != null ? url.trim() : "";
1310 
1311         // if we already have this stylesheet, make sure it is the first element
1312         for (int n=0, nMax= platformUserAgentStylesheetContainers.size(); n < nMax; n++) {
1313             StylesheetContainer container = platformUserAgentStylesheetContainers.get(n);
1314             if (fname.equals(container.fname)) {
1315                 if (n > 0) {
1316                     platformUserAgentStylesheetContainers.remove(n);
1317                     if (hasDefaultUserAgentStylesheet) {
1318                         platformUserAgentStylesheetContainers.set(0, container);
1319                     } else {
1320                         platformUserAgentStylesheetContainers.add(0, container);
1321                     }
1322                 }
1323                 return;
1324             }
1325         }
1326 
1327         StylesheetContainer sc = new StylesheetContainer(fname, ua_stylesheet);
1328         if (platformUserAgentStylesheetContainers.size() == 0) {
1329             platformUserAgentStylesheetContainers.add(sc);
1330         } else if (hasDefaultUserAgentStylesheet) {
1331             platformUserAgentStylesheetContainers.set(0,sc);
1332         } else {
1333             platformUserAgentStylesheetContainers.add(0,sc);
1334         }
1335         hasDefaultUserAgentStylesheet = true;
1336 
1337         ua_stylesheet.setOrigin(StyleOrigin.USER_AGENT);
1338         userAgentStylesheetsChanged();
1339 
1340         // RT-20643
1341         CssError.setCurrentScene(null);
1342     }
1343 
1344     /*
1345      * If the userAgentStylesheets change, then all scenes are updated.
1346      */
1347     private void userAgentStylesheetsChanged() {
1348 
1349         for (CacheContainer container : cacheContainerMap.values()) {
1350             container.clearCache();
1351         }
1352 
1353         StyleConverterImpl.clearCache();
1354 
1355         for (Parent root : cacheContainerMap.keySet()) {
1356             if (root == null) {
1357                 continue;
1358             }
1359             root.impl_reapplyCSS();
1360         }
1361 
1362     }
1363 
1364     private List<StylesheetContainer> processStylesheets(List<String> stylesheets, Parent parent) {
1365 
1366         final List<StylesheetContainer> list = new ArrayList<StylesheetContainer>();
1367         for (int n = 0, nMax = stylesheets.size(); n < nMax; n++) {
1368             final String fname = stylesheets.get(n);
1369 
1370             StylesheetContainer container = null;
1371             if (stylesheetContainerMap.containsKey(fname)) {
1372                 container = stylesheetContainerMap.get(fname);
1373 
1374                 if (!list.contains(container)) {
1375                     // minor optimization: if existing checksum in byte[0], then don't bother recalculating
1376                     if (container.checksumInvalid) {
1377                         final byte[] checksum = calculateCheckSum(fname);
1378                         if (!Arrays.equals(checksum, container.checksum)) {
1379                             removeStylesheetContainer(container);
1380 
1381                             // Stylesheet did change. Re-load the stylesheet and update the container map.
1382                             Stylesheet stylesheet = loadStylesheet(fname);
1383                             container = new StylesheetContainer(fname, stylesheet, checksum);
1384                             stylesheetContainerMap.put(fname, container);
1385                         } else {
1386                             container.checksumInvalid = false;
1387                         }
1388                     }
1389                     list.add(container);
1390                 }
1391 
1392                 // RT-22565: remember that this parent or scene uses this stylesheet.
1393                 // Later, if the cache is cleared, the parent or scene is told to
1394                 // reapply css.
1395                 container.parentUsers.add(parent);
1396 
1397             } else {
1398                 final Stylesheet stylesheet = loadStylesheet(fname);
1399                 // stylesheet may be null which would mean that some IOException
1400                 // was thrown while trying to load it. Add it to the
1401                 // stylesheetContainerMap anyway as this will prevent further
1402                 // attempts to parse the file
1403                 container = new StylesheetContainer(fname, stylesheet);
1404                 // RT-22565: remember that this parent or scene uses this stylesheet.
1405                 // Later, if the cache is cleared, the parent or scene is told to
1406                 // reapply css.
1407                 container.parentUsers.add(parent);
1408                 stylesheetContainerMap.put(fname, container);
1409 
1410                 list.add(container);
1411             }
1412         }
1413         return list;
1414     }
1415 
1416     //
1417     // recurse so that stylesheets of Parents closest to the root are
1418     // added to the list first. The ensures that declarations for
1419     // stylesheets further down the tree (closer to the leaf) have
1420     // a higher ordinal in the cascade.
1421     //
1422     private List<StylesheetContainer> gatherParentStylesheets(final Parent parent) {
1423 
1424         if (parent == null) {
1425             return Collections.<StylesheetContainer>emptyList();
1426         }
1427 
1428         final List<String> parentStylesheets = parent.impl_getAllParentStylesheets();
1429 
1430         if (parentStylesheets == null || parentStylesheets.isEmpty()) {
1431             return Collections.<StylesheetContainer>emptyList();
1432         }
1433 
1434         // RT-20643
1435         CssError.setCurrentScene(parent.getScene());
1436 
1437         final List<StylesheetContainer> list = processStylesheets(parentStylesheets, parent);
1438 
1439         // RT-20643
1440         CssError.setCurrentScene(null);
1441 
1442         return list;
1443     }
1444 
1445     //
1446     //
1447     //
1448     private List<StylesheetContainer> gatherSceneStylesheets(final Scene scene) {
1449 
1450         if (scene == null) {
1451             return Collections.<StylesheetContainer>emptyList();
1452         }
1453 
1454         final List<String> sceneStylesheets = scene.getStylesheets();
1455 
1456         if (sceneStylesheets == null || sceneStylesheets.isEmpty()) {
1457             return Collections.<StylesheetContainer>emptyList();
1458         }
1459 
1460         // RT-20643
1461         CssError.setCurrentScene(scene);
1462 
1463         final List<StylesheetContainer> list = processStylesheets(sceneStylesheets, scene.getRoot());
1464 
1465         // RT-20643
1466         CssError.setCurrentScene(null);
1467 
1468         return list;
1469     }
1470 
1471     // return true if this node or any of its parents has an inline style.
1472     private static Node nodeWithInlineStyles(Node node) {
1473 
1474         Node parent = node;
1475 
1476         while (parent != null) {
1477 
1478             final String inlineStyle = parent.getStyle();
1479             if (inlineStyle != null && inlineStyle.isEmpty() == false) {
1480                 return parent;
1481             }
1482             parent = parent.getParent();
1483 
1484         }
1485 
1486         return null;
1487     }
1488 
1489     // reuse key to avoid creation of numerous small objects
1490     private Key key = null;
1491 
1492     /**
1493      * Finds matching styles for this Node.
1494      */
1495     public StyleMap findMatchingStyles(Node node, SubScene subScene, Set<PseudoClass>[] triggerStates) {
1496 
1497         final Scene scene = node.getScene();
1498         if (scene == null) {
1499             return StyleMap.EMPTY_MAP;
1500         }
1501 
1502         CacheContainer cacheContainer = getCacheContainer(node, subScene);
1503         if (cacheContainer == null) {
1504             assert false : node.toString();
1505             return StyleMap.EMPTY_MAP;
1506         }
1507 
1508         final Parent parent =
1509             (node instanceof Parent)
1510                 ? (Parent) node : node.getParent();
1511 
1512         final List<StylesheetContainer> parentStylesheets =
1513                     gatherParentStylesheets(parent);
1514 
1515         final boolean hasParentStylesheets = parentStylesheets.isEmpty() == false;
1516 
1517         final List<StylesheetContainer> sceneStylesheets = gatherSceneStylesheets(scene);
1518 
1519         final boolean hasSceneStylesheets = sceneStylesheets.isEmpty() == false;
1520 
1521         final String inlineStyle = node.getStyle();
1522         final boolean hasInlineStyles = inlineStyle != null && inlineStyle.trim().isEmpty() == false;
1523 
1524         final String sceneUserAgentStylesheet = scene.getUserAgentStylesheet();
1525         final boolean hasSceneUserAgentStylesheet =
1526                 sceneUserAgentStylesheet != null && sceneUserAgentStylesheet.trim().isEmpty() == false;
1527 
1528         final String subSceneUserAgentStylesheet =
1529                 (subScene != null) ? subScene.getUserAgentStylesheet() : null;
1530         final boolean hasSubSceneUserAgentStylesheet =
1531                 subSceneUserAgentStylesheet != null && subSceneUserAgentStylesheet.trim().isEmpty() == false;
1532 
1533         //
1534         // Are there any stylesheets at all?
1535         // If not, then there is nothing to match and the
1536         // resulting StyleMap is going to end up empty
1537         //
1538         if (hasInlineStyles == false
1539                 && hasParentStylesheets == false
1540                 && hasSceneStylesheets == false
1541                 && hasSceneUserAgentStylesheet == false
1542                 && hasSubSceneUserAgentStylesheet == false
1543                 && platformUserAgentStylesheetContainers.isEmpty()) {
1544             return StyleMap.EMPTY_MAP;
1545         }
1546 
1547         final String cname = node.getTypeSelector();
1548         final String id = node.getId();
1549         final List<String> styleClasses = node.getStyleClass();
1550 
1551         if (key == null) {
1552             key = new Key();
1553         }
1554 
1555         key.className = cname;
1556         key.id = id;
1557         for(int n=0, nMax=styleClasses.size(); n<nMax; n++) {
1558 
1559             final String styleClass = styleClasses.get(n);
1560             if (styleClass == null || styleClass.isEmpty()) continue;
1561 
1562             key.styleClasses.add(StyleClassSet.getStyleClass(styleClass));
1563         }
1564 
1565         Map<Key, Cache> cacheMap = cacheContainer.getCacheMap(parentStylesheets);
1566         Cache cache = cacheMap.get(key);
1567 
1568         if (cache != null) {
1569             // key will be reused, so clear the styleClasses for next use
1570             key.styleClasses.clear();
1571 
1572         } else {
1573 
1574             // If the cache is null, then we need to create a new Cache and
1575             // add it to the cache map
1576 
1577             // Construct the list of Selectors that could possibly apply
1578             final List<Selector> selectorData = new ArrayList<>();
1579 
1580             // User agent stylesheets have lowest precedence and go first
1581             if (hasSubSceneUserAgentStylesheet || hasSceneUserAgentStylesheet) {
1582 
1583                 // if has both, use SubScene
1584                 final String uaFileName = hasSubSceneUserAgentStylesheet ?
1585                         subScene.getUserAgentStylesheet().trim() :
1586                         scene.getUserAgentStylesheet().trim();
1587 
1588 
1589                 StylesheetContainer container = null;
1590                 for (int n=0, nMax=userAgentStylesheetContainers.size(); n<nMax; n++) {
1591                     container = userAgentStylesheetContainers.get(n);
1592                     if (uaFileName.equals(container.fname)) {
1593                         break;
1594                     }
1595                     container = null;
1596                 }
1597 
1598                 if (container == null) {
1599                     Stylesheet stylesheet = loadStylesheet(uaFileName);
1600                     if (stylesheet != null) {
1601                         stylesheet.setOrigin(StyleOrigin.USER_AGENT);
1602                     }
1603                     container = new StylesheetContainer(uaFileName, stylesheet);
1604                     userAgentStylesheetContainers.add(container);
1605                 }
1606 
1607                 if (container.selectorPartitioning != null) {
1608 
1609                     final Parent root = hasSubSceneUserAgentStylesheet ? subScene.getRoot() : scene.getRoot();
1610                     container.parentUsers.add(root);
1611 
1612                     final List<Selector> matchingRules =
1613                             container.selectorPartitioning.match(id, cname, key.styleClasses);
1614                     selectorData.addAll(matchingRules);
1615                 }
1616 
1617             } else if (platformUserAgentStylesheetContainers.isEmpty() == false) {
1618                 for(int n=0, nMax= platformUserAgentStylesheetContainers.size(); n<nMax; n++) {
1619                     final StylesheetContainer container = platformUserAgentStylesheetContainers.get(n);
1620                     if (container != null && container.selectorPartitioning != null) {
1621                         final List<Selector> matchingRules =
1622                                 container.selectorPartitioning.match(id, cname, key.styleClasses);
1623                         selectorData.addAll(matchingRules);
1624                     }
1625                 }
1626             }
1627 
1628             // Scene stylesheets come next since declarations from
1629             // parent stylesheets should take precedence.
1630             if (sceneStylesheets.isEmpty() == false) {
1631                 for(int n=0, nMax=sceneStylesheets.size(); n<nMax; n++) {
1632                     final StylesheetContainer container = sceneStylesheets.get(n);
1633                     if (container != null && container.selectorPartitioning != null) {
1634                         final List<Selector> matchingRules =
1635                                 container.selectorPartitioning.match(id, cname, key.styleClasses);
1636                         selectorData.addAll(matchingRules);
1637                     }
1638                 }
1639             }
1640 
1641             // lastly, parent stylesheets
1642             if (hasParentStylesheets) {
1643                 final int nMax = parentStylesheets == null ? 0 : parentStylesheets.size();
1644                 for(int n=0; n<nMax; n++) {
1645                     final StylesheetContainer container = parentStylesheets.get(n);
1646                     if (container.selectorPartitioning != null) {
1647                         final List<Selector> matchingRules =
1648                                 container.selectorPartitioning.match(id, cname, key.styleClasses);
1649                         selectorData.addAll(matchingRules);
1650                     }
1651                 }
1652             }
1653 
1654             // create a new Cache from these selectors.
1655             cache = new Cache(selectorData);
1656             cacheMap.put(key, cache);
1657 
1658             // cause a new Key to be created the next time this method is called
1659             key = null;
1660         }
1661 
1662         //
1663         // Create a style helper for this node from the styles that match.
1664         //
1665         StyleMap smap = cache.getStyleMap(cacheContainer, node, triggerStates, hasInlineStyles);
1666 
1667         return smap;
1668     }
1669 
1670     ////////////////////////////////////////////////////////////////////////////
1671     //
1672     // CssError reporting
1673     //
1674     ////////////////////////////////////////////////////////////////////////////
1675 
1676     private static ObservableList<CssError> errors = null;
1677     /**
1678      * Errors that may have occurred during css processing.
1679      * This list is null until errorsProperty() is called.
1680      * @return
1681      */
1682     public static ObservableList<CssError> errorsProperty() {
1683         if (errors == null) {
1684             errors = FXCollections.observableArrayList();
1685         }
1686         return errors;
1687     }
1688 
1689     /**
1690      * Errors that may have occurred during css processing.
1691      * This list is null until errorsProperty() is called and is used
1692      * internally to figure out whether or  not anyone is interested in
1693      * receiving CssError.
1694      * Not meant for general use - call errorsProperty() instead.
1695      * @return
1696      */
1697     public static ObservableList<CssError> getErrors() {
1698         return errors;
1699     }
1700 
1701     ////////////////////////////////////////////////////////////////////////////
1702     //
1703     // Classes and routines for mapping styles to a Node
1704     //
1705     ////////////////////////////////////////////////////////////////////////////
1706 
1707     private static List<String> cacheMapKey;
1708 
1709     // Each Scene has its own cache
1710     // package for testing
1711     static class CacheContainer {
1712 
1713         private Map<StyleCache.Key,StyleCache> getStyleCache() {
1714             if (styleCache == null) styleCache = new HashMap<StyleCache.Key, StyleCache>();
1715             return styleCache;
1716         }
1717 
1718         private Map<Key,Cache> getCacheMap(List<StylesheetContainer> parentStylesheets) {
1719 
1720             if (cacheMap == null) {
1721                 cacheMap = new HashMap<List<String>,Map<Key,Cache>>();
1722             }
1723 
1724             if (parentStylesheets == null || parentStylesheets.isEmpty()) {
1725 
1726                 Map<Key,Cache> cmap = cacheMap.get(null);
1727                 if (cmap == null) {
1728                     cmap = new HashMap<Key,Cache>();
1729                     cacheMap.put(null, cmap);
1730                 }
1731                 return cmap;
1732 
1733             } else {
1734 
1735                 final int nMax = parentStylesheets.size();
1736                 if (cacheMapKey == null) {
1737                     cacheMapKey = new ArrayList<String>(nMax);
1738                 }
1739                 for (int n=0; n<nMax; n++) {
1740                     StylesheetContainer sc = parentStylesheets.get(n);
1741                     if (sc == null || sc.fname == null || sc.fname.isEmpty()) continue;
1742                     cacheMapKey.add(sc.fname);
1743                 }
1744                 Map<Key,Cache> cmap = cacheMap.get(cacheMapKey);
1745                 if (cmap == null) {
1746                     cmap = new HashMap<Key,Cache>();
1747                     cacheMap.put(cacheMapKey, cmap);
1748                     // create a new cacheMapKey the next time this method is called
1749                     cacheMapKey = null;
1750                 } else {
1751                     // reuse cacheMapKey, but not the data, the next time this method is called
1752                     cacheMapKey.clear();
1753                 }
1754                 return cmap;
1755 
1756             }
1757 
1758         }
1759 
1760         private List<StyleMap> getStyleMapList() {
1761             if (styleMapList == null) styleMapList = new ArrayList<StyleMap>();
1762             return styleMapList;
1763         }
1764 
1765         private int nextSmapId() {
1766             styleMapId = baseStyleMapId + getStyleMapList().size();
1767             return styleMapId;
1768         }
1769 
1770         private void addStyleMap(StyleMap smap) {
1771             assert ((smap.getId() - baseStyleMapId) == getStyleMapList().size());
1772             getStyleMapList().add(smap);
1773         }
1774 
1775         public StyleMap getStyleMap(int smapId) {
1776 
1777             final int correctedId = smapId - baseStyleMapId;
1778 
1779             if (0 <= correctedId && correctedId < getStyleMapList().size()) {
1780                 return getStyleMapList().get(correctedId);
1781             }
1782 
1783             return StyleMap.EMPTY_MAP;
1784         }
1785 
1786         private void clearCache() {
1787 
1788             if (cacheMap != null) cacheMap.clear();
1789             if (styleCache != null) styleCache.clear();
1790             if (styleMapList != null) styleMapList.clear();
1791 
1792             baseStyleMapId = styleMapId;
1793             // 7/8ths is totally arbitrary
1794             if (baseStyleMapId > Integer.MAX_VALUE/8*7) {
1795                 baseStyleMapId = styleMapId = 0;
1796             }
1797         }
1798 
1799         /**
1800          * Get the mapping of property to style from Node.style for this node.
1801          */
1802         private Selector getInlineStyleSelector(String inlineStyle) {
1803 
1804             // If there are no styles for this property then we can just bail
1805             if ((inlineStyle == null) || inlineStyle.trim().isEmpty()) return null;
1806 
1807             if (inlineStylesCache != null && inlineStylesCache.containsKey(inlineStyle)) {
1808                 // Value of Map entry may be null!
1809                 return inlineStylesCache.get(inlineStyle);
1810             }
1811 
1812             //
1813             // inlineStyle wasn't in the inlineStylesCache, or inlineStylesCache was null
1814             //
1815 
1816             if (inlineStylesCache == null) {
1817                 inlineStylesCache = new HashMap<>();
1818             }
1819 
1820             final Stylesheet inlineStylesheet =
1821                     CSSParser.getInstance().parse("*{"+inlineStyle+"}");
1822 
1823             if (inlineStylesheet != null) {
1824 
1825                 inlineStylesheet.setOrigin(StyleOrigin.INLINE);
1826 
1827                 List<Rule> rules = inlineStylesheet.getRules();
1828                 Rule rule = rules != null && !rules.isEmpty() ? rules.get(0) : null;
1829 
1830                 List<Selector> selectors = rule != null ? rule.getUnobservedSelectorList() : null;
1831                 Selector selector = selectors != null && !selectors.isEmpty() ? selectors.get(0) : null;
1832 
1833                 // selector might be null if parser throws some exception
1834                 if (selector != null) {
1835                     selector.setOrdinal(-1);
1836 
1837                     inlineStylesCache.put(inlineStyle, selector);
1838                     return selector;
1839                 }
1840                 // if selector is null, fall through
1841 
1842             }
1843 
1844             // even if selector is null, put it in cache so we don't
1845             // bother with trying to parse it again.
1846             inlineStylesCache.put(inlineStyle, null);
1847             return null;
1848 
1849         }
1850 
1851         private Map<StyleCache.Key,StyleCache> styleCache;
1852 
1853         private Map<List<String>, Map<Key,Cache>> cacheMap;
1854 
1855         private List<StyleMap> styleMapList;
1856 
1857         /**
1858          * Cache of parsed, inline styles. The key is Node.style.
1859          * The value is the Selector from the inline stylesheet.
1860          */
1861         private Map<String,Selector> inlineStylesCache;
1862 
1863         /*
1864          * A simple counter used to generate a unique id for a StyleMap.
1865          * This unique id is used by StyleHelper in figuring out which
1866          * style cache to use.
1867          */
1868         private int styleMapId = 0;
1869 
1870         // When the cache is cleared, styleMapId counting begins here.
1871         // If a StyleHelper calls getStyleMap with an id less than the
1872         // baseStyleMapId, then that StyleHelper is working with an old
1873         // cache and is no longer valid.
1874         private int baseStyleMapId = 0;
1875 
1876             }
1877 
1878     /**
1879      * Creates and caches maps of styles, reusing them as often as practical.
1880      */
1881     private static class Cache {
1882 
1883         private static class Key {
1884             final long[] key;
1885             final String inlineStyle;
1886 
1887             Key(long[] key, String inlineStyle) {
1888                 this.key = key;
1889                 // let inlineStyle be null if it is empty
1890                 this.inlineStyle =  (inlineStyle != null && inlineStyle.trim().isEmpty() ? null : inlineStyle);
1891             }
1892 
1893             @Override public String toString() {
1894                 return Arrays.toString(key) + (inlineStyle != null ? "* {" + inlineStyle + "}" : "");
1895             }
1896 
1897             @Override
1898             public int hashCode() {
1899                 int hash = 3;
1900                 hash = 17 * hash + Arrays.hashCode(this.key);
1901                 if (inlineStyle != null) hash = 17 * hash + inlineStyle.hashCode();
1902                 return hash;
1903             }
1904 
1905             @Override
1906             public boolean equals(Object obj) {
1907                 if (obj == null) {
1908                     return false;
1909                 }
1910                 if (getClass() != obj.getClass()) {
1911                     return false;
1912                 }
1913                 final Key other = (Key) obj;
1914                 if (inlineStyle == null ? other.inlineStyle != null : !inlineStyle.equals(other.inlineStyle)) {
1915                     return false;
1916                 }
1917                 if (!Arrays.equals(this.key, other.key)) {
1918                     return false;
1919                 }
1920                 return true;
1921             }
1922 
1923         }
1924 
1925         // this must be initialized to the appropriate possible selectors when
1926         // the helper cache is created by the StylesheetContainer. Note that
1927         // SelectorPartioning sorts the matched selectors by ordinal, so this
1928         // list of selectors will be in the same order in which the selectors
1929         // appear in the stylesheets.
1930         private final List<Selector> selectors;
1931         private final Map<Key, Integer> cache;
1932 
1933         Cache(List<Selector> selectors) {
1934             this.selectors = selectors;
1935             this.cache = new HashMap<Key, Integer>();
1936         }
1937 
1938         private StyleMap getStyleMap(CacheContainer cacheContainer, Node node, Set<PseudoClass>[] triggerStates, boolean hasInlineStyle) {
1939 
1940             if ((selectors == null || selectors.isEmpty()) && !hasInlineStyle) {
1941                 return StyleMap.EMPTY_MAP;
1942             }
1943 
1944             final int selectorDataSize = selectors.size();
1945 
1946             //
1947             // Since the list of selectors is found by matching only the
1948             // rightmost selector, the set of selectors may larger than those
1949             // selectors that actually match the node. The following loop
1950             // whittles the list down to those selectors that apply.
1951             //
1952             //
1953             // To lookup from the cache, we construct a key from a Long
1954             // where the selectors that match this particular node are
1955             // represented by bits on the long[].
1956             //
1957             long key[] = new long[selectorDataSize/Long.SIZE + 1];
1958             boolean nothingMatched = true;
1959 
1960             for (int s = 0; s < selectorDataSize; s++) {
1961 
1962                 final Selector sel = selectors.get(s);
1963 
1964                 //
1965                 // This particular flavor of applies takes a PseudoClassState[]
1966                 // fills in the pseudo-class states from the selectors where
1967                 // they apply to a node. This is an expedient to looking the
1968                 // applies loopa second time on the matching selectors. This has to
1969                 // be done ahead of the cache lookup since not all nodes that
1970                 // have the same set of selectors will have the same node hierarchy.
1971                 //
1972                 // For example, if I have .foo:hover:focused .bar:selected {...}
1973                 // and the "bar" node is 4 away from the root and the foo
1974                 // node is two away from the root, pseudoclassBits would be
1975                 // [selected, 0, hover:focused, 0]
1976                 // Note that the states run from leaf to root. This is how
1977                 // the code in StyleHelper expects things.
1978                 // Note also that, if the selector does not apply, the triggerStates
1979                 // is unchanged.
1980                 //
1981 
1982                 if (sel.applies(node, triggerStates, 0)) {
1983                     final int index = s / Long.SIZE;
1984                     final long mask = key[index] | 1l << s;
1985                     key[index] = mask;
1986                     nothingMatched = false;
1987                 }
1988             }
1989 
1990             // nothing matched!
1991             if (nothingMatched && hasInlineStyle == false) {
1992                 return StyleMap.EMPTY_MAP;
1993             }
1994 
1995             final String inlineStyle = node.getStyle();
1996             final Key keyObj = new Key(key, inlineStyle);
1997 
1998             if (cache.containsKey(keyObj)) {
1999                 Integer styleMapId = cache.get(keyObj);
2000                 final StyleMap styleMap = styleMapId != null
2001                         ? cacheContainer.getStyleMap(styleMapId.intValue())
2002                         : StyleMap.EMPTY_MAP;
2003                 return styleMap;
2004             }
2005 
2006             final List<Selector> selectors = new ArrayList<>();
2007 
2008             if (hasInlineStyle) {
2009                 Selector selector = cacheContainer.getInlineStyleSelector(inlineStyle);
2010                 if (selector != null) selectors.add(selector);
2011             }
2012 
2013             for (int k = 0; k<key.length; k++) {
2014 
2015                 if (key[k] == 0) continue;
2016 
2017                 final int offset = k * Long.SIZE;
2018 
2019                 for (int b = 0; b<Long.SIZE; b++) {
2020 
2021                     // bit at b in key[k] set?
2022                     final long mask = 1l << b;
2023                     if ((mask & key[k]) != mask) continue;
2024 
2025                     final Selector pair = this.selectors.get(offset + b);
2026                     selectors.add(pair);
2027                 }
2028             }
2029 
2030             int id = cacheContainer.nextSmapId();
2031             cache.put(keyObj, Integer.valueOf(id));
2032 
2033             final StyleMap styleMap = new StyleMap(id, selectors);
2034             cacheContainer.addStyleMap(styleMap);
2035             return styleMap;
2036         }
2037 
2038     }
2039 
2040     /**
2041      * Get the map of property to style from the rules and declarations
2042      * in the stylesheet. There is no need to do selector matching here since
2043      * the stylesheet is from parsing Node.style.
2044      */
2045     public StyleMap createInlineStyleMap(Styleable styleable) {
2046 
2047         Stylesheet inlineStylesheet =
2048                 CSSParser.getInstance().parseInlineStyle(styleable);
2049         if (inlineStylesheet == null)  return StyleMap.EMPTY_MAP;
2050 
2051         inlineStylesheet.setOrigin(StyleOrigin.INLINE);
2052 
2053         List<Selector> pairs = new ArrayList<>(1);
2054 
2055         int ordinal = 0;
2056 
2057         final List<Rule> stylesheetRules = inlineStylesheet.getRules();
2058 
2059         List<Selector> selectorList =  null;
2060 
2061         for (int i = 0, imax = stylesheetRules.size(); i < imax; i++) {
2062             
2063             final Rule rule = stylesheetRules.get(i);
2064             if (rule == null) continue;
2065 
2066             List<Selector> selectors = rule.getUnobservedSelectorList();
2067             if (selectorList == null || selectors.isEmpty()) continue;
2068 
2069             selectorList.addAll(selectors);
2070         }
2071 
2072         // TODO: should have a cacheContainer for inline styles?
2073         return new StyleMap(-1, selectorList);
2074     }
2075 
2076 
2077     /**
2078      * The key used in the cacheMap of the StylesheetContainer
2079      */
2080     private static class Key {
2081         // note that the class name here is the *full* class name, such as
2082         // javafx.scene.control.Button. We only bother parsing this down to the
2083         // last part when doing matching against selectors, and so want to avoid
2084         // having to do a bunch of preliminary parsing in places where it isn't
2085         // necessary.
2086         String className;
2087         String id;
2088         final StyleClassSet styleClasses;
2089 
2090         private Key() {
2091             styleClasses = new StyleClassSet();
2092         }
2093 
2094         @Override
2095         public boolean equals(Object o) {
2096             if (this == o) {
2097                 return true;
2098             }
2099             if (o instanceof Key) {
2100                 Key other = (Key)o;
2101 
2102                 if (className == null ? other.className != null : (className.equals(other.className) == false)) {
2103                     return false;
2104                 }
2105 
2106                 if (id == null ? other.id != null : (id.equals(other.id) == false)) {
2107                     return false;
2108                 }
2109 
2110                 return this.styleClasses.equals(other.styleClasses);
2111             }
2112             return true;
2113         }
2114 
2115         @Override
2116         public int hashCode() {
2117             int hash = 7;
2118             hash = 29 * hash + (this.className != null ? this.className.hashCode() : 0);
2119             hash = 29 * hash + (this.id != null ? this.id.hashCode() : 0);
2120             hash = 29 * hash + this.styleClasses.hashCode();
2121             return hash;
2122         }
2123 
2124     }
2125 
2126 
2127         }