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