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