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