    rev 4839 : RT-2529: Locale specific runtime errors (Turkish)

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