1 /*
   2  * Copyright (c) 2011, 2018, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package javafx.scene;
  26 
  27 import java.util.ArrayList;
  28 import java.util.Collections;
  29 import java.util.HashMap;
  30 import java.util.HashSet;
  31 import java.util.List;
  32 import java.util.Locale;
  33 import java.util.Map;
  34 import java.util.Map.Entry;
  35 import java.util.Set;
  36 
  37 import javafx.beans.property.ObjectProperty;
  38 import javafx.beans.property.SimpleObjectProperty;
  39 import javafx.beans.value.WritableValue;
  40 import com.sun.javafx.css.CascadingStyle;
  41 import javafx.css.CssMetaData;
  42 import javafx.css.CssParser;
  43 import javafx.css.FontCssMetaData;
  44 import javafx.css.ParsedValue;
  45 import javafx.css.PseudoClass;
  46 import javafx.css.Rule;
  47 import javafx.css.Selector;
  48 import javafx.css.Style;
  49 import javafx.css.StyleConverter;
  50 import javafx.css.StyleOrigin;
  51 import javafx.css.Styleable;
  52 import javafx.css.StyleableProperty;
  53 import javafx.css.Stylesheet;
  54 import javafx.scene.text.Font;
  55 import javafx.scene.text.FontPosture;
  56 import javafx.scene.text.FontWeight;
  57 
  58 import com.sun.javafx.css.CalculatedValue;
  59 import com.sun.javafx.css.ParsedValueImpl;
  60 import com.sun.javafx.css.PseudoClassState;
  61 import com.sun.javafx.css.StyleCache;
  62 import com.sun.javafx.css.StyleCacheEntry;
  63 import com.sun.javafx.css.StyleManager;
  64 import com.sun.javafx.css.StyleMap;
  65 import javafx.css.converter.FontConverter;
  66 import com.sun.javafx.util.Logging;
  67 import com.sun.javafx.util.Utils;
  68 
  69 import com.sun.javafx.logging.PlatformLogger;
  70 import com.sun.javafx.logging.PlatformLogger.Level;
  71 
  72 import static com.sun.javafx.css.CalculatedValue.*;
  73 
  74 /**
  75  * The StyleHelper is a helper class used for applying CSS information to Nodes.
  76  */
  77 final class CssStyleHelper {
  78 
  79     private static final PlatformLogger LOGGER = com.sun.javafx.util.Logging.getCSSLogger();
  80 
  81     private CssStyleHelper() {
  82         this.triggerStates = new PseudoClassState();
  83     }
  84 
  85     /**
  86      * Creates a new StyleHelper.
  87      */
  88     static CssStyleHelper createStyleHelper(final Node node) {
  89 
  90         // need to know how far we are to root in order to init arrays.
  91         // TODO: should we hang onto depth to avoid this nonsense later?
  92         // TODO: is there some other way of knowing how far from the root a node is?
  93         Styleable parent = node;
  94         int depth = 0;
  95         while(parent != null) {
  96             depth++;
  97             parent = parent.getStyleableParent();
  98         }
  99 
 100         // The List<CacheEntry> should only contain entries for those
 101         // pseudo-class states that have styles. The StyleHelper's
 102         // pseudoclassStateMask is a bitmask of those pseudoclasses that
 103         // appear in the node's StyleHelper's smap. This list of
 104         // pseudo-class masks is held by the StyleCacheKey. When a node is
 105         // styled, its pseudoclasses and the pseudoclasses of its parents
 106         // are gotten. By comparing the actual pseudo-class state to the
 107         // pseudo-class states that apply, a CacheEntry can be created or
 108         // fetched using only those pseudoclasses that matter.
 109         final PseudoClassState[] triggerStates = new PseudoClassState[depth];
 110 
 111         final StyleMap styleMap =
 112                 StyleManager.getInstance().findMatchingStyles(node, node.getSubScene(), triggerStates);
 113 
 114         //
 115         // reuse the existing styleHelper if possible.
 116         //
 117         if ( canReuseStyleHelper(node, styleMap) ) {
 118 
 119             //
 120             // RT-33080
 121             //
 122             // If we're reusing a style helper, clear the fontSizeCache in case either this node or some parent
 123             // node has changed font from a user calling setFont.
 124             //
 125             // It may be the case that the node's font has changed from a call to setFont, which will
 126             // trigger a REAPPLY. If the REAPPLY comes because of a change in font, then the fontSizeCache
 127             // needs to be invalidated (cleared) so that new values will be looked up for all transition states.
 128             //
 129             if (node.styleHelper.cacheContainer != null && node.styleHelper.isUserSetFont(node)) {
 130                 node.styleHelper.cacheContainer.fontSizeCache.clear();
 131             }
 132             node.styleHelper.cacheContainer.forceSlowpath = true;
 133             node.styleHelper.triggerStates.addAll(triggerStates[0]);
 134             node.styleHelper.firstStyleableAncestor = findFirstStyleableAncestor(node);
 135             updateParentTriggerStates(node, depth, triggerStates);
 136             return node.styleHelper;
 137 
 138         }
 139 
 140         if (styleMap == null || styleMap.isEmpty()) {
 141 
 142             boolean mightInherit = false;
 143 
 144             final List<CssMetaData<? extends Styleable, ?>> props = node.getCssMetaData();
 145 
 146             final int pMax = props != null ? props.size() : 0;
 147             for (int p=0; p<pMax; p++) {
 148 
 149                 final CssMetaData<? extends Styleable, ?> prop = props.get(p);
 150                 if (prop.isInherits()) {
 151                     mightInherit = true;
 152                     break;
 153                 }
 154             }
 155 
 156             if (mightInherit == false) {
 157 
 158                 // If this node had a style helper, then reset properties to their initial value
 159                 // since the node won't have a style helper after this call
 160                 if (node.styleHelper != null) {
 161                     node.styleHelper.resetToInitialValues(node);
 162                 }
 163 
 164                 //
 165                 // This node didn't have a StyleHelper before and it doesn't need one now since there are
 166                 // no styles in the StyleMap and no inherited styles.
 167                 return null;
 168             }
 169 
 170         }
 171 
 172         final CssStyleHelper helper = new CssStyleHelper();
 173         helper.triggerStates.addAll(triggerStates[0]);
 174 
 175         updateParentTriggerStates(node, depth, triggerStates);
 176 
 177         helper.cacheContainer = new CacheContainer(node, styleMap, depth);
 178 
 179         helper.firstStyleableAncestor = findFirstStyleableAncestor(node);
 180 
 181         // If this node had a style helper, then reset properties to their initial value
 182         // since the style map might now be different
 183         if (node.styleHelper != null) {
 184             node.styleHelper.resetToInitialValues(node);
 185         }
 186         return helper;
 187     }
 188 
 189     private static void updateParentTriggerStates(Styleable styleable, int depth, PseudoClassState[] triggerStates) {
 190         // make sure parent's transition states include the pseudo-classes
 191         // found when matching selectors
 192         Styleable parent = styleable.getStyleableParent();
 193         for(int n=1; n<depth; n++) {
 194 
 195             // TODO: this means that a style like .menu-item:hover won't work. Need to separate CssStyleHelper tree from scene-graph tree
 196             if (parent instanceof Node == false) {
 197                 parent=parent.getStyleableParent();
 198                 continue;
 199             }
 200             Node parentNode = (Node)parent;
 201 
 202             final PseudoClassState triggerState = triggerStates[n];
 203 
 204             // if there is nothing in triggerState, then continue since there
 205             // isn't any pseudo-class state that might trigger a state change
 206             if (triggerState != null && triggerState.size() > 0) {
 207 
 208                 // Create a StyleHelper for the parent, if necessary.
 209                 // TODO : check why calling createStyleHelper(parentNode) does not work here?
 210                 if (parentNode.styleHelper == null) {
 211                     parentNode.styleHelper = new CssStyleHelper();
 212                     parentNode.styleHelper.firstStyleableAncestor = findFirstStyleableAncestor(parentNode) ;
 213                 }
 214                 parentNode.styleHelper.triggerStates.addAll(triggerState);
 215 
 216             }
 217 
 218             parent=parent.getStyleableParent();
 219         }
 220 
 221     }
 222     //
 223     // return true if the fontStyleableProperty's origin is USER
 224     //
 225     private boolean isUserSetFont(Styleable node) {
 226 
 227         if (node == null) return false; // should never happen, but just to be safe...
 228 
 229         CssMetaData<Styleable, Font> fontCssMetaData = cacheContainer != null ? cacheContainer.fontProp : null;
 230         if (fontCssMetaData != null) {
 231             StyleableProperty<Font> fontStyleableProperty = fontCssMetaData != null ? fontCssMetaData.getStyleableProperty(node) : null;
 232             if (fontStyleableProperty != null && fontStyleableProperty.getStyleOrigin() == StyleOrigin.USER) return true;
 233         }
 234 
 235         Styleable styleableParent = firstStyleableAncestor;
 236         CssStyleHelper parentStyleHelper = getStyleHelper(firstStyleableAncestor);
 237 
 238         if (parentStyleHelper != null) {
 239             return parentStyleHelper.isUserSetFont(styleableParent);
 240         } else {
 241             return false;
 242         }
 243     }
 244 
 245     private static CssStyleHelper getStyleHelper(Node n) {
 246         return (n != null)? n.styleHelper : null;
 247     }
 248 
 249     private static Node findFirstStyleableAncestor(Styleable st) {
 250         Node ancestor = null;
 251         Styleable parent = st.getStyleableParent();
 252         while (parent != null) {
 253             if (parent instanceof Node) {
 254                 if (((Node) parent).styleHelper != null) {
 255                     ancestor = (Node) parent;
 256                     break;
 257                 }
 258             }
 259             parent = parent.getStyleableParent();
 260         }
 261 
 262         return ancestor;
 263     }
 264 
 265     //
 266     // return the value of the property
 267     //
 268     private static boolean isTrue(WritableValue<Boolean> booleanProperty) {
 269         return booleanProperty != null && booleanProperty.getValue();
 270     }
 271 
 272     //
 273     // set the value of the property to true
 274     //
 275     private static void setTrue(WritableValue<Boolean> booleanProperty) {
 276         if (booleanProperty != null) booleanProperty.setValue(true);
 277     }
 278 
 279     //
 280     // return true if the Node's current styleHelper can be reused.
 281     //
 282     private static boolean canReuseStyleHelper(final Node node, final StyleMap styleMap) {
 283 
 284         // Obviously, we cannot reuse the node's style helper if it doesn't have one.
 285         if (node == null || node.styleHelper == null) {
 286             return false;
 287         }
 288 
 289         // If we have a styleHelper but the new styleMap is null, then we don't need a styleHelper at all
 290         if (styleMap == null) {
 291             return false;
 292         }
 293 
 294         StyleMap currentMap = node.styleHelper.getStyleMap(node);
 295 
 296         // We cannot reuse the style helper if the styleMap is not the same instance as the current one
 297         // Note: check instance equality!
 298         if (currentMap != styleMap) {
 299             return false;
 300         }
 301 
 302         // If the style maps are the same instance, we can re-use the current styleHelper if the cacheContainer is null.
 303         // Under this condition, there are no styles for this node _and_ no styles inherit.
 304         if (node.styleHelper.cacheContainer == null) {
 305             return true;
 306         }
 307 
 308         //
 309         // The current map might be the same, but one of the node's parent's maps might have changed which
 310         // might cause some calculated values to change. To see if we can re-use the style-helper, we need to
 311         // check if the StyleMap id's have changed, which we can do by inspecting the cacheContainer's styleCacheKey
 312         // since it is made up of the current set of StyleMap ids.
 313         //
 314 
 315         Styleable parent = node.getStyleableParent();
 316 
 317         // if the node's parent is null and the style maps are the same, then we can certainly reuse the style-helper
 318         if (parent == null) {
 319             return true;
 320         }
 321 
 322         CssStyleHelper parentHelper = getStyleHelper(node.styleHelper.firstStyleableAncestor);
 323 
 324         if (parentHelper != null && parentHelper.cacheContainer != null) {
 325 
 326             int[] parentIds = parentHelper.cacheContainer.styleCacheKey.getStyleMapIds();
 327             int[] nodeIds = node.styleHelper.cacheContainer.styleCacheKey.getStyleMapIds();
 328 
 329             if (parentIds.length == nodeIds.length - 1) {
 330 
 331                 boolean isSame = true;
 332 
 333                 // check that all of the style map ids are the same.
 334                 for (int i = 0; i < parentIds.length; i++) {
 335                     if (nodeIds[i + 1] != parentIds[i]) {
 336                         isSame = false;
 337                         break;
 338                     }
 339                 }
 340 
 341                 return isSame;
 342 
 343             }
 344         }
 345 
 346         return false;
 347     }
 348 
 349     /* This is the first Styleable parent (of Node this StyleHelper belongs to)
 350      * having a valid StyleHelper */
 351     private Node firstStyleableAncestor;
 352 
 353     private CacheContainer cacheContainer;
 354 
 355     private final static class CacheContainer {
 356 
 357         // Set internal internalState structures
 358         private CacheContainer(
 359                 Node node,
 360                 final StyleMap styleMap,
 361                 int depth) {
 362 
 363             int ctr = 0;
 364             int[] smapIds = new int[depth];
 365             smapIds[ctr++] = this.smapId = styleMap.getId();
 366 
 367             //
 368             // Create a set of StyleMap id's from the parent's smapIds.
 369             // The resulting smapIds array may have less than depth elements.
 370             // If a parent doesn't have a styleHelper or the styleHelper's
 371             // internal state is null, then that parent doesn't contribute
 372             // to the selection of a style. Any Node that has the same
 373             // set of smapId's can potentially share previously calculated
 374             // values.
 375             //
 376             Styleable parent = node.getStyleableParent();
 377             for(int d=1; d<depth; d++) {
 378 
 379                 // TODO: won't work for something like .menu-item:hover. Need to separate CssStyleHelper tree from scene-graph tree
 380                 if ( parent instanceof Node) {
 381                     Node parentNode = (Node)parent;
 382                 final CssStyleHelper helper = parentNode.styleHelper;
 383                     if (helper != null && helper.cacheContainer != null) {
 384                         smapIds[ctr++] = helper.cacheContainer.smapId;
 385                     }
 386                 }
 387                 parent = parent.getStyleableParent();
 388 
 389             }
 390 
 391             this.styleCacheKey = new StyleCache.Key(smapIds, ctr);
 392 
 393             CssMetaData<Styleable,Font> styleableFontProperty = null;
 394 
 395             final List<CssMetaData<? extends Styleable, ?>> props = node.getCssMetaData();
 396             final int pMax = props != null ? props.size() : 0;
 397             for (int p=0; p<pMax; p++) {
 398                 final CssMetaData<? extends Styleable, ?> prop = props.get(p);
 399 
 400                 if ("-fx-font".equals(prop.getProperty())) {
 401                     // unchecked!
 402                     styleableFontProperty = (CssMetaData<Styleable, Font>) prop;
 403                     break;
 404                 }
 405             }
 406 
 407             this.fontProp = styleableFontProperty;
 408             this.fontSizeCache = new HashMap<>();
 409 
 410             this.cssSetProperties = new HashMap<>();
 411 
 412         }
 413 
 414         private StyleMap getStyleMap(Styleable styleable) {
 415             if (styleable != null) {
 416                 SubScene subScene =  (styleable instanceof Node) ? ((Node) styleable).getSubScene() : null;
 417                 return StyleManager.getInstance().getStyleMap(styleable, subScene, smapId);
 418             } else {
 419                 return StyleMap.EMPTY_MAP;
 420             }
 421 
 422         }
 423 
 424         // This is the key we use to find the shared cache
 425         private final StyleCache.Key styleCacheKey;
 426 
 427         // If the node has a fontProperty, we hang onto the CssMetaData for it
 428         // so we can get at it later.
 429         // TBD - why not the fontProperty itself?
 430         private final CssMetaData<Styleable,Font> fontProp;
 431 
 432         // The id of StyleMap that contains the styles that apply to this node
 433         private final int smapId;
 434 
 435         // All nodes with the same set of styles share the same cache of
 436         // calculated values. But one node might have a different font-size
 437         // than another so the values are stored in cache by font-size.
 438         // This map associates a style cache entry with the font to use when
 439         // getting a value from or putting a value into cache.
 440         private final Map<StyleCacheEntry.Key, CalculatedValue> fontSizeCache;
 441 
 442         // Any properties that have been set by this style helper are tracked
 443         // here so the property can be reset without expanding properties that
 444         // were not set by css.
 445         private final Map<CssMetaData, CalculatedValue> cssSetProperties;
 446 
 447         private boolean forceSlowpath = false;
 448     }
 449 
 450     private void resetToInitialValues(final Styleable styleable) {
 451 
 452         if (cacheContainer == null ||
 453                 cacheContainer.cssSetProperties == null ||
 454                 cacheContainer.cssSetProperties.isEmpty()) return;
 455 
 456         // RT-31714 - make a copy of the entry set and clear the cssSetProperties immediately.
 457         Set<Entry<CssMetaData, CalculatedValue>> entrySet = new HashSet<>(cacheContainer.cssSetProperties.entrySet());
 458         cacheContainer.cssSetProperties.clear();
 459 
 460         for (Entry<CssMetaData, CalculatedValue> resetValues : entrySet) {
 461 
 462             final CssMetaData metaData = resetValues.getKey();
 463             final StyleableProperty styleableProperty = metaData.getStyleableProperty(styleable);
 464 
 465             final StyleOrigin styleOrigin = styleableProperty.getStyleOrigin();
 466             if (styleOrigin != null && styleOrigin != StyleOrigin.USER) {
 467                 final CalculatedValue calculatedValue = resetValues.getValue();
 468                 styleableProperty.applyStyle(calculatedValue.getOrigin(), calculatedValue.getValue());
 469             }
 470         }
 471     }
 472 
 473 
 474     private StyleMap getStyleMap(Styleable styleable) {
 475         if (cacheContainer == null || styleable == null) return null;
 476         return cacheContainer.getStyleMap(styleable);
 477     }
 478 
 479     /**
 480      * A Set of all the pseudo-class states which, if they change, need to
 481      * cause the Node to be set to UPDATE its CSS styles on the next pulse.
 482      * For example, your stylesheet might have:
 483      * <pre><code>
 484      * .button { ... }
 485      * .button:hover { ... }
 486      * .button *.label { text-fill: black }
 487      * .button:hover *.label { text-fill: blue }
 488      * </code></pre>
 489      * In this case, the first 2 rules apply to the Button itself, but the
 490      * second two rules apply to the label within a Button. When the hover
 491      * changes on the Button, however, we must mark the Button as needing
 492      * an UPDATE. StyleHelper though only contains styles for the first two
 493      * rules for Button. The pseudoclassStateMask would in this case have
 494      * only a single bit set for "hover". In this way the StyleHelper associated
 495      * with the Button would know whether a change to "hover" requires the
 496      * button and all children to be update. Other pseudo-class state changes
 497      * that are not in this hash set are ignored.
 498      * *
 499      * Called "triggerStates" since they would trigger a CSS update.
 500      */
 501     private PseudoClassState triggerStates = new PseudoClassState();
 502 
 503     boolean pseudoClassStateChanged(PseudoClass pseudoClass) {
 504         return triggerStates.contains(pseudoClass);
 505     }
 506 
 507     /**
 508      * Dynamic pseudo-class state of the node and its parents.
 509      * Only valid during a pulse.
 510      *
 511      * The StyleCacheEntry to choose depends on the Node's pseudo-class state
 512      * and the pseudo-class state of its parents. Without the parent
 513      * pseudo-class state, the fact that the the node in this pseudo-class state
 514      * matched foo:blah bar { } is lost.
 515      */
 516     // TODO: this should work on Styleable, not Node
 517     private Set<PseudoClass>[] getTransitionStates(final Node node) {
 518 
 519         // if cacheContainer is null, then CSS just doesn't apply to this node
 520         if (cacheContainer == null) return null;
 521 
 522         int depth = 0;
 523         Node parent = node;
 524         while (parent != null) {
 525             depth += 1;
 526             parent = parent.getParent();
 527         }
 528 
 529         //
 530         // StyleHelper#triggerStates is the set of pseudo-classes that appear
 531         // in the style maps of this StyleHelper. Calculated values are
 532         // cached by pseudo-class state, but only the pseudo-class states
 533         // that mater are used in the search. So we take the transition states
 534         // and intersect them with triggerStates to remove the
 535         // transition states that don't matter when it comes to matching states
 536         // on a  selector. For example if the style map contains only
 537         // .foo:hover { -fx-fill: red; } then only the hover state matters
 538         // but the transtion state could be [hover, focused]
 539         //
 540         final Set<PseudoClass>[] retainedStates = new PseudoClassState[depth];
 541 
 542         //
 543         // Note Well: The array runs from leaf to root. That is,
 544         // retainedStates[0] is the pseudo-class state for node and
 545         // retainedStates[1..(states.length-1)] are the retainedStates for the
 546         // node's parents.
 547         //
 548 
 549         int count = 0;
 550         parent = node;
 551         while (parent != null) { // This loop traverses through all ancestors till root
 552             final CssStyleHelper helper = (parent instanceof Node) ? parent.styleHelper : null;
 553             if (helper != null) {
 554                 final Set<PseudoClass> pseudoClassState = parent.pseudoClassStates;
 555                 retainedStates[count] = new PseudoClassState();
 556                 retainedStates[count].addAll(pseudoClassState);
 557                 // retainAll method takes the intersection of pseudoClassState and helper.triggerStates
 558                 retainedStates[count].retainAll(helper.triggerStates);
 559                 count += 1;
 560             }
 561             parent = parent.getParent();
 562         }
 563 
 564         final Set<PseudoClass>[] transitionStates = new PseudoClassState[count];
 565         System.arraycopy(retainedStates, 0, transitionStates, 0, count);
 566 
 567         return transitionStates;
 568 
 569     }
 570 
 571     /**
 572      * Called by the Node whenever it has transitioned from one set of
 573      * pseudo-class states to another. This function will then lookup the
 574      * new values for each of the styleable variables on the Node, and
 575      * then either set the value directly or start an animation based on
 576      * how things are specified in the CSS file. Currently animation support
 577      * is disabled until the new parser comes online with support for
 578      * animations and that support is detectable via the API.
 579      */
 580     void transitionToState(final Node node) {
 581 
 582         if (cacheContainer == null) {
 583             return;
 584         }
 585 
 586         //
 587         // If styleMap is null, then StyleManager has blown it away and we need to reapply CSS.
 588         //
 589         final StyleMap styleMap = getStyleMap(node);
 590         if (styleMap == null) {
 591             cacheContainer = null;
 592             node.reapplyCSS();
 593             return;
 594         }
 595 
 596         // if the style-map is empty, then we are only looking for inherited styles.
 597         final boolean inheritOnly = styleMap.isEmpty();
 598 
 599         //
 600         // Styles that need lookup can be cached provided none of the styles
 601         // are from Node.style.
 602         //
 603         final StyleCache sharedCache = StyleManager.getInstance().getSharedCache(node, node.getSubScene(), cacheContainer.styleCacheKey);
 604 
 605         if (sharedCache == null) {
 606             // Shared cache was blown away by StyleManager.
 607             // Therefore, this CssStyleHelper is no good.
 608             cacheContainer = null;
 609             node.reapplyCSS();
 610             return;
 611 
 612         }
 613 
 614         final Set<PseudoClass>[] transitionStates = getTransitionStates(node);
 615 
 616         final StyleCacheEntry.Key fontCacheKey = new StyleCacheEntry.Key(transitionStates, Font.getDefault());
 617         CalculatedValue cachedFont = cacheContainer.fontSizeCache.get(fontCacheKey);
 618 
 619         if (cachedFont == null) {
 620 
 621             cachedFont = lookupFont(node, "-fx-font", styleMap, cachedFont);
 622 
 623             if (cachedFont == SKIP) cachedFont = getCachedFont(node.getStyleableParent());
 624             if (cachedFont == null) cachedFont = new CalculatedValue(Font.getDefault(), null, false);
 625 
 626             cacheContainer.fontSizeCache.put(fontCacheKey,cachedFont);
 627 
 628         }
 629 
 630         final Font fontForRelativeSizes = (Font)cachedFont.getValue();
 631 
 632         final StyleCacheEntry.Key cacheEntryKey = new StyleCacheEntry.Key(transitionStates, fontForRelativeSizes);
 633         StyleCacheEntry cacheEntry = sharedCache.getStyleCacheEntry(cacheEntryKey);
 634 
 635         // if the cacheEntry already exists, take the fastpath
 636         final boolean fastpath = cacheEntry != null;
 637 
 638         if (cacheEntry == null) {
 639             cacheEntry = new StyleCacheEntry();
 640             sharedCache.addStyleCacheEntry(cacheEntryKey, cacheEntry);
 641         }
 642 
 643         final List<CssMetaData<? extends Styleable,  ?>> styleables = node.getCssMetaData();
 644 
 645         // Used in the for loop below, and a convenient place to stop when debugging.
 646         final int max = styleables.size();
 647 
 648         final boolean isForceSlowpath = cacheContainer.forceSlowpath;
 649         cacheContainer.forceSlowpath = false;
 650 
 651         // For each property that is settable, we need to do a lookup and
 652         // transition to that value.
 653         for(int n=0; n<max; n++) {
 654 
 655             @SuppressWarnings("unchecked") // this is a widening conversion
 656             final CssMetaData<Styleable,Object> cssMetaData =
 657                     (CssMetaData<Styleable,Object>)styleables.get(n);
 658 
 659             // Don't bother looking up styles that don't inherit.
 660             if (inheritOnly && cssMetaData.isInherits() == false) {
 661                 continue;
 662             }
 663 
 664             // Skip the lookup if we know there isn't a chance for this property
 665             // to be set (usually due to a "bind").
 666             if (!cssMetaData.isSettable(node)) continue;
 667 
 668             final String property = cssMetaData.getProperty();
 669 
 670             CalculatedValue calculatedValue = cacheEntry.get(property);
 671 
 672             // If there is no calculatedValue and we're on the fast path,
 673             // take the slow path if cssFlags is REAPPLY (RT-31691)
 674             final boolean forceSlowpath =
 675                     fastpath && calculatedValue == null && isForceSlowpath;
 676 
 677             final boolean addToCache =
 678                     (!fastpath && calculatedValue == null) || forceSlowpath;
 679 
 680             if (fastpath && !forceSlowpath) {
 681 
 682                 // If the cache contains SKIP, then there was an
 683                 // exception thrown from applyStyle
 684                 if (calculatedValue == SKIP) {
 685                     continue;
 686                 }
 687 
 688             } else if (calculatedValue == null) {
 689 
 690                 // slowpath!
 691                 calculatedValue = lookup(node, cssMetaData, styleMap, transitionStates[0],
 692                         node, cachedFont);
 693 
 694                 // lookup is not supposed to return null.
 695                 if (calculatedValue == null) {
 696                     assert false : "lookup returned null for " + property;
 697                     continue;
 698                 }
 699 
 700             }
 701 
 702             // StyleableProperty#applyStyle might throw an exception and it is called
 703             // from two places in this try block.
 704             try {
 705 
 706                 //
 707                 // RT-19089
 708                 // If the current value of the property was set by CSS
 709                 // and there is no style for the property, then reset this
 710                 // property to its initial value. If it was not set by CSS
 711                 // then leave the property alone.
 712                 //
 713                 if (calculatedValue == null || calculatedValue == SKIP) {
 714 
 715                     // cssSetProperties keeps track of the StyleableProperty's that were set by CSS in the previous state.
 716                     // If this property is not in cssSetProperties map, then the property was not set in the previous state.
 717                     // This accomplishes two things. First, it lets us know if the property was set in the previous state
 718                     // so it can be reset in this state if there is no value for it. Second, it calling
 719                     // CssMetaData#getStyleableProperty which is rather expensive as it may cause expansion of lazy
 720                     // properties.
 721                     CalculatedValue initialValue = cacheContainer.cssSetProperties.get(cssMetaData);
 722 
 723                     // if the current value was set by CSS and there
 724                     // is no calculated value for the property, then
 725                     // there was no style for the property in the current
 726                     // state, so reset the property to its initial value.
 727                     if (initialValue != null) {
 728 
 729                         StyleableProperty styleableProperty = cssMetaData.getStyleableProperty(node);
 730                         if (styleableProperty.getStyleOrigin() != StyleOrigin.USER) {
 731                             styleableProperty.applyStyle(initialValue.getOrigin(), initialValue.getValue());
 732                         }
 733                     }
 734 
 735                     continue;
 736 
 737                 }
 738 
 739                 if (addToCache) {
 740 
 741                     // If we're not on the fastpath, then add the calculated
 742                     // value to cache.
 743                     cacheEntry.put(property, calculatedValue);
 744                 }
 745 
 746                 StyleableProperty styleableProperty = cssMetaData.getStyleableProperty(node);
 747 
 748                 // need to know who set the current value - CSS, the user, or init
 749                 final StyleOrigin originOfCurrentValue = styleableProperty.getStyleOrigin();
 750 
 751 
 752                 // RT-10522:
 753                 // If the user set the property and there is a style and
 754                 // the style came from the user agent stylesheet, then
 755                 // skip the value. A style from a user agent stylesheet should
 756                 // not override the user set style.
 757                 //
 758                 final StyleOrigin originOfCalculatedValue = calculatedValue.getOrigin();
 759 
 760                 // A calculated value should never have a null style origin since that would
 761                 // imply the style didn't come from a stylesheet or in-line style.
 762                 if (originOfCalculatedValue == null) {
 763                     assert false : styleableProperty.toString();
 764                     continue;
 765                 }
 766 
 767                 if (originOfCurrentValue == StyleOrigin.USER) {
 768                     if (originOfCalculatedValue == StyleOrigin.USER_AGENT) {
 769                         continue;
 770                     }
 771                 }
 772 
 773                 final Object value = calculatedValue.getValue();
 774                 final Object currentValue = styleableProperty.getValue();
 775 
 776                 // RT-21185: Only apply the style if something has changed.
 777                 if ((originOfCurrentValue != originOfCalculatedValue)
 778                         || (currentValue != null
 779                         ? currentValue.equals(value) == false
 780                         : value != null)) {
 781 
 782                     if (LOGGER.isLoggable(Level.FINER)) {
 783                         LOGGER.finer(property + ", call applyStyle: " + styleableProperty + ", value =" +
 784                                 String.valueOf(value) + ", originOfCalculatedValue=" + originOfCalculatedValue);
 785                     }
 786 
 787                     styleableProperty.applyStyle(originOfCalculatedValue, value);
 788 
 789                     if (cacheContainer.cssSetProperties.containsKey(cssMetaData) == false) {
 790                         // track this property
 791                         CalculatedValue initialValue = new CalculatedValue(currentValue, originOfCurrentValue, false);
 792                         cacheContainer.cssSetProperties.put(cssMetaData, initialValue);
 793                     }
 794 
 795                 }
 796 
 797             } catch (Exception e) {
 798 
 799                 StyleableProperty styleableProperty = cssMetaData.getStyleableProperty(node);
 800 
 801                 final String msg = String.format("Failed to set css [%s] on [%s] due to '%s'\n",
 802                         cssMetaData.getProperty(), styleableProperty, e.getMessage());
 803 
 804                 List<CssParser.ParseError> errors = null;
 805                 if ((errors = StyleManager.getErrors()) != null) {
 806                     final CssParser.ParseError error = new CssParser.ParseError.PropertySetError(cssMetaData, node, msg);
 807                     errors.add(error);
 808                 }
 809 
 810                 PlatformLogger logger = Logging.getCSSLogger();
 811                 if (logger.isLoggable(Level.WARNING)) {
 812                     logger.warning(msg);
 813                 }
 814 
 815                 // RT-27155: if setting value raises exception, reset value
 816                 // the value to initial and thereafter skip setting the property
 817                 cacheEntry.put(property, SKIP);
 818 
 819                 CalculatedValue cachedValue = null;
 820                 if (cacheContainer != null && cacheContainer.cssSetProperties != null) {
 821                     cachedValue = cacheContainer.cssSetProperties.get(cssMetaData);
 822                 }
 823                 Object value = (cachedValue != null) ? cachedValue.getValue() : cssMetaData.getInitialValue(node);
 824                 StyleOrigin origin = (cachedValue != null) ? cachedValue.getOrigin() : null;
 825                 try {
 826                     styleableProperty.applyStyle(origin, value);
 827                 } catch (Exception ebad) {
 828                     // This would be bad.
 829                     if (logger.isLoggable(Level.SEVERE)) {
 830                         logger.severe(String.format("Could not reset [%s] on [%s] due to %s\n" ,
 831                                 cssMetaData.getProperty(), styleableProperty, e.getMessage()));
 832                     }
 833                 }
 834 
 835             }
 836 
 837         }
 838     }
 839 
 840     /**
 841      * Gets the CSS CascadingStyle for the property of this node in these pseudo-class
 842      * states. A null style may be returned if there is no style information
 843      * for this combination of input parameters.
 844      *
 845      *
 846      * @param styleable
 847      * @param property
 848      * @param styleMap
 849      * @param states   @return
 850      * */
 851     private CascadingStyle getStyle(final Styleable styleable, final String property, final StyleMap styleMap, final Set<PseudoClass> states){
 852 
 853         if (styleMap == null || styleMap.isEmpty()) return null;
 854 
 855         final Map<String, List<CascadingStyle>> cascadingStyleMap = styleMap.getCascadingStyles();
 856         if (cascadingStyleMap == null || cascadingStyleMap.isEmpty()) return null;
 857 
 858         // Get all of the Styles which may apply to this particular property
 859         List<CascadingStyle> styles = cascadingStyleMap.get(property);
 860 
 861         // If there are no styles for this property then we can just bail
 862         if ((styles == null) || styles.isEmpty()) return null;
 863 
 864         // Go looking for the style. We do this by visiting each CascadingStyle in
 865         // order finding the first that matches the current node & set of
 866         // pseudo-class states. We use an iteration style that avoids creating
 867         // garbage iterators (and wish javac did it for us...)
 868        CascadingStyle style = null;
 869         final int max = (styles == null) ? 0 : styles.size();
 870         for (int i=0; i<max; i++) {
 871             final CascadingStyle s = styles.get(i);
 872             final Selector sel = s == null ? null : s.getSelector();
 873             if (sel == null) continue; // bail if the selector is null.
 874 //System.out.println(node.toString() + "\n\tstates=" + PseudoClassSet.getPseudoClasses(states) + "\n\tstateMatches? " + sel.stateMatches(node, states) + "\n\tsel=" + sel.toString());
 875             if (sel.stateMatches(styleable, states)) {
 876                 style = s;
 877                 break;
 878             }
 879         }
 880 
 881         return style;
 882     }
 883 
 884     /**
 885      * The main workhorse of this class, the lookup method walks up the CSS
 886      * style tree looking for the style information for the Node, the
 887      * property associated with the given styleable, in these states for this font.
 888      *
 889      *
 890      *
 891      *
 892      * @param styleable
 893      * @param states
 894      * @param originatingStyleable
 895      * @return
 896      */
 897     private CalculatedValue lookup(final Styleable styleable,
 898                                    final CssMetaData cssMetaData,
 899                                    final StyleMap styleMap,
 900                                    final Set<PseudoClass> states,
 901                                    final Styleable originatingStyleable,
 902                                    final CalculatedValue cachedFont) {
 903 
 904         if (cssMetaData.getConverter() == FontConverter.getInstance()) {
 905             return lookupFont(styleable, cssMetaData.getProperty(), styleMap, cachedFont);
 906         }
 907 
 908         final String property = cssMetaData.getProperty();
 909 
 910         // Get the CascadingStyle which may apply to this particular property
 911         CascadingStyle style = getStyle(styleable, property, styleMap, states);
 912 
 913         // If no style was found and there are no sub styleables, then there
 914         // are no matching styles for this property. We will then either SKIP
 915         // or we will INHERIT. We will inspect the default value for the styleable,
 916         // and if it is INHERIT then we will inherit otherwise we just skip it.
 917         final List<CssMetaData<? extends Styleable, ?>> subProperties = cssMetaData.getSubProperties();
 918         final int numSubProperties = (subProperties != null) ? subProperties.size() : 0;
 919         if (style == null) {
 920 
 921             if (numSubProperties == 0) {
 922 
 923                 return handleNoStyleFound(styleable, cssMetaData,
 924                         styleMap, states, originatingStyleable, cachedFont);
 925 
 926             } else {
 927 
 928                 // If style is null then it means we didn't successfully find the
 929                 // property we were looking for. However, there might be sub styleables,
 930                 // in which case we should perform a lookup for them. For example,
 931                 // there might not be a style for "font", but there might be one
 932                 // for "font-size" or "font-weight". So if the style is null, then
 933                 // we need to check with the sub-styleables.
 934 
 935                 // Build up a list of all SubProperties which have a constituent part.
 936                 // I default the array to be the size of the number of total
 937                 // sub styleables to avoid having the array grow.
 938                 Map<CssMetaData,Object> subs = null;
 939                 StyleOrigin origin = null;
 940 
 941                 boolean isRelative = false;
 942 
 943                 for (int i=0; i<numSubProperties; i++) {
 944                     CssMetaData subkey = subProperties.get(i);
 945                     CalculatedValue constituent =
 946                         lookup(styleable, subkey, styleMap, states,
 947                                 originatingStyleable, cachedFont);
 948                     if (constituent != SKIP) {
 949                         if (subs == null) {
 950                             subs = new HashMap<>();
 951                         }
 952                         subs.put(subkey, constituent.getValue());
 953 
 954                         // origin of this style is the most specific
 955                         if ((origin != null && constituent.getOrigin() != null)
 956                                 ? origin.compareTo(constituent.getOrigin()) < 0
 957                                 : constituent.getOrigin() != null) {
 958                             origin = constituent.getOrigin();
 959                         }
 960 
 961                         // if the constiuent uses relative sizes, then
 962                         // isRelative is true;
 963                         isRelative = isRelative || constituent.isRelative();
 964 
 965                     }
 966                 }
 967 
 968                 // If there are no subkeys which apply...
 969                 if (subs == null || subs.isEmpty()) {
 970                     return handleNoStyleFound(styleable, cssMetaData,
 971                             styleMap, states, originatingStyleable, cachedFont);
 972                 }
 973 
 974                 try {
 975                     final StyleConverter keyType = cssMetaData.getConverter();
 976                     Object ret = keyType.convert(subs);
 977                     return new CalculatedValue(ret, origin, isRelative);
 978                 } catch (ClassCastException cce) {
 979                     final String msg = formatExceptionMessage(styleable, cssMetaData, null, cce);
 980                     List<CssParser.ParseError> errors = null;
 981                     if ((errors = StyleManager.getErrors()) != null) {
 982                         final CssParser.ParseError error = new CssParser.ParseError.PropertySetError(cssMetaData, styleable, msg);
 983                         errors.add(error);
 984                     }
 985                     if (LOGGER.isLoggable(Level.WARNING)) {
 986                         LOGGER.warning(msg);
 987                         LOGGER.fine("caught: ", cce);
 988                         LOGGER.fine("styleable = " + cssMetaData);
 989                         LOGGER.fine("node = " + styleable.toString());
 990                     }
 991                     return SKIP;
 992                 }
 993             }
 994 
 995         } else { // style != null
 996 
 997             // RT-10522:
 998             // If the user set the property and there is a style and
 999             // the style came from the user agent stylesheet, then
1000             // skip the value. A style from a user agent stylesheet should
1001             // not override the user set style.
1002             if (style.getOrigin() == StyleOrigin.USER_AGENT) {
1003 
1004                 StyleableProperty styleableProperty = cssMetaData.getStyleableProperty(originatingStyleable);
1005                 // if styleableProperty is null, then we're dealing with a sub-property.
1006                 if (styleableProperty != null && styleableProperty.getStyleOrigin() == StyleOrigin.USER) {
1007                     return SKIP;
1008                 }
1009             }
1010 
1011             // If there was a style found, then we want to check whether the
1012             // value was "inherit". If so, then we will simply inherit.
1013             final ParsedValue cssValue = style.getParsedValue();
1014             if (cssValue != null && "inherit".equals(cssValue.getValue())) {
1015                 style = getInheritedStyle(styleable, property);
1016                 if (style == null) return SKIP;
1017             }
1018         }
1019 
1020 //        System.out.println("lookup " + property +
1021 //                ", selector = \'" + style.selector.toString() + "\'" +
1022 //                ", node = " + node.toString());
1023 
1024         return calculateValue(style, styleable, cssMetaData, styleMap, states,
1025                 originatingStyleable, cachedFont);
1026     }
1027 
1028     /**
1029      * Called when there is no style found.
1030      */
1031     private CalculatedValue handleNoStyleFound(final Styleable styleable,
1032                                                final CssMetaData cssMetaData,
1033                                                final StyleMap styleMap, Set<PseudoClass> pseudoClassStates, Styleable originatingStyleable,
1034                                                final CalculatedValue cachedFont) {
1035 
1036         if (cssMetaData.isInherits()) {
1037 
1038 
1039             StyleableProperty styleableProperty = cssMetaData.getStyleableProperty(styleable);
1040             StyleOrigin origin = styleableProperty != null ? styleableProperty.getStyleOrigin() : null;
1041 
1042             // RT-16308: if there is no matching style and the user set
1043             // the property, do not look for inherited styles.
1044             if (origin == StyleOrigin.USER) {
1045 
1046                     return SKIP;
1047 
1048             }
1049 
1050             CascadingStyle style = getInheritedStyle(styleable, cssMetaData.getProperty());
1051             if (style == null) return SKIP;
1052 
1053             CalculatedValue cv =
1054                     calculateValue(style, styleable, cssMetaData,
1055                             styleMap, pseudoClassStates, originatingStyleable,
1056                                    cachedFont);
1057 
1058             return cv;
1059 
1060         } else {
1061 
1062             // Not inherited. There is no style
1063             return SKIP;
1064 
1065         }
1066     }
1067     /**
1068      * Called when we must getInheritedStyle a value from a parent node in the scenegraph.
1069      */
1070     private CascadingStyle getInheritedStyle(
1071             final Styleable styleable,
1072             final String property) {
1073 
1074         Styleable parent = ((Node)styleable).styleHelper.firstStyleableAncestor;
1075         CssStyleHelper parentStyleHelper = getStyleHelper((Node) parent);
1076 
1077         if (parent != null && parentStyleHelper != null) {
1078 
1079             StyleMap parentStyleMap = parentStyleHelper.getStyleMap(parent);
1080             Set<PseudoClass> transitionStates = ((Node)parent).pseudoClassStates;
1081             CascadingStyle cascadingStyle = parentStyleHelper.getStyle(parent, property, parentStyleMap, transitionStates);
1082 
1083             if (cascadingStyle != null) {
1084 
1085                 final ParsedValue cssValue = cascadingStyle.getParsedValue();
1086 
1087                 if ("inherit".equals(cssValue.getValue())) {
1088                     return getInheritedStyle(parent, property);
1089                 }
1090                 return cascadingStyle;
1091             }
1092         }
1093 
1094         return null;
1095     }
1096 
1097 
1098     // helps with self-documenting the code
1099     private static final Set<PseudoClass> NULL_PSEUDO_CLASS_STATE = null;
1100 
1101     /**
1102      * Find the property among the styles that pertain to the Node
1103      */
1104     private CascadingStyle resolveRef(final Styleable styleable, final String property, final StyleMap styleMap, final Set<PseudoClass> states) {
1105 
1106         final CascadingStyle style = getStyle(styleable, property, styleMap, states);
1107         if (style != null) {
1108             return style;
1109         } else {
1110             // if style is null, it may be because there isn't a style for this
1111             // node in this state, or we may need to look up the parent chain
1112             if (states != null && states.size() > 0) {
1113                 // if states > 0, then we need to check this node again,
1114                 // but without any states.
1115                 return resolveRef(styleable,property, styleMap, NULL_PSEUDO_CLASS_STATE);
1116             } else {
1117                 // TODO: This block was copied from inherit. Both should use same code somehow.
1118 
1119                 Styleable styleableParent = ((Node)styleable).styleHelper.firstStyleableAncestor;
1120                 CssStyleHelper parentStyleHelper = getStyleHelper((Node) styleableParent);
1121 
1122                 if (styleableParent == null || parentStyleHelper == null) {
1123                     return null;
1124                 }
1125 
1126                 StyleMap parentStyleMap = parentStyleHelper.getStyleMap(styleableParent);
1127                 Set<PseudoClass> styleableParentPseudoClassStates =
1128                     styleableParent instanceof Node
1129                         ? ((Node)styleableParent).pseudoClassStates
1130                         : styleable.getPseudoClassStates();
1131 
1132                 return parentStyleHelper.resolveRef(styleableParent, property,
1133                         parentStyleMap, styleableParentPseudoClassStates);
1134             }
1135         }
1136     }
1137 
1138     // to resolve a lookup, we just need to find the parsed value.
1139     private ParsedValue resolveLookups(
1140             final Styleable styleable,
1141             final ParsedValue parsedValue,
1142             final StyleMap styleMap, Set<PseudoClass> states,
1143             final ObjectProperty<StyleOrigin> whence,
1144             Set<ParsedValue> resolves) {
1145 
1146         //
1147         // either the value itself is a lookup, or the value contain a lookup
1148         //
1149         if (parsedValue.isLookup()) {
1150 
1151             // The value we're looking for should be a Paint, one of the
1152             // containers for linear, radial or ladder, or a derived color.
1153             final Object val = parsedValue.getValue();
1154             if (val instanceof String) {
1155 
1156                 final String sval = ((String) val).toLowerCase(Locale.ROOT);
1157 
1158                 CascadingStyle resolved =
1159                     resolveRef(styleable, sval, styleMap, states);
1160 
1161                 if (resolved != null) {
1162 
1163                     if (resolves.contains(resolved.getParsedValue())) {
1164 
1165                         if (LOGGER.isLoggable(Level.WARNING)) {
1166                             LOGGER.warning("Loop detected in " + resolved.getRule().toString() + " while resolving '" + sval + "'");
1167                         }
1168                         throw new IllegalArgumentException("Loop detected in " + resolved.getRule().toString() + " while resolving '" + sval + "'");
1169 
1170                     } else {
1171                         resolves.add(parsedValue);
1172                     }
1173 
1174                     // The origin of this parsed value is the greatest of
1175                     // any of the resolved reference. If a resolved reference
1176                     // comes from an inline style, for example, then the value
1177                     // calculated from the resolved lookup should have inline
1178                     // as its origin. Otherwise, an inline style could be
1179                     // stored in shared cache.
1180                     final StyleOrigin wOrigin = whence.get();
1181                     final StyleOrigin rOrigin = resolved.getOrigin();
1182                     if (rOrigin != null && (wOrigin == null ||  wOrigin.compareTo(rOrigin) < 0)) {
1183                         whence.set(rOrigin);
1184                     }
1185 
1186                     // the resolved value may itself need to be resolved.
1187                     // For example, if the value "color" resolves to "base",
1188                     // then "base" will need to be resolved as well.
1189                     ParsedValue pv = resolveLookups(styleable, resolved.getParsedValue(), styleMap, states, whence, resolves);
1190 
1191                     if (resolves != null) {
1192                         resolves.remove(parsedValue);
1193                     }
1194 
1195                     return pv;
1196 
1197                 }
1198             }
1199         }
1200 
1201         // If the value doesn't contain any values that need lookup, then bail
1202         if (!parsedValue.isContainsLookups()) {
1203             return parsedValue;
1204         }
1205 
1206         final Object val = parsedValue.getValue();
1207 
1208         if (val instanceof ParsedValue[][]) {
1209 
1210             // If ParsedValue is a layered sequence of values, resolve the lookups for each.
1211             final ParsedValue[][] layers = (ParsedValue[][])val;
1212             ParsedValue[][] resolved = new ParsedValue[layers.length][0];
1213             for (int l=0; l<layers.length; l++) {
1214                 resolved[l] = new ParsedValue[layers[l].length];
1215                 for (int ll=0; ll<layers[l].length; ll++) {
1216                     if (layers[l][ll] == null) continue;
1217                     resolved[l][ll] =
1218                         resolveLookups(styleable, layers[l][ll], styleMap, states, whence, resolves);
1219                 }
1220             }
1221 
1222             resolves.clear();
1223 
1224             return new ParsedValueImpl(resolved, parsedValue.getConverter(), false);
1225 
1226         } else if (val instanceof ParsedValueImpl[]) {
1227 
1228             // If ParsedValue is a sequence of values, resolve the lookups for each.
1229             final ParsedValue[] layer = (ParsedValue[])val;
1230             ParsedValue[] resolved = new ParsedValue[layer.length];
1231             for (int l=0; l<layer.length; l++) {
1232                 if (layer[l] == null) continue;
1233                 resolved[l] =
1234                     resolveLookups(styleable, layer[l], styleMap, states, whence, resolves);
1235             }
1236 
1237             resolves.clear();
1238 
1239             return new ParsedValueImpl(resolved, parsedValue.getConverter(), false);
1240 
1241         }
1242 
1243         return parsedValue;
1244 
1245     }
1246 
1247     private String getUnresolvedLookup(final ParsedValue resolved) {
1248 
1249         Object value = resolved.getValue();
1250 
1251         if (resolved.isLookup() && value instanceof String) {
1252             return (String)value;
1253         }
1254 
1255         if (value instanceof ParsedValue[][]) {
1256             final ParsedValue[][] layers = (ParsedValue[][])value;
1257             for (int l=0; l<layers.length; l++) {
1258                 for (int ll=0; ll<layers[l].length; ll++) {
1259                     if (layers[l][ll] == null) continue;
1260                     String unresolvedLookup = getUnresolvedLookup(layers[l][ll]);
1261                     if (unresolvedLookup != null) return unresolvedLookup;
1262                 }
1263             }
1264 
1265         } else if (value instanceof ParsedValue[]) {
1266         // If ParsedValue is a sequence of values, resolve the lookups for each.
1267             final ParsedValue[] layer = (ParsedValue[])value;
1268             for (int l=0; l<layer.length; l++) {
1269                 if (layer[l] == null) continue;
1270                 String unresolvedLookup = getUnresolvedLookup(layer[l]);
1271                 if (unresolvedLookup != null) return unresolvedLookup;
1272             }
1273         }
1274 
1275         return null;
1276     }
1277 
1278     private String formatUnresolvedLookupMessage(Styleable styleable, CssMetaData cssMetaData, Style style, ParsedValue resolved, ClassCastException cce) {
1279 
1280         // Find value that could not be looked up. If the resolved value does not contain lookups, then the
1281         // ClassCastException is not because of trying to convert a String (which is the missing lookup)
1282         // to some value, but is because the convert method got some wrong value - like a paint when it should be a color.
1283         // See RT-33319 for an example of this.
1284         String missingLookup = resolved != null && resolved.isContainsLookups() ? getUnresolvedLookup(resolved) : null;
1285 
1286         StringBuilder sbuf = new StringBuilder();
1287         if (missingLookup != null) {
1288             sbuf.append("Could not resolve '")
1289                     .append(missingLookup)
1290                     .append("'")
1291                     .append(" while resolving lookups for '")
1292                     .append(cssMetaData.getProperty())
1293                     .append("'");
1294         } else {
1295             sbuf.append("Caught '")
1296                     .append(cce)
1297                     .append("'")
1298                     .append(" while converting value for '")
1299                     .append(cssMetaData.getProperty())
1300                     .append("'");
1301         }
1302 
1303         final Rule rule = style != null ? style.getDeclaration().getRule(): null;
1304         final Stylesheet stylesheet = rule != null ? rule.getStylesheet() : null;
1305         final String url = stylesheet != null ? stylesheet.getUrl() : null;
1306         if (url != null) {
1307             sbuf.append(" from rule '")
1308                 .append(style.getSelector())
1309                 .append("' in stylesheet ").append(url);
1310         } else if (stylesheet != null && StyleOrigin.INLINE == stylesheet.getOrigin()) {
1311             sbuf.append(" from inline style on " )
1312                 .append(styleable.toString());
1313         }
1314 
1315         return sbuf.toString();
1316     }
1317 
1318     private String formatExceptionMessage(Styleable styleable, CssMetaData cssMetaData, Style style, Exception e) {
1319 
1320         StringBuilder sbuf = new StringBuilder();
1321         sbuf.append("Caught ")
1322             .append(String.valueOf(e));
1323 
1324         if (cssMetaData != null) {
1325             sbuf.append("'")
1326                 .append(" while calculating value for '")
1327                 .append(cssMetaData.getProperty())
1328                 .append("'");
1329         }
1330 
1331         if (style != null) {
1332 
1333             final Rule rule = style.getDeclaration().getRule();
1334             final Stylesheet stylesheet = rule != null ? rule.getStylesheet() : null;
1335             final String url = stylesheet != null ? stylesheet.getUrl() : null;
1336 
1337             if (url != null) {
1338                 sbuf.append(" from rule '")
1339                         .append(style.getSelector())
1340                         .append("' in stylesheet ").append(url);
1341             } else if (styleable != null && stylesheet != null && StyleOrigin.INLINE == stylesheet.getOrigin()) {
1342                 sbuf.append(" from inline style on " )
1343                         .append(styleable.toString());
1344             } else {
1345                 sbuf.append(" from style '")
1346                     .append(String.valueOf(style))
1347                     .append("'");
1348             }
1349         }
1350 
1351         return sbuf.toString();
1352     }
1353 
1354 
1355     private CalculatedValue calculateValue(
1356             final CascadingStyle style,
1357             final Styleable styleable,
1358             final CssMetaData cssMetaData,
1359             final StyleMap styleMap, final Set<PseudoClass> states,
1360             final Styleable originatingStyleable,
1361             final CalculatedValue fontFromCacheEntry) {
1362 
1363         final ParsedValue cssValue = style.getParsedValue();
1364         if (cssValue != null && !("null".equals(cssValue.getValue()) || "none".equals(cssValue.getValue()))) {
1365 
1366             ParsedValue resolved = null;
1367             try {
1368 
1369                 ObjectProperty<StyleOrigin> whence = new SimpleObjectProperty<>(style.getOrigin());
1370                 resolved = resolveLookups(styleable, cssValue, styleMap, states, whence, new HashSet<>());
1371 
1372                 final String property = cssMetaData.getProperty();
1373 
1374                 // The computed value
1375                 Object val = null;
1376                 boolean isFontProperty =
1377                         "-fx-font".equals(property) ||
1378                         "-fx-font-size".equals(property);
1379 
1380                 boolean isRelative = ParsedValueImpl.containsFontRelativeSize(resolved, isFontProperty);
1381 
1382                 //
1383                 // Avoid using a font calculated from a relative size
1384                 // to calculate a font with a relative size.
1385                 // For example:
1386                 // Assume the default font size is 13 and we have a style with
1387                 // -fx-font-size: 1.5em, then the cacheEntry font value will
1388                 // have a size of 13*1.5=19.5.
1389                 // Now, when converting that same font size again in response
1390                 // to looking up a value for -fx-font, we do not want to use
1391                 // 19.5 as the font for relative size conversion since this will
1392                 // yield a font 19.5*1.5=29.25 when really what we want is
1393                 // a font size of 19.5.
1394                 // In this situation, then, we use the font from the parent's
1395                 // cache entry.
1396                 Font fontForFontRelativeSizes = null;
1397 
1398                 if (isRelative && isFontProperty &&
1399                     (fontFromCacheEntry == null || fontFromCacheEntry.isRelative())) {
1400 
1401                     Styleable parent = styleable;
1402                     CalculatedValue childsCachedFont = fontFromCacheEntry;
1403                     do {
1404 
1405                         CalculatedValue parentsCachedFont = getCachedFont(parent.getStyleableParent());
1406 
1407                         if (parentsCachedFont != null)  {
1408 
1409                             if (parentsCachedFont.isRelative()) {
1410 
1411                                 //
1412                                 // If the cached fonts are the same, then the cached font came from the same
1413                                 // style and we need to keep looking. Otherwise, use the font we found.
1414                                 //
1415                                 if (childsCachedFont == null || parentsCachedFont.equals(childsCachedFont)) {
1416                                     childsCachedFont = parentsCachedFont;
1417                                 } else {
1418                                     fontForFontRelativeSizes = (Font)parentsCachedFont.getValue();
1419                                 }
1420 
1421                             } else  {
1422                                 // fontValue.isRelative() == false!
1423                                 fontForFontRelativeSizes = (Font)parentsCachedFont.getValue();
1424                             }
1425 
1426                         }
1427 
1428                     } while(fontForFontRelativeSizes == null &&
1429                             (parent = parent.getStyleableParent()) != null);
1430                 }
1431 
1432                 // did we get a fontValue from the preceding block?
1433                 // if not, get it from our cacheEntry or choose the default
1434                 if (fontForFontRelativeSizes == null) {
1435                     if (fontFromCacheEntry != null && fontFromCacheEntry.isRelative() == false) {
1436                         fontForFontRelativeSizes = (Font)fontFromCacheEntry.getValue();
1437                     } else {
1438                         fontForFontRelativeSizes = Font.getDefault();
1439                     }
1440                 }
1441 
1442                 final StyleConverter cssMetaDataConverter = cssMetaData.getConverter();
1443                 // RT-37727 - handling of properties that are insets is wonky. If the property is -fx-inset, then
1444                 // there isn't an issue because the converter assigns the InsetsConverter to the ParsedValue.
1445                 // But -my-insets will parse as an array of numbers and the parser will assign the Size sequence
1446                 // converter to it. So, if the CssMetaData says it uses InsetsConverter, use the InsetsConverter
1447                 // and not the parser assigned converter.
1448                 if (cssMetaDataConverter == StyleConverter.getInsetsConverter()) {
1449                     if (resolved.getValue() instanceof ParsedValue) {
1450                         // If you give the parser "-my-insets: 5;" you end up with a ParsedValue<ParsedValue<?,Size>, Number>
1451                         // and not a ParsedValue<ParsedValue[], Number[]> so here we wrap the value into an array
1452                         // to make the InsetsConverter happy.
1453                         resolved = new ParsedValueImpl(new ParsedValue[] {(ParsedValue)resolved.getValue()}, null, false);
1454                     }
1455                     val = cssMetaDataConverter.convert(resolved, fontForFontRelativeSizes);
1456                 }
1457                 else if (resolved.getConverter() != null)
1458                     val = resolved.convert(fontForFontRelativeSizes);
1459                 else
1460                     val = cssMetaData.getConverter().convert(resolved, fontForFontRelativeSizes);
1461 
1462                 final StyleOrigin origin = whence.get();
1463                 return new CalculatedValue(val, origin, isRelative);
1464 
1465             } catch (ClassCastException cce) {
1466                 final String msg = formatUnresolvedLookupMessage(styleable, cssMetaData, style.getStyle(),resolved, cce);
1467                 List<CssParser.ParseError> errors = null;
1468                 if ((errors = StyleManager.getErrors()) != null) {
1469                     final CssParser.ParseError error = new CssParser.ParseError.PropertySetError(cssMetaData, styleable, msg);
1470                     errors.add(error);
1471                 }
1472                 if (LOGGER.isLoggable(Level.WARNING)) {
1473                     LOGGER.warning(msg);
1474                     LOGGER.fine("node = " + styleable.toString());
1475                     LOGGER.fine("cssMetaData = " + cssMetaData);
1476                     LOGGER.fine("styles = " + getMatchingStyles(styleable, cssMetaData));
1477                 }
1478                 return SKIP;
1479             } catch (IllegalArgumentException iae) {
1480                 final String msg = formatExceptionMessage(styleable, cssMetaData, style.getStyle(), iae);
1481                 List<CssParser.ParseError> errors = null;
1482                 if ((errors = StyleManager.getErrors()) != null) {
1483                     final CssParser.ParseError error = new CssParser.ParseError.PropertySetError(cssMetaData, styleable, msg);
1484                     errors.add(error);
1485                 }
1486                 if (LOGGER.isLoggable(Level.WARNING)) {
1487                     LOGGER.warning(msg);
1488                     LOGGER.fine("caught: ", iae);
1489                     LOGGER.fine("styleable = " + cssMetaData);
1490                     LOGGER.fine("node = " + styleable.toString());
1491                 }
1492                 return SKIP;
1493             } catch (NullPointerException npe) {
1494                 final String msg = formatExceptionMessage(styleable, cssMetaData, style.getStyle(), npe);
1495                 List<CssParser.ParseError> errors = null;
1496                 if ((errors = StyleManager.getErrors()) != null) {
1497                     final CssParser.ParseError error = new CssParser.ParseError.PropertySetError(cssMetaData, styleable, msg);
1498                     errors.add(error);
1499                 }
1500                 if (LOGGER.isLoggable(Level.WARNING)) {
1501                     LOGGER.warning(msg);
1502                     LOGGER.fine("caught: ", npe);
1503                     LOGGER.fine("styleable = " + cssMetaData);
1504                     LOGGER.fine("node = " + styleable.toString());
1505                 }
1506                 return SKIP;
1507             }
1508 
1509         }
1510         // either cssValue was null or cssValue's value was "null" or "none"
1511         return new CalculatedValue(null, style.getOrigin(), false);
1512 
1513     }
1514 
1515     private static final CssMetaData dummyFontProperty =
1516             new FontCssMetaData<Node>("-fx-font", Font.getDefault()) {
1517 
1518         @Override
1519         public boolean isSettable(Node node) {
1520             return true;
1521         }
1522 
1523         @Override
1524         public StyleableProperty<Font> getStyleableProperty(Node node) {
1525             return null;
1526         }
1527     };
1528 
1529     private CalculatedValue getCachedFont(final Styleable styleable) {
1530 
1531         if (styleable instanceof Node == false) return null;
1532 
1533         CalculatedValue cachedFont = null;
1534 
1535         Node parent = (Node)styleable;
1536 
1537         final CssStyleHelper parentHelper = parent.styleHelper;
1538 
1539         // if there is no parentHelper,
1540         // or there is a parentHelper but no cacheContainer,
1541         // then look to the next parent
1542         if (parentHelper == null || parentHelper.cacheContainer == null) {
1543 
1544             cachedFont = getCachedFont(parent.getStyleableParent());
1545 
1546         // there is a parent helper and a cacheContainer,
1547         } else  {
1548 
1549             CacheContainer parentCacheContainer = parentHelper.cacheContainer;
1550             if ( parentCacheContainer != null
1551                     && parentCacheContainer.fontSizeCache != null
1552                     && parentCacheContainer.fontSizeCache.isEmpty() == false) {
1553 
1554                 Set<PseudoClass>[] transitionStates = parentHelper.getTransitionStates(parent);
1555                 StyleCacheEntry.Key parentCacheEntryKey = new StyleCacheEntry.Key(transitionStates, Font.getDefault());
1556                 cachedFont = parentCacheContainer.fontSizeCache.get(parentCacheEntryKey);
1557             }
1558 
1559             if (cachedFont == null)  {
1560                 StyleMap smap = parentHelper.getStyleMap(parent);
1561                 cachedFont = parentHelper.lookupFont(parent, "-fx-font", smap, null);
1562             }
1563         }
1564 
1565         return cachedFont != SKIP ? cachedFont : null;
1566     }
1567 
1568     /*package access for testing*/ FontPosture getFontPosture(Font font) {
1569         if (font == null) return FontPosture.REGULAR;
1570 
1571         String fontName = font.getName().toLowerCase(Locale.ROOT);
1572 
1573         if (fontName.contains("italic")) {
1574             return FontPosture.ITALIC;
1575         }
1576 
1577         return FontPosture.REGULAR;
1578     }
1579 
1580     /*package access for testing*/ FontWeight getFontWeight(Font font) {
1581         if (font == null) return FontWeight.NORMAL;
1582 
1583         String fontName = font.getName().toLowerCase(Locale.ROOT);
1584 
1585         if (fontName.contains("bold")) {
1586             if (fontName.contains("extra")) return FontWeight.EXTRA_BOLD;
1587             if (fontName.contains("ultra")) return FontWeight.EXTRA_BOLD;
1588             else if (fontName.contains("semi")) return FontWeight.SEMI_BOLD;
1589             else if (fontName.contains("demi")) return FontWeight.SEMI_BOLD;
1590             else return FontWeight.BOLD;
1591 
1592         } else if (fontName.contains("light")) {
1593             if (fontName.contains("extra")) return FontWeight.EXTRA_LIGHT;
1594             if (fontName.contains("ultra")) return FontWeight.EXTRA_LIGHT;
1595             else return FontWeight.LIGHT;
1596 
1597         } else if (fontName.contains("black")) {
1598             return FontWeight.BLACK;
1599 
1600         } else if (fontName.contains("heavy")) {
1601             return FontWeight.BLACK;
1602 
1603         } else if (fontName.contains("medium")) {
1604             return FontWeight.MEDIUM;
1605         }
1606 
1607         return FontWeight.NORMAL;
1608 
1609     }
1610 
1611     /*package access for testing*/ String getFontFamily(Font font) {
1612         if (font == null) return Font.getDefault().getFamily();
1613         return font.getFamily();
1614     }
1615 
1616 
1617     /*package access for testing*/ Font deriveFont(
1618             Font font,
1619             String fontFamily,
1620             FontWeight fontWeight,
1621             FontPosture fontPosture,
1622             double fontSize) {
1623 
1624         if (font != null && fontFamily == null) fontFamily = getFontFamily(font);
1625         else if (fontFamily != null) fontFamily = Utils.stripQuotes(fontFamily);
1626 
1627         if (font != null && fontWeight == null) fontWeight = getFontWeight(font);
1628         if (font != null && fontPosture == null) fontPosture = getFontPosture(font);
1629         if (font != null && fontSize <= 0) fontSize = font.getSize();
1630 
1631         return  Font.font(
1632                 fontFamily,
1633                 fontWeight,
1634                 fontPosture,
1635                 fontSize);
1636     }
1637 
1638     /**
1639      * Look up a font property. This is handled separately from lookup since
1640      * font is inherited and has sub-properties. One should expect that the
1641      * text font for the following would be 16px Arial. The lookup method would
1642      * give 16px system since it would look <em>only</em> for font-size,
1643      * font-family, etc <em>only</em> if the lookup on font failed.
1644      * <pre>
1645      * Text text = new Text("Hello World");
1646      * text.setStyle("-fx-font-size: 16px;");
1647      * Group group = new Group();
1648      * group.setStyle("-fx-font: 12px Arial;");
1649      * group.getChildren().add(text);
1650      * </pre>
1651      */
1652      /*package access for testing*/ CalculatedValue lookupFont(
1653             final Styleable styleable,
1654             final String property,
1655             final StyleMap styleMap,
1656             final CalculatedValue cachedFont)
1657     {
1658 
1659         StyleOrigin origin = null;
1660 
1661         // How far from this node did we travel to find a font shorthand?
1662         // Don't look past this distance for other font properties.
1663         int distance = 0;
1664 
1665         // Did we find a style?
1666         boolean foundStyle = false;
1667 
1668         String family = null;
1669         double size = -1;
1670         FontWeight weight = null;
1671         FontPosture posture = null;
1672 
1673         CalculatedValue cvFont = cachedFont;
1674 
1675 
1676         Set<PseudoClass> states = styleable instanceof Node ? ((Node)styleable).pseudoClassStates : styleable.getPseudoClassStates();
1677 
1678         // RT-20145 - if looking for font size and the node has a font,
1679         // use the font property's value if it was set by the user and
1680         // there is not an inline or author style.
1681 
1682         if (cacheContainer.fontProp != null) {
1683             StyleableProperty<Font> styleableProp = cacheContainer.fontProp.getStyleableProperty(styleable);
1684             StyleOrigin fpOrigin = styleableProp.getStyleOrigin();
1685             Font font = styleableProp.getValue();
1686             if (font == null) font = Font.getDefault();
1687             if (fpOrigin == StyleOrigin.USER) {
1688                 origin = fpOrigin;
1689                 family = getFontFamily(font);
1690                 size = font.getSize();
1691                 weight = getFontWeight(font);
1692                 posture = getFontPosture(font);
1693                 cvFont = new CalculatedValue(font, fpOrigin, false);
1694             }
1695         }
1696 
1697         CalculatedValue parentCachedFont = getCachedFont(styleable.getStyleableParent());
1698         if (parentCachedFont == null) parentCachedFont = new CalculatedValue(Font.getDefault(), null, false);
1699 
1700         //
1701         // Look up the font- properties
1702         //
1703         CascadingStyle fontShorthand = getStyle(styleable, property, styleMap, states);
1704 
1705         // don't look past current node for font shorthand if user set the font
1706         if (fontShorthand == null && origin != StyleOrigin.USER) {
1707 
1708             Styleable parent = styleable != null ? styleable.getStyleableParent() : null;
1709 
1710             while (parent != null) { // This loop traverses through all ancestors till root
1711 
1712                 CssStyleHelper parentStyleHelper = parent instanceof Node ? ((Node)parent).styleHelper : null;
1713                 if (parentStyleHelper != null) {
1714 
1715                     distance += 1;
1716 
1717                     StyleMap parentStyleMap = parentStyleHelper.getStyleMap(parent);
1718                     Set<PseudoClass> transitionStates = ((Node)parent).pseudoClassStates;
1719                     CascadingStyle cascadingStyle = parentStyleHelper.getStyle(parent, property, parentStyleMap, transitionStates);
1720 
1721                     if (cascadingStyle != null) {
1722 
1723                         final ParsedValue cssValue = cascadingStyle.getParsedValue();
1724 
1725                         if ("inherit".equals(cssValue.getValue()) == false) {
1726                             fontShorthand = cascadingStyle;
1727                             break;
1728                         }
1729                     }
1730 
1731                 }
1732 
1733                 parent = parent.getStyleableParent();
1734 
1735             }
1736 
1737         }
1738 
1739         if (fontShorthand != null) {
1740 
1741             //
1742             // If we don't have an existing font, or if the origin of the
1743             // existing font is less than that of the shorthand, then
1744             // take the shorthand. If the origins compare equals, then take
1745             // the shorthand since the fontProp value will not have been
1746             // updated yet.
1747             //
1748             if (origin == null || origin.compareTo(fontShorthand.getOrigin()) <= 0) {
1749 
1750                 final CalculatedValue cv =
1751                         calculateValue(fontShorthand, styleable, dummyFontProperty,
1752                                 styleMap, states, styleable, parentCachedFont);
1753 
1754                 // cv could be SKIP
1755                 if (cv.getValue() instanceof Font) {
1756                     origin = cv.getOrigin();
1757                     Font font = (Font)cv.getValue();
1758                     family = getFontFamily(font);
1759                     size = font.getSize();
1760                     weight = getFontWeight(font);
1761                     posture = getFontPosture(font);
1762                     cvFont = cv;
1763                     foundStyle = true;
1764                 }
1765 
1766             }
1767         }
1768 
1769         CascadingStyle fontSize = getStyle(styleable, property.concat("-size"), styleMap, states);
1770         if (fontSize != null) {
1771             // if we have a font shorthand and it is more specific than font-size, then don't use the font-size style
1772             if (fontShorthand != null && fontShorthand.compareTo(fontSize) < 0) {
1773                 fontSize = null;
1774             } else if (origin == StyleOrigin.USER) {
1775                 // If fontSize is an inline or author-stylesheet style, use it.
1776                 // Otherwise, fontSize is a user-agent stylesheet style and should not override the USER style.
1777                 if (StyleOrigin.USER.compareTo(fontSize.getOrigin()) > 0) {
1778                     fontSize = null;
1779                 }
1780             }
1781         } else if (origin != StyleOrigin.USER) {
1782             //
1783             // If we don't have a font-size, see if there is an inherited font-size.
1784             // If lookupInheritedFontProperty returns other than null, then we know that font-size is closer (more specific)
1785             // than the font shorthand
1786             //
1787             fontSize = lookupInheritedFontProperty(styleable, property.concat("-size"), styleMap, distance, fontShorthand);
1788         }
1789 
1790         if (fontSize != null) {
1791 
1792             // The logic above ensures that, if fontSize is not null, then it is either
1793             // 1) a style matching this node and is more specific than the font shorthand or
1794             // 2) an inherited style that is more specific than the font shorthand
1795             // and, therefore, we can use the fontSize style
1796 
1797             final CalculatedValue cv =
1798                     calculateValue(fontSize, styleable, dummyFontProperty,
1799                             styleMap, states, styleable, parentCachedFont);
1800 
1801             if (cv.getValue() instanceof Double) {
1802                 if (origin == null || origin.compareTo(fontSize.getOrigin()) <= 0) {
1803 
1804                     origin = cv.getOrigin();
1805                 }
1806                 size = (Double) cv.getValue();
1807 
1808                 if (cvFont != null) {
1809                     boolean isRelative = cvFont.isRelative() || cv.isRelative();
1810                     Font font = deriveFont((Font) cvFont.getValue(), family, weight, posture, size);
1811                     cvFont = new CalculatedValue(font, origin, isRelative);
1812                 } else {
1813                     boolean isRelative = cv.isRelative();
1814                     Font font = deriveFont(Font.getDefault(), family, weight, posture, size);
1815                     cvFont = new CalculatedValue(font, origin, isRelative);
1816                 }
1817                 foundStyle = true;
1818             }
1819 
1820         }
1821 
1822         // if cachedFont is null, then we're in this method to look up a font for the CacheContainer's fontSizeCache
1823         // and we only care about font-size or the size from font shorthand.
1824         if (cachedFont == null) {
1825             return (cvFont != null) ? cvFont : SKIP;
1826         }
1827 
1828         CascadingStyle fontWeight = getStyle(styleable, property.concat("-weight"), styleMap, states);
1829         if (fontWeight != null) {
1830             // if we have a font shorthand and it is more specific than font-weight, then don't use the font-weight style
1831             if (fontShorthand != null && fontShorthand.compareTo(fontWeight) < 0) {
1832                 fontWeight = null;
1833             }
1834 
1835         } else if (origin != StyleOrigin.USER) {
1836             //
1837             // If we don't have a font-weight, see if there is an inherited font-weight.
1838             // If lookupInheritedFontProperty returns other than null, then we know that font-weight is closer (more specific)
1839             // than the font shorthand
1840             //
1841             fontWeight = lookupInheritedFontProperty(styleable, property.concat("-weight"), styleMap, distance, fontShorthand);
1842         }
1843 
1844         if (fontWeight != null) {
1845 
1846             // The logic above ensures that, if fontWeight is not null, then it is either
1847             // 1) a style matching this node and is more specific than the font shorthand or
1848             // 2) an inherited style that is more specific than the font shorthand
1849             // and, therefore, we can use the fontWeight style
1850 
1851             final CalculatedValue cv =
1852                     calculateValue(fontWeight, styleable, dummyFontProperty,
1853                             styleMap, states, styleable, null);
1854 
1855             if (cv.getValue() instanceof FontWeight) {
1856                 if (origin == null || origin.compareTo(fontWeight.getOrigin()) <= 0) {
1857                     origin = cv.getOrigin();
1858                 }
1859                 weight = (FontWeight)cv.getValue();
1860                 foundStyle = true;
1861             }
1862         }
1863 
1864 
1865         CascadingStyle fontStyle = getStyle(styleable, property.concat("-style"), styleMap, states);
1866         if (fontStyle != null) {
1867             // if we have a font shorthand and it is more specific than font-style, then don't use the font-style style
1868             if (fontShorthand != null && fontShorthand.compareTo(fontStyle) < 0) {
1869                 fontStyle = null;
1870             }
1871 
1872         } else if (origin != StyleOrigin.USER) {
1873             //
1874             // If we don't have a font-style, see if there is an inherited font-style.
1875             // If lookupInheritedFontProperty returns other than null, then we know that font-style is closer (more specific)
1876             // than the font shorthand
1877             //
1878             fontStyle = lookupInheritedFontProperty(styleable, property.concat("-style"), styleMap, distance, fontShorthand);
1879         }
1880 
1881         if (fontStyle != null) {
1882 
1883             // The logic above ensures that, if fontStyle is not null, then it is either
1884             // 1) a style matching this node and is more specific than the font shorthand or
1885             // 2) an inherited style that is more specific than the font shorthand
1886             // and, therefore, we can use the fontStyle style
1887 
1888             final CalculatedValue cv =
1889                     calculateValue(fontStyle, styleable, dummyFontProperty,
1890                             styleMap, states, styleable, null);
1891 
1892             if (cv.getValue() instanceof FontPosture) {
1893                 if (origin == null || origin.compareTo(fontStyle.getOrigin()) <= 0) {
1894                     origin = cv.getOrigin();
1895                 }
1896                 posture = (FontPosture)cv.getValue();
1897                 foundStyle = true;
1898             }
1899 
1900         }
1901 
1902         CascadingStyle fontFamily = getStyle(styleable, property.concat("-family"), styleMap, states);
1903         if (fontFamily != null) {
1904             // if we have a font shorthand and it is more specific than font-family, then don't use the font-family style
1905             if (fontShorthand != null && fontShorthand.compareTo(fontFamily) < 0) {
1906                 fontFamily = null;
1907             }
1908 
1909         } else if (origin != StyleOrigin.USER) {
1910             //
1911             // If we don't have a font-family, see if there is an inherited font-family.
1912             // If lookupInheritedFontProperty returns other than null, then we know that font-family is closer (more specific)
1913             // than the font shorthand
1914             //
1915             fontFamily = lookupInheritedFontProperty(styleable, property.concat("-family"), styleMap, distance, fontShorthand);
1916         }
1917 
1918         if (fontFamily != null) {
1919 
1920             // The logic above ensures that, if fontFamily is not null, then it is either
1921             // 1) a style matching this node and is more specific than the font shorthand or
1922             // 2) an inherited style that is more specific than the font shorthand
1923             // and, therefore, we can use the fontFamily style
1924 
1925             final CalculatedValue cv =
1926                     calculateValue(fontFamily, styleable, dummyFontProperty,
1927                             styleMap, states, styleable, null);
1928 
1929             if (cv.getValue() instanceof String) {
1930                 if (origin == null || origin.compareTo(fontFamily.getOrigin()) <= 0) {
1931                     origin = cv.getOrigin();
1932                 }
1933                 family = (String)cv.getValue();
1934                 foundStyle = true;
1935             }
1936 
1937         }
1938 
1939         if (foundStyle) {
1940 
1941             Font font = cvFont != null ? (Font)cvFont.getValue() : Font.getDefault();
1942             Font derivedFont = deriveFont(font, family, weight, posture, size);
1943             return new CalculatedValue(derivedFont,origin,false);
1944 
1945         }
1946 
1947         return SKIP;
1948     }
1949 
1950     private CascadingStyle lookupInheritedFontProperty(
1951             final Styleable styleable,
1952             final String property,
1953             final StyleMap styleMap,
1954             final int distance,
1955             CascadingStyle fontShorthand) {
1956 
1957         Styleable parent = styleable != null ? styleable.getStyleableParent() : null;
1958 
1959         int nlooks = distance;
1960         while (parent != null && nlooks > 0) { // This loop traverses through all ancestors till root
1961 
1962             CssStyleHelper parentStyleHelper = parent instanceof Node ? ((Node)parent).styleHelper : null;
1963             if (parentStyleHelper != null) {
1964 
1965                 nlooks -= 1;
1966 
1967                 StyleMap parentStyleMap = parentStyleHelper.getStyleMap((parent));
1968                 Set<PseudoClass> transitionStates = ((Node)parent).pseudoClassStates;
1969                 CascadingStyle cascadingStyle = parentStyleHelper.getStyle(parent, property, parentStyleMap, transitionStates);
1970 
1971                 if (cascadingStyle != null) {
1972 
1973                     // If we are closer to the node than the font shorthand, then font shorthand doesn't matter.
1974                     // If the font shorthand and this style are the same distance, then we need to compare.
1975                     if (fontShorthand != null && nlooks == 0) {
1976                         if (fontShorthand.compareTo(cascadingStyle) < 0) {
1977                             return null;
1978                         }
1979                     }
1980 
1981                     final ParsedValue cssValue = cascadingStyle.getParsedValue();
1982 
1983                     if ("inherit".equals(cssValue.getValue()) == false) {
1984                         return cascadingStyle;
1985                     }
1986                 }
1987 
1988             }
1989 
1990             parent = parent.getStyleableParent();
1991 
1992         }
1993 
1994         return null;
1995     }
1996 
1997 
1998     /**
1999      * Called from Node NodeHelper.getMatchingStyles
2000      * @param styleable
2001      * @param styleableProperty
2002      * @return
2003      */
2004     static List<Style> getMatchingStyles(final Styleable styleable, final CssMetaData styleableProperty) {
2005 
2006         if (!(styleable instanceof Node)) return Collections.<Style>emptyList();
2007 
2008         Node node = (Node)styleable;
2009         final CssStyleHelper helper = (node.styleHelper != null) ? node.styleHelper : createStyleHelper(node);
2010 
2011         if (helper != null) {
2012             return helper.getMatchingStyles(node, styleableProperty, false);
2013         }
2014         else {
2015             return Collections.<Style>emptyList();
2016         }
2017     }
2018 
2019     static Map<StyleableProperty<?>, List<Style>> getMatchingStyles(Map<StyleableProperty<?>, List<Style>> map, final Node node) {
2020 
2021         final CssStyleHelper helper = (node.styleHelper != null) ? node.styleHelper : createStyleHelper(node);
2022         if (helper != null) {
2023             if (map == null) map = new HashMap<>();
2024             for (CssMetaData metaData : node.getCssMetaData()) {
2025                 List<Style> styleList = helper.getMatchingStyles(node, metaData, true);
2026                 if (styleList != null && !styleList.isEmpty()) {
2027                     StyleableProperty prop = metaData.getStyleableProperty(node);
2028                     map.put(prop, styleList);
2029                 }
2030             }
2031         }
2032 
2033         if (node instanceof Parent) {
2034             for (Node child : ((Parent)node).getChildren()) {
2035                 map = getMatchingStyles(map, child);
2036             }
2037         }
2038 
2039         return map;
2040     }
2041 
2042     private List<Style> getMatchingStyles(final Styleable node, final CssMetaData styleableProperty, boolean matchState) {
2043 
2044         final List<CascadingStyle> styleList = new ArrayList<>();
2045 
2046         getMatchingStyles(node, styleableProperty, styleList, matchState);
2047 
2048         List<CssMetaData<? extends Styleable, ?>> subProperties = styleableProperty.getSubProperties();
2049         if (subProperties != null) {
2050             for (int n=0,nMax=subProperties.size(); n<nMax; n++) {
2051                 final CssMetaData subProperty = subProperties.get(n);
2052                 getMatchingStyles(node, subProperty, styleList, matchState);
2053             }
2054         }
2055 
2056         Collections.sort(styleList);
2057 
2058         final List<Style> matchingStyles = new ArrayList<>(styleList.size());
2059         for (int n=0,nMax=styleList.size(); n<nMax; n++) {
2060             final Style style = styleList.get(n).getStyle();
2061             if (!matchingStyles.contains(style)) matchingStyles.add(style);
2062         }
2063 
2064         return matchingStyles;
2065     }
2066 
2067     private void getMatchingStyles(final Styleable node, final CssMetaData styleableProperty, final List<CascadingStyle> styleList, boolean matchState) {
2068 
2069         if (node != null) {
2070 
2071             String property = styleableProperty.getProperty();
2072             Node _node = node instanceof Node ? (Node)node : null;
2073             final StyleMap smap = getStyleMap(_node);
2074             if (smap == null) return;
2075 
2076             if (matchState) {
2077                 CascadingStyle cascadingStyle = getStyle(node, styleableProperty.getProperty(), smap, _node.pseudoClassStates);
2078                 if (cascadingStyle != null) {
2079                     styleList.add(cascadingStyle);
2080                     final ParsedValue parsedValue = cascadingStyle.getParsedValue();
2081                     getMatchingLookupStyles(node, parsedValue, styleList, matchState);
2082                 }
2083             }  else {
2084 
2085                 Map<String, List<CascadingStyle>> cascadingStyleMap = smap.getCascadingStyles();
2086                 // StyleMap.getCascadingStyles() does not return null
2087                 List<CascadingStyle> styles = cascadingStyleMap.get(property);
2088 
2089                 if (styles != null) {
2090                     styleList.addAll(styles);
2091                     for (int n=0, nMax=styles.size(); n<nMax; n++) {
2092                         final CascadingStyle style = styles.get(n);
2093                         final ParsedValue parsedValue = style.getParsedValue();
2094                         getMatchingLookupStyles(node, parsedValue, styleList, matchState);
2095                     }
2096                 }
2097             }
2098 
2099             if (styleableProperty.isInherits()) {
2100                 Styleable parent = node.getStyleableParent();
2101                 while (parent != null) { // This loop traverses through all ancestors till root
2102                     CssStyleHelper parentHelper = parent instanceof Node
2103                             ? ((Node)parent).styleHelper
2104                             : null;
2105                     if (parentHelper != null) {
2106                         parentHelper.getMatchingStyles(parent, styleableProperty, styleList, matchState);
2107                     }
2108                     parent = parent.getStyleableParent();
2109                 }
2110             }
2111 
2112         }
2113 
2114     }
2115 
2116     // Pretty much a duplicate of resolveLookups, but without the state
2117     private void getMatchingLookupStyles(final Styleable node, final ParsedValue parsedValue, final List<CascadingStyle> styleList, boolean matchState) {
2118 
2119         if (parsedValue.isLookup()) {
2120 
2121             Object value = parsedValue.getValue();
2122 
2123             if (value instanceof String) {
2124 
2125                 final String property = (String)value;
2126                 // gather up any and all styles that contain this value as a property
2127                 Styleable parent = node;
2128                 do {
2129 
2130                     final Node _parent = parent instanceof Node ? (Node)parent : null;
2131                     final CssStyleHelper helper = _parent != null
2132                             ? _parent.styleHelper
2133                             : null;
2134                     if (helper != null) {
2135 
2136                         StyleMap styleMap = helper.getStyleMap(parent);
2137                         if (styleMap == null || styleMap.isEmpty()) continue;
2138 
2139                         final int start = styleList.size();
2140 
2141                         if (matchState) {
2142                             CascadingStyle cascadingStyle = helper.resolveRef(_parent, property, styleMap, _parent.pseudoClassStates);
2143                             if (cascadingStyle != null) {
2144                                 styleList.add(cascadingStyle);
2145                             }
2146                         } else {
2147                             final Map<String, List<CascadingStyle>> smap = styleMap.getCascadingStyles();
2148                             // getCascadingStyles does not return null
2149                             List<CascadingStyle> styles = smap.get(property);
2150 
2151                             if (styles != null) {
2152                                 styleList.addAll(styles);
2153                             }
2154 
2155                         }
2156 
2157                         final int end = styleList.size();
2158 
2159                         for (int index=start; index<end; index++) {
2160                             final CascadingStyle style = styleList.get(index);
2161                             getMatchingLookupStyles(parent, style.getParsedValue(), styleList, matchState);
2162                         }
2163                     }
2164 
2165                 } while ((parent = parent.getStyleableParent()) != null); // This loop traverses through all ancestors till root
2166 
2167             }
2168         }
2169 
2170         // If the value doesn't contain any values that need lookup, then bail
2171         if (!parsedValue.isContainsLookups()) {
2172             return;
2173         }
2174 
2175         final Object val = parsedValue.getValue();
2176         if (val instanceof ParsedValue[][]) {
2177         // If ParsedValue is a layered sequence of values, resolve the lookups for each.
2178             final ParsedValue[][] layers = (ParsedValue[][])val;
2179             for (int l=0; l<layers.length; l++) {
2180                 for (int ll=0; ll<layers[l].length; ll++) {
2181                     if (layers[l][ll] == null) continue;
2182                         getMatchingLookupStyles(node, layers[l][ll], styleList, matchState);
2183                 }
2184             }
2185 
2186         } else if (val instanceof ParsedValue[]) {
2187         // If ParsedValue is a sequence of values, resolve the lookups for each.
2188             final ParsedValue[] layer = (ParsedValue[])val;
2189             for (int l=0; l<layer.length; l++) {
2190                 if (layer[l] == null) continue;
2191                     getMatchingLookupStyles(node, layer[l], styleList, matchState);
2192             }
2193         }
2194 
2195     }
2196 
2197 }