Print this page

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

Split | Close
Expand all
Collapse all

              --- old/javafx-ui-common/src/com/sun/javafx/css/StyleManager.java
              +++ new/javafx-ui-common/src/com/sun/javafx/css/StyleManager.java

       1    1  /*
       2    2   * Copyright (c) 2010, 2012, Oracle and/or its affiliates. All rights reserved.
       3    3   * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       4    4   *
       5    5   * This code is free software; you can redistribute it and/or modify it
       6    6   * under the terms of the GNU General Public License version 2 only, as
       7    7   * published by the Free Software Foundation.  Oracle designates this
       8    8   * particular file as subject to the "Classpath" exception as provided
       9    9   * by Oracle in the LICENSE file that accompanied this code.
      10   10   *
      11   11   * This code is distributed in the hope that it will be useful, but WITHOUT
      12   12   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
      13   13   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
      14   14   * version 2 for more details (a copy is included in the LICENSE file that
      15   15   * accompanied this code).
      16   16   *
      17   17   * You should have received a copy of the GNU General Public License version

↓ open down ↓ | 17 lines elided | ↑ open up ↑

      18   18   * 2 along with this work; if not, write to the Free Software Foundation,
      19   19   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
      20   20   *
      21   21   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
      22   22   * or visit www.oracle.com if you need additional information or have any
      23   23   * questions.
      24   24   */
      25   25
      26   26  package com.sun.javafx.css;
      27   27
      28      -import com.sun.javafx.collections.TrackableObservableList;
      29   28  import java.io.IOException;
      30   29  import java.net.MalformedURLException;
      31   30  import java.net.URI;
      32   31  import java.net.URL;
      33   32  import java.io.FilePermission;
      34   33  import java.security.AccessControlContext;
      35   34  import java.security.AccessController;
      36   35  import java.security.PermissionCollection;
      37   36  import java.security.ProtectionDomain;
      38   37  import java.util.ArrayList;
      39      -import java.util.Collection;
      40   38  import java.util.Collections;
      41   39  import java.util.HashMap;
      42   40  import java.util.Iterator;
      43   41  import java.util.List;
      44   42  import java.util.Map;
      45   43  import java.util.jar.JarEntry;
      46   44  import java.util.jar.JarFile;
      47   45
      48   46  import javafx.collections.FXCollections;
      49   47  import javafx.scene.Node;
      50   48  import javafx.scene.Scene;
      51   49
      52   50  import com.sun.javafx.css.parser.CSSParser;
      53   51  import com.sun.javafx.css.StyleHelper.StyleCacheBucket;
      54   52  import com.sun.javafx.css.StyleHelper.StyleCacheKey;
      55   53  import com.sun.javafx.logging.PlatformLogger;
      56   54  import java.io.FileNotFoundException;
      57   55  import java.lang.ref.Reference;
      58      -import java.lang.ref.ReferenceQueue;
      59   56  import java.lang.ref.WeakReference;
      60   57  import java.security.PrivilegedAction;
      61   58  import java.security.PrivilegedExceptionAction;
      62   59  import java.security.PrivilegedActionException;
      63   60  import java.util.*;
      64   61  import java.util.Map.Entry;
      65      -import javafx.beans.InvalidationListener;
      66      -import javafx.beans.value.ChangeListener;
      67      -import javafx.beans.value.ObservableValue;
      68   62  import javafx.collections.*;
      69      -import javafx.collections.ListChangeListener.Change;
      70   63  import javafx.scene.Parent;
      71      -import javafx.stage.PopupWindow;
      72   64  import javafx.stage.Window;
      73   65
      74   66  /**
      75   67   * Contains the stylesheet state for a single scene. This includes both the
      76   68   * Stylesheets defined on the Scene itself as well as a map of stylesheets for
      77   69   * "style"s defined on the Node itself. These containers are kept in the
      78   70   * containerMap, key'd by the Scene to which they belong. <p> One of the key
      79   71   * responsibilities of the StylesheetContainer is to create and maintain an
      80   72   * admittedly elaborate series of caches so as to minimize the amount of time it
      81   73   * takes to match a Node to its eventual StyleHelper, and to reuse the

      82   74   * StyleHelper as much as possible. <p> Initially, the cache is empty. It is
      83   75   * recreated whenever the userStylesheets on the container change, or whenever
      84   76   * the userAgentStylesheet changes. The cache is built up as nodes are looked
      85   77   * for, and thus there is some overhead associated with the first lookup but
      86   78   * which is then not repeated for subsequent lookups. <p> The cache system used
      87   79   * is a two level cache. The first level cache simply maps the
      88   80   * classname/id/styleclass combination of the request node to a 2nd level cache.
      89   81   * If the node has "styles" specified then we still use this 2nd level cache,
      90   82   * but must combine its rules with the rules specified in "styles" and perform
      91   83   * more work to cascade properly. <p> The 2nd level cache contains a data
      92   84   * structure called the Cache. The Cache contains an ordered sequence of Rules,
      93   85   * a Long, and a Map. The ordered sequence of rules are the rules that *may*
      94   86   * match a node with the given classname, id, and style class. For example,
      95   87   * rules which may apply are any rule where the simple selector of the rule
      96   88   * contains a reference to the id, style class, or classname of the Node, or a
      97   89   * compound selector who's "descendant" part is a simple selector which contains
      98   90   * a reference to the id, style class, or classname of the Node. <p> During
      99   91   * lookup, we will iterate over all the potential rules and discover if they
     100   92   * apply to this particular node. If so, then we toggle a bit position in the
     101   93   * Long corresponding to the position of the rule that matched. This long then
     102   94   * becomes our key into the final map. <p> Once we have established our key, we
     103   95   * will visit the map and look for an existing StyleHelper. If we find a
     104   96   * StyleHelper, then we will return it. If not, then we will take the Rules that

↓ open down ↓ | 23 lines elided | ↑ open up ↑

     105   97   * matched and construct a new StyleHelper from their various parts. <p> This
     106   98   * system, while elaborate, also provides for numerous fast paths and sharing of
     107   99   * data structures which should dramatically reduce the memory and runtime
     108  100   * performance overhead associated with CSS by reducing the matching overhead
     109  101   * and caching as much as possible. We make no attempt to use weak references
     110  102   * here, so if memory issues result one work around would be to toggle the root
     111  103   * user agent stylesheet or stylesheets on the scene to cause the cache to be
     112  104   * flushed.
     113  105   */
     114  106
     115      -public final class StyleManager {
          107 +abstract public class StyleManager<T> {
     116  108
     117      -    private static class Holder {
     118      -        private static PlatformLogger LOGGER = com.sun.javafx.Logging.getCSSLogger();
     119      -    }
     120      -
     121      -    private static PlatformLogger logger() {
     122      -        return Holder.LOGGER;
     123      -    }
          109 +    protected static PlatformLogger LOGGER = com.sun.javafx.Logging.getCSSLogger();
     124  110
     125  111      /**
     126  112       * Return the StyleManager for the given Scene, or null if the scene
     127  113       * is no longer referenced.
     128  114       */
     129      -    public static StyleManager getStyleManager(Scene scene) {
          115 +    public static <T> StyleManager getStyleManager(T key) {
     130  116          final Reference<StyleManager> styleManagerRef =
     131      -                scene != null ? managerReferenceMap.get(scene) : null;
          117 +                key != null ? managerReferenceMap.get(key) : null;
     132  118          return styleManagerRef != null ? styleManagerRef.get() : null;
     133  119      }
     134  120
          121 +    public static StyleManager createStyleManager(Scene scene) {
          122 +        return new SceneStyleManager(scene);
          123 +    }
          124 +
          125 +    public static StyleManager createStyleManager(Parent parent, StyleManager ancestorStyleManager) {
          126 +        return new ParentStyleManager(parent, ancestorStyleManager);
          127 +    }
          128 +
     135  129      /**
     136  130       *
     137      -     * @param scene
          131 +     * @param owner
     138  132       */
     139      -    public StyleManager(Scene scene) {
          133 +    protected StyleManager(T owner) {
     140  134
     141      -        if (scene == null) throw new IllegalArgumentException("scene cannot be null");
          135 +        if (owner == null) throw new IllegalArgumentException("scene cannot be null");
     142  136
     143      -        this.rootScene = this.scene = scene;
          137 +        this.owner = owner;
     144  138
     145  139          cacheMap = new HashMap<Key, Cache>();
     146  140
     147  141          referencedStylesheets = new ArrayList<Stylesheet>();
     148  142          if (defaultUserAgentStylesheet != null) {
     149  143              referencedStylesheets.add(defaultUserAgentStylesheet);
     150  144          }
     151  145
     152  146          styleCache = new HashMap<StyleCacheKey, StyleCacheBucket>();
     153  147
     154      -        key = new Key();
     155      -
     156  148          smapCount = 0;
     157  149
     158  150          // A Reference is needed here since Scene is both in the key and value
     159      -        managerReferenceMap.put(scene, new WeakReference<StyleManager>(this));
          151 +        managerReferenceMap.put(owner, new WeakReference<StyleManager>(this));
     160  152
     161      -        // We have special handling for the case of a node which is in a popup
     162      -        // scene. The problem is that the root scene used for the purpose of style
     163      -        // sheet support isn't the scene of the popup (which is a hidden API
     164      -        // member), but rather the main stage's scene. So we need the scene of the
     165      -        // root stage.
     166      -        Window window = scene.getWindow();
     167      -        while (window instanceof PopupWindow) {
     168      -            window = ((PopupWindow)window).getOwnerWindow();
     169      -        }
     170      -
     171      -        // A popup owner's window might not be set yet.
     172      -        if (window == null) scene.windowProperty().addListener(windowListener);
     173  153      }
     174      -
     175      -    private final ChangeListener<Window> windowListener = new ChangeListener<Window>() {
     176      -
     177      -        @Override
     178      -        public void changed(ObservableValue<? extends Window> observable, Window oldValue, Window newValue) {
     179      -
     180      -            if (newValue != null) {
     181      -
     182      -                observable.removeListener(this);
     183      -
     184      -                if (newValue instanceof PopupWindow) {
     185      -
     186      -                    //
     187      -                    // Find the owner window of this popup
     188      -                    //
     189      -                    Window window = newValue;
     190      -                    PopupWindow popup = null;
     191      -                    while (window instanceof PopupWindow) {
     192      -                        popup = (PopupWindow)window;
     193      -                        window = popup.getOwnerWindow();
     194      -                    }
     195      -
     196      -                    Scene root_scene = null;
     197      -                    if (window != null) {
     198      -                        root_scene = window.getScene();
     199      -                    }
     200      -
     201      -                    if (root_scene != null) {
     202      -                        setRootScene(root_scene);
     203      -                    } else {
     204      -                        //
     205      -                        // Still don't have the root Scene. Keep listening.
     206      -                        //
     207      -                        popup.ownerWindowProperty().addListener(this);
     208      -                    }
     209      -
     210      -                } else {
     211      -
     212      -                    // if the newWindow isn't a popup window, then
     213      -                    // we've found the owner window
     214      -                    setRootScene(newValue.getScene());
     215      -                }
     216      -            }
     217      -        }
     218      -    };
     219      -
     220      -    private void setRootScene(Scene root_scene) {
     221      -
     222      -        assert(root_scene != null);
     223      -
     224      -        if (root_scene != this.scene) {
     225      -
     226      -            // If the root Scene's stylesheets change, then this popup's styles
     227      -            // need to be updated.
     228      -            root_scene.getStylesheets().addListener(new ListChangeListener<String>() {
     229      -
     230      -                @Override
     231      -                public void onChanged(Change<? extends String> c) {
     232      -                    updateStylesheets();
     233      -                }
     234      -            });
     235      -            this.rootScene = root_scene;
     236      -            scene.getRoot().impl_reapplyCSS();
     237      -
     238      -        }
     239  154
     240      -    }
     241      -
     242      -    /*
     243      -     * The scene that created this StyleManager.
     244      -     */
     245      -    private final Scene scene;
     246      -    Scene getScene() { return scene; } // for testing
     247  155      /*
     248      -     * In the case of a popup, this is the Scene of the ownerWindow. If
     249      -     * the Scene's window is not a PopupWindow, then the rootScene == scene.
          156 +     * The scene or parent that created this StyleManager.
     250  157       */
     251      -    private Scene rootScene; // TODO: potential leak?
     252      -    Scene getRootScene() { return rootScene; } // for testing
          158 +    protected final T owner;
          159 +    T getOwner() { return owner; } // for testing
     253  160
     254  161      /*
     255  162       * Stylesheets that are referenced by this Scene. The parsed stylesheets
     256  163       * are held in the StylesheetContainer and could be looked up from there,
     257  164       * but that lookup would be frequent so we hang onto the parsed stylesheets
     258  165       */
     259      -    private List<Stylesheet> referencedStylesheets;
          166 +    protected final List<Stylesheet> referencedStylesheets;
     260  167      // for testing
     261  168      List<Stylesheet> getReferencedStylesheets() { return referencedStylesheets; }
     262  169
          170 +
     263  171      /*
     264  172       * Scene does not have a inline style, but an applet might. This is
     265  173       * the applet's stylesheet from a 'style' attribute.
     266  174       */
     267      -    private Stylesheet inlineStylesheet;
          175 +    protected Stylesheet inlineStylesheet;
     268  176      // for testing
     269  177      Stylesheet getInlineStylesheet() { return inlineStylesheet; }
     270  178
     271  179      /**
     272  180       * The map of Caches, key'd by a combination of class name, style class, and
     273  181       * id.
     274  182       */
     275      -    private Map<Key, Cache> cacheMap;
          183 +    protected final Map<Key, Cache> cacheMap;
     276  184      // for testing
     277  185      Map<Key, Cache> getCacheMap() { return cacheMap; }
     278  186
     279      -    /**
     280      -     * This instance is reused for each lookup in the cache so as to avoid
     281      -     * creating temporary objects as much as reasonable.
     282      -     */
     283      -    private final Key key;
     284      -
     285  187      /*
     286  188       * The StyleHelper's cached values are relevant only for a given scene.
     287  189       * Since a StylesheetContainer is created for a Scene with stylesheets,
     288  190       * it makes sense that the container should own the valueCache. This
     289  191       * way, each scene gets its own valueCache.
     290  192       */
     291      -    private Map<StyleCacheKey, StyleCacheBucket> styleCache;
          193 +    private final Map<StyleCacheKey, StyleCacheBucket> styleCache;
     292  194      // for testing
     293  195      Map<StyleCacheKey, StyleCacheBucket> getStyleCache() { return styleCache; }
     294  196
     295  197      /*
     296  198       * A simple counter used to generate a unique id for a StyleMap.
     297  199       * This unique id is used by StyleHelper in figuring out which
     298  200       * style cache to use.
     299  201       */
     300  202      private int smapCount;
     301  203
     302  204      /**
     303  205       * A map from Scene => StyleManager. This provides us a way to find
     304  206       * the StyleManager which is owned by a given Scene without having to
     305  207       * add a getStyleManager public API to Scene. This map is used primarily
     306  208       * for getting the StyleManager of the owning Scene for a popup.
     307  209       */
     308      -    private static Map<Scene, Reference<StyleManager>> managerReferenceMap =
     309      -            new WeakHashMap<Scene, Reference<StyleManager>>();
          210 +    protected static final Map<Object, Reference<StyleManager>> managerReferenceMap =
          211 +            new WeakHashMap<Object, Reference<StyleManager>>();
     310  212
     311      -    /**
     312      -     * A map from String => Stylesheet for a Scene. If a stylesheet for the
     313      -     * given URL has already been loaded then we'll simply reuse the stylesheet
     314      -     * rather than loading a duplicate.
     315      -     */
     316      -    private static final Map<String,StylesheetContainer<Scene>> sceneStylesheetMap
     317      -        = new HashMap<String,StylesheetContainer<Scene>>();
     318      -
     319      -    /**
     320      -     * Another map from String => Stylesheet for a Parent. If a stylesheet for the
     321      -     * given URL has already been loaded then we'll simply reuse the stylesheet
     322      -     * rather than loading a duplicate.
     323      -     */
     324      -    private static final Map<String,StylesheetContainer<Parent>> parentStylesheetMap
     325      -        = new HashMap<String,StylesheetContainer<Parent>>();
     326      -
     327  213     /**
     328  214       * This stylesheet represents the "default" set of styles for the entire
     329  215       * platform. This is typically only set by the UI Controls module, and
     330  216       * otherwise is generally null. Whenever this variable changes (via the
     331  217       * setDefaultUserAgentStylesheet function call) then we will end up clearing
     332  218       * all of the caches.
     333  219       */
     334      -    private static Stylesheet defaultUserAgentStylesheet;
          220 +    protected static Stylesheet defaultUserAgentStylesheet;
     335  221
     336  222
     337  223      /**
     338  224       * User agent stylesheets from Control.getUserAgentStylesheet.
     339  225       */
     340      -    private static final Map<String,Stylesheet> userAgentStylesheetMap =
          226 +    protected static final Map<String,Stylesheet> userAgentStylesheetMap =
     341  227              new HashMap<String,Stylesheet>();
     342  228
     343  229
     344  230      /*
     345  231       * A container for stylesheets and the Parents or Scenes that use them.
     346  232       * If a stylesheet is removed, then all other Parents or Scenes
     347  233       * that use that stylesheet should get new styles if the
     348  234       * stylesheet is added back in since the stylesheet may have been
     349  235       * removed and re-added because it was edited (typical of SceneBuilder).
     350  236       * This container provides the hooks to get back to those Parents or Scenes.
     351  237       *
     352  238       * StylesheetContainer<Parent> are created and added to parentStylesheetMap
     353  239       * in the method gatherParentStylesheets.
     354  240       *
     355  241       * StylesheetContainer<Scene> are created and added to sceneStylesheetMap in
     356  242       * the method updateStylesheets
     357  243       */
     358      -    private static class StylesheetContainer<T> {
          244 +    protected static class StylesheetContainer<T> {
     359  245          // the stylesheet url
     360      -        private final String fname;
          246 +        final String fname;
     361  247          // the parsed stylesheet so we don't reparse for every parent that uses it
     362      -        private final Stylesheet stylesheet;
          248 +        final Stylesheet stylesheet;
     363  249          // the parents or scenes that use this stylesheet. Typically, this list
     364  250          //  should be very small.
     365      -        private final RefList<T> users;
          251 +        final RefList<T> users;
     366  252          // the keys for finding Cache entries that use this stylesheet.
     367  253          // This list should also be fairly small
     368      -        private final RefList<Key> keys;
          254 +        final RefList<Key> keys;
     369  255
     370      -        private StylesheetContainer(String fname, Stylesheet stylesheet) {
          256 +        StylesheetContainer(String fname, Stylesheet stylesheet) {
     371  257              this.fname = fname;
     372  258              this.stylesheet = stylesheet;
     373  259              this.users = new RefList<T>();
     374  260              this.keys = new RefList<Key>();
     375  261          }
     376  262      }
     377  263
     378  264      /*
     379  265       * A list that holds references. Used by StylesheetContainer.
     380  266       * We only ever add to this list, or the whole container gets thrown
     381  267       * away.
     382  268       */
     383      -    private static class RefList<K> {
          269 +    protected static class RefList<K> {
     384  270
     385      -        private final List<Reference<K>> list = new ArrayList<Reference<K>>();
          271 +        final List<Reference<K>> list = new ArrayList<Reference<K>>();
     386  272
     387      -        private void add(K key) {
          273 +        void add(K key) {
     388  274
     389  275              for (int n=list.size()-1; 0<=n; --n) {
     390  276                  final Reference<K> ref = list.get(n);
     391  277                  final K k = ref.get();
     392  278                  if (k == null) {
     393  279                      // stale reference, remove it.
     394  280                      list.remove(n);
     395  281                  } else {
     396  282                      // already have it, bail
     397  283                      if (k == key) return;
     398  284                  }
     399  285              }
     400  286              // not found, add it.
     401  287              list.add(new WeakReference<K>(key));
     402  288          }
     403  289
     404  290      }
     405  291
     406      -    ////////////////////////////////////////////////////////////////////////////
     407      -    //
     408      -    // Parent stylesheet handling
     409      -    //
     410      -    ////////////////////////////////////////////////////////////////////////////
          292 +    public abstract void updateStylesheets();
     411  293
          294 +    public void stylesheetsChanged(ListChangeListener.Change<String> c) {
          295 +        // for now, just call updateStylesheets
          296 +        updateStylesheets();
          297 +    }
          298 +
     412  299      /**
     413      -     * called from Parent's stylesheets property's onChanged method
          300 +     * Set a stylesheet for the scene from the given stylesheet string. This
          301 +     * stylesheet comes after user agent stylesheets but before author
          302 +     * stylesheets and applies only to this scene.
     414  303       */
     415      -    public void parentStylesheetsChanged(Parent parent, Change<String> c) {
     416      -
     417      -        final Scene scene = parent.getScene();
          304 +    public static void setInlineStylesheet(Scene scene, Stylesheet stylesheet) {
          305 +
     418  306          if (scene == null) return;
     419      -
     420      -        boolean wasRemoved = false;
     421      -
     422      -        while (c.next()) {
     423      -
     424      -            // RT-22565
     425      -            // If the String was removed, remove it from parentStylesheetMap
     426      -            // and remove all Caches that got styles from the stylesheet.
     427      -            // The stylesheet may still be referenced by some other parent,
     428      -            // in which case the stylesheet will be reparsed and added
     429      -            // back to parentStylesheetMap when the StyleHelper is recreated.
     430      -            // This incurs a some overhead since the stylesheet has to be
     431      -            // reparsed, but this keeps the other Parents that use this
     432      -            // in sync. For example, SceneBuilder will remove and then add
     433      -            // a stylesheet after it has been edited and all Parents that use
     434      -            // that stylesheet should get the new values.
     435      -            //
     436      -            if (c.wasRemoved()) {
     437      -
     438      -                final List<String> list = c.getRemoved();
     439      -                int nMax = list != null ? list.size() : 0;
     440      -                for (int n=0; n<nMax; n++) {
     441      -                    final String fname = list.get(n);
     442      -
     443      -                    // remove this parent from the container and clear
     444      -                    // all caches used by this stylesheet
     445      -                    StylesheetContainer<Parent> psc = parentStylesheetMap.remove(fname);
     446      -                    if (psc != null) {
     447      -                        clearParentCache(psc);
     448      -                    }
     449      -                }
     450      -            }
     451      -
     452      -            // RT-22565: only wasRemoved matters. If the logic was applied to
     453      -            // wasAdded, then the stylesheet would be reparsed each time a
     454      -            // a Parent added it.
     455      -
     456      -        }
     457      -        // parent uses change also
     458      -        c.reset();
     459      -    }
     460      -
     461      -    // RT-22565: Called from parentStylesheetsChanged to clear the cache entries
     462      -    // for parents that use the same stylesheet
     463      -    private void clearParentCache(StylesheetContainer<Parent> psc) {
     464      -
     465      -        final List<Reference<Parent>> parentList = psc.users.list;
     466      -        final List<Reference<Key>>    keyList    = psc.keys.list;
     467      -        for (int n=parentList.size()-1; 0<=n; --n) {
     468      -
     469      -            final Reference<Parent> ref = parentList.get(n);
     470      -            final Parent parent = ref.get();
     471      -            if (parent == null) continue;
     472      -
     473      -            final Scene scene = parent.getScene();
     474      -            if (scene == null) continue;
     475      -
     476      -            Reference<StyleManager> styleManagerRef = managerReferenceMap.get(scene);
     477      -            if (styleManagerRef == null) continue;
     478      -
     479      -            StyleManager styleManager = styleManagerRef.get();
     480      -            if (styleManager == null) continue;
     481      -
     482      -            styleManager.clearParentCache(keyList);
     483      -
     484      -            // tell parent it needs to reapply css
     485      -            parent.impl_reapplyCSS();
     486      -        }
     487      -    }
     488      -
     489      -    // RT-22565: Called from clearParentCache to clear the cache entries.
     490      -    private void clearParentCache(List<Reference<Key>> keyList) {
     491      -
     492      -        if (cacheMap.isEmpty()) return;
     493      -
     494      -        for (int n=keyList.size()-1; 0<=n; --n) {
     495      -
     496      -            final Reference<Key> ref = keyList.get(n);
     497      -            final Key key = ref.get();
     498      -            if (key == null) continue;
     499      -
     500      -            final Cache cache = cacheMap.remove(key);
          307 +
          308 +        StyleManager styleManager = StyleManager.getStyleManager(scene);
          309 +        if (styleManager != null) {
          310 +            styleManager.inlineStylesheet = stylesheet;
          311 +            styleManager.updateStylesheets();
     501  312          }
     502      -    }
     503      -
          313 +
          314 +    }
          315 +
          316 +
     504  317      ////////////////////////////////////////////////////////////////////////////
     505  318      //
     506  319      // Stylesheet loading
     507  320      //
     508  321      ////////////////////////////////////////////////////////////////////////////
     509  322      private static URL getURL(String urlStr) {
     510  323          try {
     511  324              return new URL(urlStr);
     512  325          } catch (MalformedURLException malf) {
     513  326              // This may be a relative URL, so try resolving

     514  327              // it using the application classloader

↓ open down ↓ | 1 lines elided | ↑ open up ↑

     515  328              ClassLoader cl = Thread.currentThread().getContextClassLoader();
     516  329              if (cl != null) {
     517  330                  String path = (urlStr != null && urlStr.startsWith("/"))
     518  331                          ? urlStr.substring(1) : urlStr;
     519  332                  return cl.getResource(path);
     520  333              }
     521  334              return null;
     522  335          }
     523  336      }
     524  337
     525      -    private static Stylesheet loadStylesheet(final String fname) {
          338 +    protected static Stylesheet loadStylesheet(final String fname) {
     526  339          try {
     527  340              return loadStylesheetUnPrivileged(fname);
     528  341          } catch (java.security.AccessControlException ace) {
     529      -            if (logger().isLoggable(PlatformLogger.INFO)) {
     530      -                logger().info("Could not load the stylesheet, trying with FilePermissions : " + fname);
          342 +            if (LOGGER.isLoggable(PlatformLogger.INFO)) {
          343 +                LOGGER.info("Could not load the stylesheet, trying with FilePermissions : " + fname);
     531  344              }
     532  345
     533  346              /*
     534  347              ** we got an access control exception, so
     535  348              ** we could be running from an applet/jnlp/or with a security manager.
     536  349              ** we'll allow the app to read a css file from our runtime jar,
     537  350              ** and give it one more chance.
     538  351              */
     539  352
     540  353              /*

     541  354              ** check that there are enough chars after the !/ to have a valid .css or .bss file name
     542  355              */
     543  356              if ((fname.length() < 7) && (fname.indexOf("!/") < fname.length()-7)) {
     544  357                  return null;
     545  358              }
     546  359
     547  360              /*
     548  361              **
     549  362              ** first check that it's actually looking for the same runtime jar
     550  363              ** that we're running from, and not some other file.
     551  364              */
     552  365              try {
     553  366                  URI requestedFileUrI = new URI(fname);
     554  367
     555  368                  /*
     556  369                  ** is the requested file in a jar
     557  370                  */
     558  371                  if ("jar".equals(requestedFileUrI.getScheme())) {
     559  372                      /*
     560  373                      ** let's check that the css file is being requested from our
     561  374                      ** runtime jar
     562  375                      */
     563  376                      URI styleManagerJarURI = AccessController.doPrivileged(new PrivilegedExceptionAction<URI>() {
     564  377                              public URI run() throws java.net.URISyntaxException, java.security.PrivilegedActionException {
     565  378                              return StyleManager.class.getProtectionDomain().getCodeSource().getLocation().toURI();
     566  379                          }
     567  380                      });
     568  381
     569  382                      final String styleManagerJarPath = styleManagerJarURI.getSchemeSpecificPart();
     570  383                      String requestedFilePath = requestedFileUrI.getSchemeSpecificPart();
     571  384                      String requestedFileJarPart = requestedFilePath.substring(requestedFilePath.indexOf('/'), requestedFilePath.indexOf("!/"));
     572  385                      /*
     573  386                      ** it's the correct jar, check it's a file access
     574  387                      ** strip off the leading jar
     575  388                      */
     576  389                      if (styleManagerJarPath.equals(requestedFileJarPart.toString())) {
     577  390                          /*
     578  391                          ** strip off the leading "jar",
     579  392                          ** the css file name is past the last '!'
     580  393                          */
     581  394                          String requestedFileJarPathNoLeadingSlash = fname.substring(fname.indexOf("!/")+2);
     582  395                          /*
     583  396                          ** check that it's looking for a css file in the runtime jar
     584  397                          */
     585  398                          if (fname.endsWith(".css") || fname.endsWith(".bss")) {
     586  399                              /*
     587  400                              ** set up a read permission for the jar
     588  401                              */
     589  402                              FilePermission perm = new FilePermission(styleManagerJarPath, "read");
     590  403
     591  404                              PermissionCollection perms = perm.newPermissionCollection();
     592  405                              perms.add(perm);
     593  406                              AccessControlContext permsAcc = new AccessControlContext(
     594  407                                  new ProtectionDomain[] {
     595  408                                      new ProtectionDomain(null, perms)
     596  409                                  });
     597  410                              /*
     598  411                              ** check that the jar file exists, and that we're allowed to
     599  412                              ** read it.
     600  413                              */
     601  414                              JarFile jar = null;
     602  415                              try {
     603  416                                  jar = AccessController.doPrivileged(new PrivilegedExceptionAction<JarFile>() {
     604  417                                          public JarFile run() throws FileNotFoundException, IOException {
     605  418                                              return new JarFile(styleManagerJarPath);
     606  419                                          }
     607  420                                      }, permsAcc);
     608  421                              } catch (PrivilegedActionException pae) {
     609  422                                  /*
     610  423                                  ** we got either a FileNotFoundException or an IOException
     611  424                                  ** in the privileged read. Return the same error as we
     612  425                                  ** would have returned if the css file hadn't of existed.
     613  426                                  */
     614  427                                  return null;
     615  428                              }
     616  429                              if (jar != null) {
     617  430                                  /*
     618  431                                  ** check that the file is in the jar
     619  432                                  */
     620  433                                  JarEntry entry = jar.getJarEntry(requestedFileJarPathNoLeadingSlash);
     621  434                                  if (entry != null) {
     622  435                                      /*
     623  436                                      ** allow read access to the jar
     624  437                                      */
     625  438                                      return AccessController.doPrivileged(
     626  439                                          new PrivilegedAction<Stylesheet>() {
     627  440                                              @Override public Stylesheet run() {
     628  441                                                  return loadStylesheetUnPrivileged(fname);
     629  442                                              }}, permsAcc);
     630  443                                  }
     631  444                              }
     632  445                          }
     633  446                      }
     634  447                  }
     635  448                  /*
     636  449                  ** no matter what happen, we return the same error that would
     637  450                  ** be returned if the css file hadn't of existed.
     638  451                  ** That way there in no information leaked.
     639  452                  */
     640  453                  return null;
     641  454              }
     642  455              /*
     643  456              ** no matter what happen, we return the same error that would
     644  457              ** be returned if the css file hadn't of existed.
     645  458              ** That way there in no information leaked.
     646  459              */
     647  460              catch (java.net.URISyntaxException e) {
     648  461                  return null;
     649  462              }
     650  463              catch (java.security.PrivilegedActionException e) {
     651  464                  return null;
     652  465              }
     653  466         }
     654  467      }
     655  468
     656  469
     657  470      private static Stylesheet loadStylesheetUnPrivileged(final String fname) {
     658  471
     659  472          Boolean parse = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
     660  473              @Override public Boolean run() {
     661  474
     662  475                  final String bss = System.getProperty("binary.css");
     663  476                  // binary.css is true by default.
     664  477                  // parse only if the file is not a .bss
     665  478                  // and binary.css is set to false
     666  479                  return (!fname.endsWith(".bss") && bss != null) ?
     667  480                      !Boolean.valueOf(bss) : Boolean.FALSE;
     668  481              }
     669  482          });
     670  483
     671  484          try {
     672  485              final String ext = (parse) ? (".css") : (".bss");
     673  486
     674  487              final String name =
     675  488                  (fname.endsWith(".css") || fname.endsWith(".bss")) ?
     676  489                      fname.substring(0, fname.length() - 4) : fname;
     677  490
     678  491              java.net.URL url = getURL(name+ext);
     679  492              if (url == null && (parse = !parse)) {
     680  493                  // If we failed to get the URL for the .bss file,
     681  494                  // fall back to the .css file.
     682  495                  // Note that 'parse' is toggled in the test.
     683  496                  url = getURL(name+".css");
     684  497              }
     685  498
     686  499              Stylesheet stylesheet = null;
     687  500              if ((url != null) && !parse) {
     688  501                  stylesheet = Stylesheet.loadBinary(url);
     689  502
     690  503                  if (stylesheet == null && (parse = !parse)) {
     691  504                      // If we failed to load the .bss file,
     692  505                      // fall back to the .css file.
     693  506                      // Note that 'parse' is toggled in the test.
     694  507                      url = getURL(name+".css");
     695  508                  }
     696  509              }
     697  510
     698  511              // either we failed to load the .bss file, or parse
     699  512              // was set to true.
     700  513              if ((url != null) && parse) {
     701  514                  stylesheet = CSSParser.getInstance().parse(url);

↓ open down ↓ | 161 lines elided | ↑ open up ↑

     702  515              }
     703  516
     704  517              if (stylesheet == null) {
     705  518                  if (errors != null) {
     706  519                      CssError error =
     707  520                          new CssError(
     708  521                              "Resource \""+fname+"\" not found."
     709  522                          );
     710  523                      errors.add(error);
     711  524                  }
     712      -                if (logger().isLoggable(PlatformLogger.WARNING)) {
     713      -                    logger().warning(
          525 +                if (LOGGER.isLoggable(PlatformLogger.WARNING)) {
          526 +                    LOGGER.warning(
     714  527                          String.format("Resource \"%s\" not found.", fname)
     715  528                      );
     716  529                  }
     717  530              }
     718  531              return stylesheet;
     719  532
     720  533          } catch (FileNotFoundException fnfe) {
     721  534              if (errors != null) {
     722  535                  CssError error =
     723  536                      new CssError(
     724  537                          "Stylesheet \""+fname+"\" not found."
     725  538                      );
     726  539                  errors.add(error);
     727  540              }
     728      -            if (logger().isLoggable(PlatformLogger.INFO)) {
     729      -                logger().info("Could not find stylesheet: " + fname);//, fnfe);
          541 +            if (LOGGER.isLoggable(PlatformLogger.INFO)) {
          542 +                LOGGER.info("Could not find stylesheet: " + fname);//, fnfe);
     730  543              }
     731  544          } catch (IOException ioe) {
     732  545                  if (errors != null) {
     733  546                      CssError error =
     734  547                          new CssError(
     735  548                              "Could not load stylesheet: " + fname
     736  549                          );
     737  550                      errors.add(error);
     738  551                  }
     739      -            if (logger().isLoggable(PlatformLogger.INFO)) {
     740      -                logger().info("Could not load stylesheet: " + fname);//, ioe);
          552 +            if (LOGGER.isLoggable(PlatformLogger.INFO)) {
          553 +                LOGGER.info("Could not load stylesheet: " + fname);//, ioe);
     741  554              }
     742  555          }
     743  556          return null;
     744  557      }
     745  558
     746  559      ////////////////////////////////////////////////////////////////////////////
     747  560      //
     748  561      // User Agent stylesheet handling
     749  562      //
     750  563      ////////////////////////////////////////////////////////////////////////////

     751  564
     752  565      /**
     753  566       * Add a user agent stylesheet, possibly overriding styles in the default
     754  567       * user agent stylesheet.
     755  568       *
     756  569       * @param fname The file URL, either relative or absolute, as a String.
     757  570       */
     758  571      public static void addUserAgentStylesheet(String fname) {
     759  572          addUserAgentStylesheet(null, fname);
     760  573      }
     761  574
     762  575      /**
     763  576       * Add a user agent stylesheet, possibly overriding styles in the default
     764  577       * user agent stylesheet.
     765  578       * @param scene Only used in CssError for tracking back to the scene that loaded the stylesheet
     766  579       * @param fname  The file URL, either relative or absolute, as a String.
     767  580       */
     768  581      // For RT-20643
     769  582      public static void addUserAgentStylesheet(Scene scene, String fname) {
     770  583
     771  584          // nothing to add
     772  585          if (fname == null ||  fname.trim().isEmpty()) return;
     773  586
     774  587          if (userAgentStylesheetMap.containsKey(fname) == false) {
     775  588
     776  589              // RT-20643
     777  590              CssError.setCurrentScene(scene);
     778  591
     779  592              Stylesheet ua_stylesheet = loadStylesheet(fname);
     780  593              ua_stylesheet.setOrigin(Stylesheet.Origin.USER_AGENT);
     781  594              userAgentStylesheetMap.put(fname, ua_stylesheet);
     782  595
     783  596              if (ua_stylesheet != null) {
     784  597                  userAgentStylesheetsChanged();
     785  598              }
     786  599
     787  600              // RT-20643
     788  601              CssError.setCurrentScene(null);
     789  602          }
     790  603
     791  604      }
     792  605
     793  606      /**
     794  607       * Set the default user agent stylesheet.
     795  608       *
     796  609       * @param fname The file URL, either relative or absolute, as a String.
     797  610       */
     798  611      public static void setDefaultUserAgentStylesheet(String fname) {
     799  612          setDefaultUserAgentStylesheet(null, fname);
     800  613      }
     801  614
     802  615      /**
     803  616       * Set the default user agent stylesheet
     804  617       * @param scene Only used in CssError for tracking back to the scene that loaded the stylesheet
     805  618       * @param fname  The file URL, either relative or absolute, as a String.
     806  619       */
     807  620      // For RT-20643
     808  621      public static void setDefaultUserAgentStylesheet(Scene scene, String fname) {
     809  622
     810  623          if (fname == null || fname.trim().isEmpty())
     811  624              throw new IllegalArgumentException("null arg fname");
     812  625
     813  626          // RT-20643
     814  627          CssError.setCurrentScene(scene);
     815  628
     816  629          Stylesheet ua_stylesheet = loadStylesheet(fname);
     817  630          if (ua_stylesheet != null) {
     818  631              ua_stylesheet.setOrigin(Stylesheet.Origin.USER_AGENT);
     819  632              setDefaultUserAgentStylesheet(ua_stylesheet);
     820  633          }
     821  634
     822  635          // RT-20643
     823  636          CssError.setCurrentScene(null);
     824  637
     825  638      }
     826  639
     827  640      /**
     828  641       * Set the user agent stylesheet. This is the base default stylesheet for
     829  642       * the platform
     830  643       */
     831  644      public static void setDefaultUserAgentStylesheet(Stylesheet stylesheet) {
     832  645          defaultUserAgentStylesheet = stylesheet;
     833  646          if (defaultUserAgentStylesheet != null) {
     834  647              defaultUserAgentStylesheet.setOrigin(Stylesheet.Origin.USER_AGENT);
     835  648          }
     836  649          userAgentStylesheetsChanged();
     837  650      }
     838  651
     839  652      /*
     840  653       * If the userAgentStylesheets change, then all scenes are updated.
     841  654       */
     842  655      private static void userAgentStylesheetsChanged() {
     843  656
     844  657          final Iterator<Window> windows = Window.impl_getWindows();
     845  658          while (windows.hasNext()) {
     846  659              final Window window = windows.next();
     847  660              final Scene scene = window.getScene();
     848  661              if (scene == null) continue;

↓ open down ↓ | 98 lines elided | ↑ open up ↑

     849  662
     850  663              final Reference<StyleManager> styleManagerRef = managerReferenceMap.get(scene);
     851  664              final StyleManager styleManager = styleManagerRef.get();
     852  665              if (styleManager == null) continue;
     853  666
     854  667              styleManager.updateStylesheets();
     855  668              scene.getRoot().impl_reapplyCSS();
     856  669          }
     857  670
     858  671      }
     859      -
     860      -    ////////////////////////////////////////////////////////////////////////////
     861      -    //
     862      -    // Author stylesheet handling
     863      -    //
     864      -    ////////////////////////////////////////////////////////////////////////////
     865      -
     866      -    /**
     867      -     * Set a stylesheet for the scene from the given stylesheet string. This
     868      -     * stylesheet comes after user agent stylesheets but before author
     869      -     * stylesheets and applies only to this scene.
     870      -     */
     871      -    public static void setInlineStylesheet(Scene scene, Stylesheet stylesheet) {
     872      -
     873      -        if (scene == null) return;
     874      -
     875      -        StyleManager styleManager = StyleManager.getStyleManager(scene);
     876      -        if (styleManager != null) {
     877      -            styleManager.inlineStylesheet = stylesheet;
     878      -            styleManager.updateStylesheets();
     879      -        }
     880      -
     881      -    }
     882      -    /**
     883      -     * Creates stylesheets and associates them with the given scene.
     884      -     *
     885      -     * Called from Scene.stylesheets on replace. Attempts to reuse existing
     886      -     * stylesheets for duplicate urls (ie: if two stages are loaded with the same
     887      -     * stylesheet urls, then the same stylesheet instance will be reused).
     888      -     *
     889      -     * All applied styles and caches are deleted, and complete clean application
     890      -     * of styles is started with new stylesheets.
     891      -     *
     892      -     * @param scene     The scene to update stylesheets for
     893      -     */
     894      -    public void updateStylesheets() {
     895      -
     896      -        // RT-7788
     897      -        // If there is no stage, then we're dealing with something like a popup
     898      -        // or a scene that hasn't been added to a stage yet. While there may
     899      -        // be a stylesheet for this scene, it is possible that the user agent
     900      -        // stylesheet will get set some time after this call to updateStylesheets
     901      -        // and the containerMap will get reset. A scene with no stage won't get added
     902      -        // back into the containerMap unless its stylesheet changes (which
     903      -        // is how it got there in the first place
     904      -        if (scene.getWindow() == null) return;
     905      -
     906      -        //
     907      -        // Here we remove the Scene's container from the container map.
     908      -        // This ensures that the Scene's nodes will either get their
     909      -        // styles from the default container (if Scene.stylesheets is empty)
     910      -        // or that a new cachMap will be computed for the nodes from the
     911      -        // new set of styles.
     912      -        //
     913      -        // This should happen infrequently since adding and removing stylesheets
     914      -        // on the fly should be the exception rather than the rule.
     915      -        //
     916      -        // annihilate the cache
     917      -        referencedStylesheets.clear();
     918      -        clearCache();
     919      -
     920      -        // RT-20643
     921      -        CssError.setCurrentScene(scene);
     922      -
     923      -        if (defaultUserAgentStylesheet != null) {
     924      -            referencedStylesheets.add(defaultUserAgentStylesheet);
     925      -        }
     926      -
     927      -        if (userAgentStylesheetMap.isEmpty() == false) {
     928      -            // TODO: This isn't right. They should be in the same order that
     929      -            // they were first added.
     930      -            referencedStylesheets.addAll(userAgentStylesheetMap.values());
     931      -        }
     932      -
     933      -        // create the stylesheets, one per URL supplied
     934      -        List<String> stylesheetURLs = rootScene == scene
     935      -                ? scene.getStylesheets()
     936      -                : rootScene.getStylesheets();
     937      -
     938      -        for (int i = 0; i < stylesheetURLs.size(); i++) {
     939      -
     940      -            String url = stylesheetURLs.get(i);
     941      -            url = url != null ? url.trim() : null;
     942      -            if (url == null || url.isEmpty()) continue;
     943      -
     944      -            // Has someone already parsed this for us?
     945      -            StylesheetContainer<Scene> container = sceneStylesheetMap.get(url);
     946      -            if (container != null) {
     947      -
     948      -                // let it be known that this Scene uses this stylesheet
     949      -                container.users.add(scene);
     950      -
     951      -                final Stylesheet stylesheet = container.stylesheet;
     952      -                if (stylesheet != null) {
     953      -                    referencedStylesheets.add(stylesheet);
     954      -                }
     955      -
     956      -            } else {
     957      -
     958      -                Stylesheet stylesheet = null;
     959      -                try {
     960      -
     961      -                    // RT-20643
     962      -                    CssError.setCurrentScene(scene);
     963      -
     964      -                    stylesheet = loadStylesheet(url);
     965      -
     966      -                    // RT-20643
     967      -                    CssError.setCurrentScene(null);
     968      -
     969      -                } catch (Exception e) {
     970      -                    // If an exception occurred while loading one of the stylesheets
     971      -                    // then we will simply print warning into system.err and skip the
     972      -                    // stylesheet, allowing other stylesheets to attempt to load
     973      -                    if (logger().isLoggable(PlatformLogger.WARNING)) {
     974      -                        logger().warning("Cannot add stylesheet. %s\n", e.getLocalizedMessage());
     975      -                    }
     976      -                    // no telling what condition stylesheet is in. make it null
     977      -                    stylesheet = null;
     978      -
     979      -                } finally {
     980      -
     981      -                    // stylesheet is added to the container even if it is null
     982      -                    // in order to prevent further attempts at parsing it.
     983      -                    container = new StylesheetContainer<Scene>(url, stylesheet);
     984      -                    container.users.add(scene);
     985      -                    if (stylesheet != null) {
     986      -                        referencedStylesheets.add(stylesheet);
     987      -                    }
     988      -
     989      -                }
     990      -            }
     991      -        }
     992      -
     993      -    }
     994      -
          672 +
     995  673      ////////////////////////////////////////////////////////////////////////////
     996  674      //
     997  675      // Pseudo-class state bit map handling
     998  676      //
     999  677      ////////////////////////////////////////////////////////////////////////////
    1000  678
    1001  679      private static final Map<String,Long> pseudoclassMasks = new HashMap<String,Long>();
    1002  680
    1003  681      public static long getPseudoclassMask(String pclass) {
    1004  682          Long mask = pseudoclassMasks.get(pclass);

    1005  683          if (mask == null) {
    1006  684              final int exp = pseudoclassMasks.size();
    1007  685              mask = Long.valueOf(1L << exp); // same as Math.pow(2,exp)
    1008  686              pseudoclassMasks.put(pclass,mask);
    1009  687          }
    1010  688          return mask.longValue();
    1011  689      }
    1012  690
    1013  691      static long getPseudoclassMask(List<String> pseudoclasses) {
    1014  692          long mask = 0;
    1015  693
    1016  694          final int max = pseudoclasses != null ? pseudoclasses.size() : -1;
    1017  695          for (int n=0; n<max; n++) {
    1018  696              long m = getPseudoclassMask(pseudoclasses.get(n));
    1019  697              mask = mask | m;
    1020  698          }
    1021  699          return mask;
    1022  700      }
    1023  701
    1024  702      public static List<String> getPseudoclassStrings(long mask) {
    1025  703          if (mask == 0) return Collections.EMPTY_LIST;
    1026  704
    1027  705          Map<Long,String> stringMap = new HashMap<Long,String>();
    1028  706          for (Entry<String,Long> entry : pseudoclassMasks.entrySet()) {
    1029  707              stringMap.put(entry.getValue(), entry.getKey());
    1030  708          }
    1031  709          List<String> strings = new ArrayList<String>();

↓ open down ↓ | 27 lines elided | ↑ open up ↑

    1032  710          for (long exp=0; exp < Long.SIZE; exp++) {
    1033  711              long key = (1L << exp) & mask;
    1034  712              if (key != 0) {
    1035  713                  String value = stringMap.get(key);
    1036  714                  if (value != null) strings.add(value);
    1037  715              }
    1038  716          }
    1039  717          return strings;
    1040  718      }
    1041  719
    1042      -    private void clearCache() {
          720 +    protected final void clearCache() {
    1043  721          styleCache.clear();
    1044  722          cacheMap.clear();
    1045  723          smapCount = 0;
    1046  724      }
    1047  725
    1048  726      private long nextSmapId() {
    1049  727          return ++smapCount;
    1050  728      }
    1051      -    //
    1052      -    // This int array represents the set of Parent's that have stylesheets.
    1053      -    // The indice is that of the Parent's hash code or -1 if
    1054      -    // the Parent has no stylesheet. See also the comments in the Key class.
    1055      -    // Note that the array is ordered from root at index zero to the
    1056      -    // leaf Node's parent at index length-1.
    1057      -    //
    1058      -
    1059      -    private static int[] getIndicesOfParentsWithStylesheets(Parent parent, int count) {
    1060      -        if (parent == null) {
    1061      -            return new int[count];
    1062      -        }
    1063      -        final int[] indices = getIndicesOfParentsWithStylesheets(parent.getParent(), ++count);
    1064      -        // logic elsewhere depends on indices of parents
    1065      -        // with no stylesheets being -1
    1066      -        if (parent.getStylesheets().isEmpty() == false) {
    1067      -            indices[indices.length - count] = parent.hashCode();
    1068      -        } else {
    1069      -            indices[indices.length - count] = -1;
    1070      -        }
    1071      -        return indices;
    1072      -    }
    1073  729
    1074      -    //
    1075      -    // recurse so that stylesheets of Parents closest to the root are
    1076      -    // added to the list first. The ensures that declarations for
    1077      -    // stylesheets further down the tree (closer to the leaf) have
    1078      -    // a higer ordinal in the cascade.
    1079      -    //
    1080      -    private List<StylesheetContainer<Parent>> gatherParentStylesheets(Parent parent) {
    1081      -
    1082      -        if (parent == null) {
    1083      -            return null;
    1084      -        }
    1085      -
    1086      -        final List<String> parentStylesheets = parent.impl_getAllParentStylesheets();
    1087      -
    1088      -        if (parentStylesheets == null || parentStylesheets.isEmpty()) {
    1089      -            return null;
    1090      -        }
    1091      -
    1092      -        final List<StylesheetContainer<Parent>> list = new ArrayList<StylesheetContainer<Parent>>();
    1093      -
    1094      -        // RT-20643
    1095      -        CssError.setCurrentScene(parent.getScene());
    1096      -
    1097      -        for (int n = 0, nMax = parentStylesheets.size(); n < nMax; n++) {
    1098      -            final String fname = parentStylesheets.get(n);
    1099      -            StylesheetContainer<Parent> container = null;
    1100      -            if (parentStylesheetMap.containsKey(fname)) {
    1101      -                container = parentStylesheetMap.get(fname);
    1102      -                // RT-22565: remember that this parent uses this stylesheet.
    1103      -                // Later, if the cache is cleared, the parent is told to
    1104      -                // reapply css.
    1105      -                container.users.add(parent);
    1106      -            } else {
    1107      -                final Stylesheet stylesheet = loadStylesheet(fname);
    1108      -                // stylesheet may be null which would mean that some IOException
    1109      -                // was thrown while trying to load it. Add it to the
    1110      -                // parentStylesheetMap anyway as this will prevent further
    1111      -                // attempts to parse the file
    1112      -                container =
    1113      -                        new StylesheetContainer<Parent>(fname, stylesheet);
    1114      -                // RT-22565: remember that this parent uses this stylesheet.
    1115      -                // Later, if the cache is cleared, the parent is told to
    1116      -                // reapply css.
    1117      -                container.users.add(parent);
    1118      -                parentStylesheetMap.put(fname, container);
    1119      -            }
    1120      -            if (container != null) {
    1121      -                list.add(container);
    1122      -            }
    1123      -        }
    1124      -        // RT-20643
    1125      -        CssError.setCurrentScene(null);
    1126      -
    1127      -        return list;
    1128      -    }
          730 +    abstract protected Key getKey(Node node);
    1129  731
    1130  732      /**
    1131  733       * Returns a StyleHelper for the given Node, or null if there are no styles
    1132  734       * for this node.
    1133  735       */
    1134      -    public StyleHelper getStyleHelper(Node node) {
    1135      -
    1136      -        // Populate our helper key with the class name, id, and style class
    1137      -        // of the node and lookup the associated Cache in the cacheMap
    1138      -        final String className = node.getClass().getName();
    1139      -        final String id = node.getId();
    1140      -        final List<String> styleClass = node.getStyleClass();
    1141      -
    1142      -        final int[] indicesOfParentsWithStylesheets =
    1143      -                getIndicesOfParentsWithStylesheets(
    1144      -                ((node instanceof Parent) ? (Parent) node : node.getParent()), 0);
    1145      -        //
    1146      -        // avoid calling gatherParentStylesheets later if we know there
    1147      -        // aren't any parent stylesheets. If there are parent stylesheets,
    1148      -        // then at least one element of indicesOfParentsWithStylesheets
    1149      -        // will be other than -1
    1150      -        //
    1151      -        boolean hasParentStylesheets = false;
    1152      -        for (int n = 0; n < indicesOfParentsWithStylesheets.length; n++) {
    1153      -            // assignment, not equality here!
    1154      -            if (hasParentStylesheets = (indicesOfParentsWithStylesheets[n] != -1)) {
    1155      -                break;
    1156      -            }
    1157      -        }
    1158      -
    1159      -        key.className = className;
    1160      -        key.id = id;
    1161      -        key.styleClass = styleClass;
    1162      -        key.indices = hasParentStylesheets ? indicesOfParentsWithStylesheets : null;
          736 +    public final StyleHelper getStyleHelper(Node node) {
    1163  737
          738 +        final Key key = getKey(node);
    1164  739          Cache cache = cacheMap.get(key);
    1165  740
    1166      -        // the key is an instance variable and so we need to null the
    1167      -        // key.styleClass to prevent holding a hard reference to the
    1168      -        // styleClass (and its Node)
    1169      -        key.styleClass = null;
    1170      -
    1171  741          // If the cache is null, then we need to create a new Cache and
    1172  742          // add it to the cache map
    1173  743          if (cache == null) {
    1174  744              // Construct the list of Rules that could possibly apply
    1175  745              final List<Rule> rules = new ArrayList<Rule>();
    1176  746
    1177  747              /*
    1178  748               * A Set of all the pseudoclass states which, if they change, need
    1179  749               * to cause the Node to be set to UPDATE its CSS styles on the next
    1180  750               * pulse. For a full explanation, see the StyleHelper's

    1181  751               * mayImpactChildren variable.
    1182  752               */
    1183  753              long pseudoclassStateMask = 0;

↓ open down ↓ | 3 lines elided | ↑ open up ↑

    1184  754
    1185  755              // If the node can have children, look at the ancestors
    1186  756              // in a compound selector for pseudoclassBits
    1187  757              final boolean canHaveChildren = node instanceof Parent;
    1188  758
    1189  759              // It may be that there are no pseudoclassBits in the ancestor
    1190  760              // selectors, but if one of the ancestor selectors might apply,
    1191  761              // then the selector impacts its children.
    1192  762              boolean impactsChildren = false;
    1193  763
    1194      -            final List<StylesheetContainer<Parent>> parentStylesheets =
    1195      -                    hasParentStylesheets
    1196      -                    ? gatherParentStylesheets(
    1197      -                    ((node instanceof Parent) ? (Parent) node : node.getParent()))
    1198      -                    : null;
    1199      -
    1200  764              //
    1201      -            List<Stylesheet> stylesheetsToProcess = null;
    1202      -
    1203      -            if (parentStylesheets == null || parentStylesheets.isEmpty()) {
    1204      -                stylesheetsToProcess = referencedStylesheets;
    1205      -            } else {
    1206      -
    1207      -                //
    1208      -                // if bypassing shared cache, then the node is a Parent
    1209      -                // with stylesheets or has a Parent with stylesheets.
    1210      -                // Gather up all of the Parent stylesheets for procesing.
    1211      -                //
    1212      -
    1213      -                // scene stylesheets come first since declarations from
    1214      -                // Parent stylesheets should take precedence.
    1215      -                stylesheetsToProcess = new ArrayList<Stylesheet>(parentStylesheets.size());
    1216      -                for (int n = 0, nMax = parentStylesheets.size(); n < nMax; n++) {
    1217      -                    final StylesheetContainer<Parent> psc = parentStylesheets.get(n);
    1218      -                    stylesheetsToProcess.add(psc.stylesheet);
    1219      -                }
    1220      -                stylesheetsToProcess.addAll(0, referencedStylesheets);
    1221      -            }
          765 +            final String className = node.getClass().getName();
          766 +            final String id = node.getId();
          767 +            final List<String> styleClass = node.getStyleClass();
          768 +
          769 +            List<Stylesheet> stylesheetsToProcess = referencedStylesheets;
    1222  770
    1223  771              for (int i = 0, imax = stylesheetsToProcess.size(); i < imax; i++) {
    1224  772                  final Stylesheet ss = stylesheetsToProcess.get(i);
    1225  773
    1226  774                  final List<Rule> stylesheetRules = ss != null ? ss.getRules() : null;
    1227  775                  if (stylesheetRules == null || stylesheetRules.isEmpty()) {
    1228  776                      continue;
    1229  777                  }
    1230  778
    1231  779                  for (int j = 0, jmax = stylesheetRules.size(); j < jmax; j++) {

    1232  780                      Rule rule = stylesheetRules.get(j);
    1233  781                      boolean mightApply = rule.mightApply(className, id, styleClass);
    1234  782                      if (mightApply) {
    1235  783                          rules.add(rule);
    1236  784                      }
    1237  785
    1238  786                      //
    1239  787                      // The following logic used to be in a separate routine,
    1240  788                      // but the need to flag compound selectors with no
    1241  789                      // pseudoclass state that might impact the node's
    1242  790                      // children encouraged moving it here.
    1243  791                      //
    1244  792
    1245  793                      // The following loop creates the pseudoclassStateMask.
    1246  794                      // For a leaf node, the loop will just gather the
    1247  795                      // pseudoclass state bits. For a parent node, this loop
    1248  796                      // looks at the ancestor selectors to see if the parent
    1249  797                      // matches. If so, then the pseudoclass state from that
    1250  798                      // parent will affect the styling of its child nodes.
    1251  799                      // For example, in the selector A:foo B { }, whenever the
    1252  800                      // "foo" pseudoclass of A changes it must notify child
    1253  801                      // nodes of the change so that if one of them happens to
    1254  802                      // be a B it can update itself accordingly. Since this
    1255  803                      // can be quite expensive, we want to make sure we
    1256  804                      // support this capability only for nodes that need it.
    1257  805                      // This function determines whether this support is
    1258  806                      // necessary (setting impactsChildren to true),
    1259  807                      // and what pseudoclasses on the parent (A) that matter.
    1260  808
    1261  809                      // The node has to have rules that apply or be a Parent
    1262  810                      // in order for the pseudoclass state to matter.
    1263  811                      if (!mightApply && !canHaveChildren) {
    1264  812                          continue;
    1265  813                      }
    1266  814
    1267  815                      // For each selector, rule, stylesheet look for whether this Node
    1268  816                      // is referenced in the ancestor part of the selector, and whether or
    1269  817                      // not it also has pseudoclasses specified
    1270  818                      final int smax = rule.selectors != null ? rule.selectors.size() : 0;
    1271  819                      for (int s = 0; s < smax; s++) {
    1272  820                          final Selector selector = rule.selectors.get(s);
    1273  821                          if (selector instanceof CompoundSelector) {
    1274  822
    1275  823                              final CompoundSelector cs = (CompoundSelector) selector;
    1276  824                              final List<SimpleSelector> csSelectors = cs.getSelectors();
    1277  825
    1278  826                              // if mightApply is true, then the right-most selector
    1279  827                              // was matched and we just need the pseudoclass state
    1280  828                              // from that selector.
    1281  829                              if (mightApply) {
    1282  830                                  final SimpleSelector simple =
    1283  831                                          csSelectors.get(csSelectors.size() - 1);
    1284  832                                  pseudoclassStateMask = pseudoclassStateMask
    1285  833                                          | getPseudoclassMask(simple.getPseudoclasses());
    1286  834
    1287  835
    1288  836                                  // Otherwise, the rule did not match but we need to check
    1289  837                                  // to see if this node matches one of the ancestor selectors.
    1290  838                                  // This only matters for nodes that can have children. You
    1291  839                                  // wouldn't find a Rectangle, for example, as an ancestor
    1292  840                                  // in a compound selector (you might, but the selector
    1293  841                                  // wouldn't match anything)
    1294  842                              } else if (canHaveChildren) {
    1295  843                                  // only check the ancestor selectors. If we are here,
    1296  844                                  // then we know mightApply is false, meaning the rule
    1297  845                                  // does not apply to this node because this node does
    1298  846                                  // not match the right-most selector.
    1299  847                                  for (int sctr = 0, max_sctr = csSelectors.size() - 1; sctr < max_sctr; sctr++) {
    1300  848                                      final SimpleSelector simple = csSelectors.get(sctr);
    1301  849                                      if (simple.mightApply(className, id, styleClass)) {
    1302  850                                          pseudoclassStateMask = pseudoclassStateMask
    1303  851                                                  | getPseudoclassMask(simple.getPseudoclasses());
    1304  852                                          impactsChildren = true;
    1305  853                                      }
    1306  854                                  }
    1307  855                              }
    1308  856                              // Not a compound selector. If the selector might apply,
    1309  857                              // then save off the pseudoclass state. StyleHelper
    1310  858                              // checks this to see if a pseudoclass matters or not.
    1311  859                          } else if (mightApply) {
    1312  860                              SimpleSelector simple = (SimpleSelector) selector;
    1313  861                              pseudoclassStateMask = pseudoclassStateMask

↓ open down ↓ | 82 lines elided | ↑ open up ↑

    1314  862                                      | getPseudoclassMask(simple.getPseudoclasses());
    1315  863                          }
    1316  864                      }
    1317  865                  }
    1318  866              }
    1319  867
    1320  868              //
    1321  869              // regardless of whether or not the cache is shared, a Cache
    1322  870              // object is still needed in order to do the lookup.
    1323  871              //
    1324      -            final Key newKey = new Key();
    1325      -            newKey.className = className;
    1326      -            newKey.id = id;
    1327      -            // Copy the list.
    1328      -            // If the contents of the Node's styleClass changes,
    1329      -            // the cacheMap lookup should miss.
    1330      -            final int nElements = styleClass.size();
    1331      -            newKey.styleClass = new ArrayList<String>(nElements);
    1332      -            for (int n = 0; n < nElements; n++) {
    1333      -                newKey.styleClass.add(styleClass.get(n));
    1334      -            }
    1335      -            newKey.indices = hasParentStylesheets ? indicesOfParentsWithStylesheets : null;
          872 +            final Key newKey = key.dup();
    1336  873
    1337  874              cache = new Cache(rules, pseudoclassStateMask, impactsChildren);
    1338  875              cacheMap.put(newKey, cache);
    1339  876
    1340      -            // RT-22565: remember where this cache is used if there are
    1341      -            // parent stylesheets involve so the cache can be cleared later
    1342      -            // from the parentStylesheetsChanged method.
    1343      -            final int nMax = parentStylesheets != null ? parentStylesheets.size() : 0;
    1344      -            for (int n = 0; n < nMax; n++) {
    1345      -                final StylesheetContainer<Parent> psc = parentStylesheets.get(n);
    1346      -                psc.keys.add(newKey);
    1347      -            }
    1348      -
    1349  877          }
          878 +
          879 +        // the key is an instance variable and so we need to null the
          880 +        // key.styleClass to prevent holding a hard reference to the
          881 +        // styleClass (and its Node)
          882 +        key.styleClass = null;
          883 +
    1350  884          // Return the style helper looked up by the cache. The cache will
    1351  885          // create a style helper if necessary (and possible), so we don't
    1352  886          // have to worry about that part.
    1353  887          StyleMap smap = cache.getStyleMap(this, node);
    1354  888          if (smap == null) {
    1355  889              return null;
    1356  890          }
    1357  891
    1358  892          StyleHelper helper =
    1359  893                  StyleHelper.create(
    1360  894                  node,
    1361  895                  smap.map,
    1362  896                  styleCache,
    1363  897                  cache.pseudoclassStateMask,
    1364  898                  smap.uniqueId);
          899 +
    1365  900          return helper;
    1366  901      }
    1367  902
    1368  903      ////////////////////////////////////////////////////////////////////////////
    1369  904      //
    1370  905      // CssError reporting
    1371  906      //
    1372  907      ////////////////////////////////////////////////////////////////////////////
    1373  908
    1374  909      private static ObservableList<CssError> errors = null;

    1375  910      /**
    1376  911       * Errors that may have occurred during css processing.
    1377  912       * This list is null until errorsProperty() is called.
    1378  913       * @return
    1379  914       */
    1380  915      public static ObservableList<CssError> errorsProperty() {
    1381  916          if (errors == null) {
    1382  917              errors = FXCollections.observableArrayList();
    1383  918          }
    1384  919          return errors;
    1385  920      }
    1386  921
    1387  922      /**

↓ open down ↓ | 13 lines elided | ↑ open up ↑

    1388  923       * Errors that may have occurred during css processing.
    1389  924       * This list is null until errorsProperty() is called and is used
    1390  925       * internally to figure out whether or  not anyone is interested in
    1391  926       * receiving CssError.
    1392  927       * Not meant for general use - call errorsProperty() instead.
    1393  928       * @return
    1394  929       */
    1395  930      public static ObservableList<CssError> getErrors() {
    1396  931          return errors;
    1397  932      }
    1398      -
          933 +
    1399  934      ////////////////////////////////////////////////////////////////////////////
    1400  935      //
    1401  936      // Inner classes
    1402  937      //
    1403  938      ////////////////////////////////////////////////////////////////////////////
    1404  939
    1405  940      //
    1406  941      // Used by StyleHelper. The key uniquely identifies this style map.
    1407  942      // These keys are used by StyleHelper in creating its StyleCacheKey.
    1408  943      // The StyleCacheKey is used to lookup calculated values in cache.

    1409  944      //
    1410  945      static class StyleMap {
    1411  946          final long uniqueId; // unique per container
    1412  947          final Map<String, List<CascadingStyle>> map;

↓ open down ↓ | 4 lines elided | ↑ open up ↑

    1413  948          private StyleMap(long key, Map<String, List<CascadingStyle>> map) {
    1414  949              this.uniqueId = key;
    1415  950              this.map  = map;
    1416  951          }
    1417  952
    1418  953      }
    1419  954
    1420  955      /**
    1421  956       * Creates and caches maps of styles, reusing them as often as practical.
    1422  957       */
    1423      -    private static class Cache {
          958 +    protected static class Cache {
    1424  959
    1425  960          // this must be initialized to the appropriate possible rules when
    1426  961          // the helper cache is created by the StylesheetContainer
    1427  962          private final List<Rule> rules;
    1428  963          private final long pseudoclassStateMask;
    1429  964          private final boolean impactsChildren;
    1430  965          private final Map<Long, StyleMap> cache;
    1431  966
    1432  967          Cache(List<Rule> rules, long pseudoclassStateMask, boolean impactsChildren) {
    1433  968              this.rules = rules;

    1434  969              this.pseudoclassStateMask = pseudoclassStateMask;
    1435  970              this.impactsChildren = impactsChildren;
    1436  971              this.cache = new HashMap<Long, StyleMap>();
    1437  972          }
    1438  973
    1439  974          private StyleMap getStyleMap(StyleManager owner, Node node) {
    1440  975
    1441  976              // If this set of rules (which may be empty) impacts children,
    1442  977              // then this node will get a StyleHelper.
    1443  978              if (!impactsChildren) {
    1444  979
    1445  980                  // Since this set of rules does not impact children, this node
    1446  981                  // will only get a StyleHelper if there are styles that might
    1447  982                  // apply. There are styles that might apply if there are rules
    1448  983                  // or if the node has an in-line style.
    1449  984                  final String nodeStyle = node.getStyle();
    1450  985                  final boolean hasStyle = nodeStyle != null && !nodeStyle.isEmpty();
    1451  986
    1452  987                  if (rules.isEmpty() && pseudoclassStateMask == 0 && hasStyle == false) {
    1453  988                      boolean hasInheritAsDefault = false;
    1454  989                      // TODO This is questionable as what happens when a node has no style helper and inherits styles
    1455  990                      final List<StyleableProperty> styleables = node.impl_getStyleableProperties();
    1456  991                      final int max = styleables != null ? styleables.size() : 0;
    1457  992                      for (int i = 0; i < max; i++) {
    1458  993                          if (styleables.get(i).isInherits()) {
    1459  994                              hasInheritAsDefault = true;
    1460  995                              break;
    1461  996                          }
    1462  997                      }
    1463  998                      // if we have no rules and no inherited properties we don't need a StyleHelper
    1464  999                      if (! hasInheritAsDefault) return null;
    1465 1000                  }
    1466 1001              }
    1467 1002
    1468 1003              // To lookup from the cache, we construct a key from a Long
    1469 1004              // where the rules that apply to this particular node are
    1470 1005              // represented by bits on the Long.
    1471 1006              long key = 0;
    1472 1007              long mask = 1;
    1473 1008              for (int i = 0, imax = rules.size(); i < imax; i++) {
    1474 1009                  Rule rule = rules.get(i);
    1475 1010                  if (rule.applies(node)) {
    1476 1011                      key = key | mask;
    1477 1012                  }
    1478 1013                  mask = mask << 1;
    1479 1014              }
    1480 1015
    1481 1016              final Long keyObj = Long.valueOf(key);
    1482 1017              if (cache.containsKey(keyObj)) {
    1483 1018                  final StyleMap styleMap = cache.get(keyObj);
    1484 1019                  return styleMap;
    1485 1020              }
    1486 1021
    1487 1022              // We need to create a new StyleHelper, add it to the cache,
    1488 1023              // and then return it.
    1489 1024              final List<CascadingStyle> styles = getStyles(node);
    1490 1025              final Map<String, List<CascadingStyle>> smap =
    1491 1026                  new HashMap<String, List<CascadingStyle>>();
    1492 1027              final int max = styles != null ? styles.size() : 0;
    1493 1028              for (int i=0; i<max; i++) {
    1494 1029                  final CascadingStyle style = styles.get(i);
    1495 1030                  final String property = style.getProperty();
    1496 1031                  // This is carefully written to use the minimal amount of hashing.
    1497 1032                  List<CascadingStyle> list = smap.get(property);
    1498 1033                  if (list == null) {
    1499 1034                      list = new ArrayList<CascadingStyle>(5);
    1500 1035                      smap.put(property, list);
    1501 1036                  }
    1502 1037                  list.add(style);
    1503 1038              }
    1504 1039
    1505 1040              final StyleMap styleMap = new StyleMap(owner.nextSmapId(), smap);
    1506 1041              cache.put(keyObj, styleMap);
    1507 1042              return styleMap;
    1508 1043          }
    1509 1044
    1510 1045          /**
    1511 1046           * Looks up all the styles for this Node. This function takes no advantage
    1512 1047           * of any caches but simply does a complete fresh lookup. The styles
    1513 1048           * returned are sorted such that the most specific style is first.
    1514 1049           */
    1515 1050          private List<CascadingStyle> getStyles(Node node) {
    1516 1051              // The priority of stylesheets from the CSS spec is
    1517 1052              // (1) user agent declarations
    1518 1053              // (2) user normal declarations
    1519 1054              // (3) author normal declarations
    1520 1055              // (4) author important declarations
    1521 1056              // (5) user important declarations
    1522 1057              // we have the correct order here for 1,2,3 and the 4 & 5
    1523 1058              // we handle when sorting the styles
    1524 1059
    1525 1060              if (rules == null || rules.isEmpty()) return null;
    1526 1061
    1527 1062              List<CascadingStyle> styles = new ArrayList<CascadingStyle>(rules.size());
    1528 1063                  // Order in which the declaration of a Style appears (see below)
    1529 1064                  int ordinal = 0;
    1530 1065                  // For each declaration in the matching rules, create a Style object
    1531 1066                  //final List<Style> styles = new ArrayList<Style>();
    1532 1067                  for (int i = 0, imax = rules.size(); i < imax; i++) {
    1533 1068                      final Rule rule = rules.get(i);
    1534 1069                      List<Match> matches = rule.matches(node);
    1535 1070                      for (int j = 0, jmax = matches.size(); j < jmax; j++) {
    1536 1071                          final Match match = matches.get(j);
    1537 1072                          if (match == null) continue;
    1538 1073                          for (int k = 0, kmax = rule.declarations.size(); k < kmax; k++) {
    1539 1074                              final Declaration decl = rule.declarations.get(k);
    1540 1075
    1541 1076                              final CascadingStyle s = new CascadingStyle(
    1542 1077                                  new Style(match.selector, decl),
    1543 1078                                  match.pseudoclasses,
    1544 1079                                  match.specificity,
    1545 1080                                  // ordinal increments at declaration level since
    1546 1081                                  // there may be more than one declaration for the
    1547 1082                                  // same attribute within a rule or within a stylesheet
    1548 1083                                  ordinal++
    1549 1084                              );
    1550 1085
    1551 1086                          styles.add(s);
    1552 1087                          }
    1553 1088                      }
    1554 1089                  }
    1555 1090

↓ open down ↓ | 122 lines elided | ↑ open up ↑

    1556 1091                  // return sorted styles
    1557 1092              Collections.sort(styles);
    1558 1093              return styles;
    1559 1094              }
    1560 1095
    1561 1096          }
    1562 1097
    1563 1098      /**
    1564 1099       * The key used in the cacheMap of the StylesheetContainer
    1565 1100       */
    1566      -    private static class Key {
         1101 +    static class Key {
         1102 +
         1103 +        public Key() {
         1104 +        }
         1105 +
         1106 +        protected Key dup() {
         1107 +
         1108 +            Key key = new Key();
         1109 +            key.className  = className;
         1110 +            key.id = id;
         1111 +            final int nElements = styleClass.size();
         1112 +            key.styleClass = new ArrayList<String>(nElements);
         1113 +            for (int n = 0; n < nElements; n++) {
         1114 +                key.styleClass.add(styleClass.get(n));
         1115 +            }
         1116 +
         1117 +            return key;
         1118 +        }
         1119 +
    1567 1120          // note that the class name here is the *full* class name, such as
    1568 1121          // javafx.scene.control.Button. We only bother parsing this down to the
    1569 1122          // last part when doing matching against selectors, and so want to avoid
    1570 1123          // having to do a bunch of preliminary parsing in places where it isn't
    1571 1124          // necessary.
    1572      -        String className;
    1573      -        String id;
    1574      -        List<String> styleClass;
    1575      -
    1576      -        // this will be initialized if a Parent has a stylesheet and will
    1577      -        // hold the indices of those Parents with stylesheets (the Parent's
    1578      -        // hash code is used). If the parent does not have a stylesheet, the
    1579      -        // indice will be -1. When finding the equals Key in the cache lookup,
    1580      -        // we only need to check up to the last parent that has a stylesheet.
    1581      -        // In other words, if one node is at level n and nearest parent with
    1582      -        // a stylesheet is at level m, then the indices match provided
    1583      -        // indices[x] == other.indices[x] for 0 <= x <= m and all other
    1584      -        // indices are -1. Thus, [1, -1, 2] and [1, -1, 2, -1, -1] are equivalent
    1585      -        // whereas [1, -1, 2] and [1, -1, 2, -1, 3] are not.
    1586      -        int[] indices;
         1125 +        protected String className;
         1126 +        protected String id;
         1127 +        protected List<String> styleClass;
    1587 1128
    1588 1129          @Override
    1589 1130          public boolean equals(Object o) {
    1590 1131              if (this == o) return true;
    1591 1132              if (o instanceof Key) {
    1592 1133                  Key other = (Key)o;
    1593 1134                  boolean eq =
    1594 1135                          className.equals(other.className)
    1595      -                    && (   (indices == null && other.indices == null)
    1596      -                        || (indices != null && other.indices != null)
    1597      -                       )
    1598 1136                      && (   (id == null && other.id == null)
    1599 1137                          || (id != null && id.equals(other.id))
    1600 1138                         )
    1601 1139                      && (   (styleClass == null && other.styleClass == null)
    1602 1140                          || (styleClass != null && styleClass.containsAll(other.styleClass))
    1603 1141                         );
    1604      -
    1605      -                if (eq && indices != null) {
    1606      -                    final int max = Math.min(indices.length, other.indices.length);
    1607      -                    for (int x = 0; eq && (x < max); x++) {
    1608      -                        eq = indices[x] == other.indices[x];
    1609      -                    }
    1610      -                    if (eq) {
    1611      -                        // ensure the remainder are -1
    1612      -                        for(int x=max; eq && x<indices.length; x++) {
    1613      -                            eq = indices[x] == -1;
    1614      -                        }
    1615      -                        for(int x=max; eq && x<other.indices.length; x++) {
    1616      -                            eq = other.indices[x] == -1;
    1617      -                        }
    1618      -                    }
    1619      -                }
    1620 1142                  return eq;
    1621 1143              }
    1622 1144              return false;
    1623 1145          }
    1624 1146
    1625 1147          @Override
    1626 1148          public int hashCode() {
    1627 1149              int hash = className.hashCode();
    1628 1150              hash = 31 * (hash + ((id == null || id.isEmpty()) ? 1231 : id.hashCode()));
    1629 1151              hash = 31 * (hash + ((styleClass == null || styleClass.isEmpty()) ? 1237 : styleClass.hashCode()));
    1630      -            if (indices != null) hash = 31 * (hash + Arrays.hashCode(indices));
    1631 1152              return hash;
    1632 1153          }
    1633 1154
    1634 1155          @Override
    1635 1156          public String toString() {
    1636 1157              return "Key ["+className+", "+String.valueOf(id)+", "+String.valueOf(styleClass)+"]";
    1637 1158          }
    1638 1159
    1639 1160      }
    1640 1161
    1641 1162  }

    XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX