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