modules/graphics/src/main/java/javafx/scene/CssStyleHelper.java

Print this page
rev 9240 : 8076423: JEP 253: Prepare JavaFX UI Controls & CSS APIs for Modularization


  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package javafx.scene;
  26 
  27 import java.util.ArrayList;
  28 import java.util.Collections;
  29 import java.util.HashMap;
  30 import java.util.HashSet;
  31 import java.util.List;
  32 import java.util.Locale;
  33 import java.util.Map;
  34 import java.util.Map.Entry;
  35 import java.util.Set;
  36 
  37 import javafx.beans.property.ObjectProperty;
  38 import javafx.beans.property.SimpleObjectProperty;
  39 import javafx.beans.value.WritableValue;

  40 import javafx.css.CssMetaData;

  41 import javafx.css.FontCssMetaData;
  42 import javafx.css.ParsedValue;
  43 import javafx.css.PseudoClass;



  44 import javafx.css.StyleConverter;
  45 import javafx.css.StyleOrigin;
  46 import javafx.css.Styleable;
  47 import javafx.css.StyleableProperty;

  48 import javafx.scene.text.Font;
  49 import javafx.scene.text.FontPosture;
  50 import javafx.scene.text.FontWeight;
  51 import com.sun.javafx.util.Logging;
  52 import com.sun.javafx.util.Utils;
  53 import com.sun.javafx.css.CalculatedValue;
  54 import com.sun.javafx.css.CascadingStyle;
  55 import com.sun.javafx.css.CssError;
  56 import com.sun.javafx.css.ParsedValueImpl;
  57 import com.sun.javafx.css.PseudoClassState;
  58 import com.sun.javafx.css.Rule;
  59 import com.sun.javafx.css.Selector;
  60 import com.sun.javafx.css.Style;
  61 import com.sun.javafx.css.StyleCache;
  62 import com.sun.javafx.css.StyleCacheEntry;
  63 import com.sun.javafx.css.StyleConverterImpl;
  64 import com.sun.javafx.css.StyleManager;
  65 import com.sun.javafx.css.StyleMap;
  66 import com.sun.javafx.css.Stylesheet;
  67 import com.sun.javafx.css.converters.FontConverter;
  68 import sun.util.logging.PlatformLogger;

  69 import sun.util.logging.PlatformLogger.Level;

  70 
  71 import static com.sun.javafx.css.CalculatedValue.*;
  72 
  73 /**
  74  * The StyleHelper is a helper class used for applying CSS information to Nodes.
  75  */
  76 final class CssStyleHelper {
  77 
  78     private static final PlatformLogger LOGGER = com.sun.javafx.util.Logging.getCSSLogger();
  79 
  80     private CssStyleHelper() {
  81         this.triggerStates = new PseudoClassState();
  82     }
  83 
  84     /**
  85      * Creates a new StyleHelper.
  86      */
  87     static CssStyleHelper createStyleHelper(final Node node) {
  88 
  89         // need to know how far we are to root in order to init arrays.


 615 
 616         final StyleCacheEntry.Key cacheEntryKey = new StyleCacheEntry.Key(transitionStates, fontForRelativeSizes);
 617         StyleCacheEntry cacheEntry = sharedCache.getStyleCacheEntry(cacheEntryKey);
 618 
 619         // if the cacheEntry already exists, take the fastpath
 620         final boolean fastpath = cacheEntry != null;
 621 
 622         if (cacheEntry == null) {
 623             cacheEntry = new StyleCacheEntry();
 624             sharedCache.addStyleCacheEntry(cacheEntryKey, cacheEntry);
 625         }
 626 
 627         final List<CssMetaData<? extends Styleable,  ?>> styleables = node.getCssMetaData();
 628 
 629         // Used in the for loop below, and a convenient place to stop when debugging.
 630         final int max = styleables.size();
 631 
 632         final boolean isForceSlowpath = cacheContainer.forceSlowpath;
 633         cacheContainer.forceSlowpath = false;
 634 
 635         // RT-20643
 636         CssError.setCurrentScene(node.getScene());
 637 
 638         // For each property that is settable, we need to do a lookup and
 639         // transition to that value.
 640         for(int n=0; n<max; n++) {
 641 
 642             @SuppressWarnings("unchecked") // this is a widening conversion
 643             final CssMetaData<Styleable,Object> cssMetaData =
 644                     (CssMetaData<Styleable,Object>)styleables.get(n);
 645 
 646             // Don't bother looking up styles that don't inherit.
 647             if (inheritOnly && cssMetaData.isInherits() == false) {
 648                 continue;
 649             }
 650 
 651             // Skip the lookup if we know there isn't a chance for this property
 652             // to be set (usually due to a "bind").
 653             if (!cssMetaData.isSettable(node)) continue;
 654 
 655             final String property = cssMetaData.getProperty();
 656 
 657             CalculatedValue calculatedValue = cacheEntry.get(property);


 771                                 String.valueOf(value) + ", originOfCalculatedValue=" + originOfCalculatedValue);
 772                     }
 773 
 774                     styleableProperty.applyStyle(originOfCalculatedValue, value);
 775 
 776                     if (cacheContainer.cssSetProperties.containsKey(cssMetaData) == false) {
 777                         // track this property
 778                         CalculatedValue initialValue = new CalculatedValue(currentValue, originOfCurrentValue, false);
 779                         cacheContainer.cssSetProperties.put(cssMetaData, initialValue);
 780                     }
 781 
 782                 }
 783 
 784             } catch (Exception e) {
 785 
 786                 StyleableProperty styleableProperty = cssMetaData.getStyleableProperty(node);
 787 
 788                 final String msg = String.format("Failed to set css [%s] on [%s] due to '%s'\n",
 789                         cssMetaData.getProperty(), styleableProperty, e.getMessage());
 790 
 791                 List<CssError> errors = null;
 792                 if ((errors = StyleManager.getErrors()) != null) {
 793                     final CssError error = new CssError.PropertySetError(cssMetaData, node, msg);
 794                     errors.add(error);
 795                 }
 796 
 797                 PlatformLogger logger = Logging.getCSSLogger();
 798                 if (logger.isLoggable(Level.WARNING)) {
 799                     logger.warning(msg);
 800                 }
 801 
 802                 // RT-27155: if setting value raises exception, reset value
 803                 // the value to initial and thereafter skip setting the property
 804                 cacheEntry.put(property, SKIP);
 805 
 806                 CalculatedValue cachedValue = null;
 807                 if (cacheContainer != null && cacheContainer.cssSetProperties != null) {
 808                     cachedValue = cacheContainer.cssSetProperties.get(cssMetaData);
 809                 }
 810                 Object value = (cachedValue != null) ? cachedValue.getValue() : cssMetaData.getInitialValue(node);
 811                 StyleOrigin origin = (cachedValue != null) ? cachedValue.getOrigin() : null;
 812                 try {
 813                     styleableProperty.applyStyle(origin, value);
 814                 } catch (Exception ebad) {
 815                     // This would be bad.
 816                     if (logger.isLoggable(Level.SEVERE)) {
 817                         logger.severe(String.format("Could not reset [%s] on [%s] due to %s\n" ,
 818                                 cssMetaData.getProperty(), styleableProperty, e.getMessage()));
 819                     }
 820                 }
 821 
 822             }
 823 
 824         }
 825 
 826         // RT-20643
 827         CssError.setCurrentScene(null);
 828 
 829     }
 830 
 831     /**
 832      * Gets the CSS CascadingStyle for the property of this node in these pseudo-class
 833      * states. A null style may be returned if there is no style information
 834      * for this combination of input parameters.
 835      *
 836      *
 837      * @param styleable
 838      * @param property
 839      * @param styleMap
 840      * @param states   @return
 841      * */
 842     private CascadingStyle getStyle(final Styleable styleable, final String property, final StyleMap styleMap, final Set<PseudoClass> states){
 843 
 844         if (styleMap == null || styleMap.isEmpty()) return null;
 845 
 846         final Map<String, List<CascadingStyle>> cascadingStyleMap = styleMap.getCascadingStyles();
 847         if (cascadingStyleMap == null || cascadingStyleMap.isEmpty()) return null;
 848 


 947                                 ? origin.compareTo(constituent.getOrigin()) < 0
 948                                 : constituent.getOrigin() != null) {
 949                             origin = constituent.getOrigin();
 950                         }
 951 
 952                         // if the constiuent uses relative sizes, then
 953                         // isRelative is true;
 954                         isRelative = isRelative || constituent.isRelative();
 955 
 956                     }
 957                 }
 958 
 959                 // If there are no subkeys which apply...
 960                 if (subs == null || subs.isEmpty()) {
 961                     return handleNoStyleFound(styleable, cssMetaData,
 962                             styleMap, states, originatingStyleable, cachedFont);
 963                 }
 964 
 965                 try {
 966                     final StyleConverter keyType = cssMetaData.getConverter();
 967                     if (keyType instanceof StyleConverterImpl) {
 968                         Object ret = ((StyleConverterImpl)keyType).convert(subs);
 969                         return new CalculatedValue(ret, origin, isRelative);
 970                     } else {
 971                         assert false; // TBD: should an explicit exception be thrown here?
 972                         return SKIP;
 973                     }
 974                 } catch (ClassCastException cce) {
 975                     final String msg = formatExceptionMessage(styleable, cssMetaData, null, cce);
 976                     List<CssError> errors = null;
 977                     if ((errors = StyleManager.getErrors()) != null) {
 978                         final CssError error = new CssError.PropertySetError(cssMetaData, styleable, msg);
 979                         errors.add(error);
 980                     }
 981                     if (LOGGER.isLoggable(Level.WARNING)) {
 982                         LOGGER.warning(msg);
 983                         LOGGER.fine("caught: ", cce);
 984                         LOGGER.fine("styleable = " + cssMetaData);
 985                         LOGGER.fine("node = " + styleable.toString());
 986                     }
 987                     return SKIP;
 988                 }
 989             }
 990 
 991         } else { // style != null
 992 
 993             // RT-10522:
 994             // If the user set the property and there is a style and
 995             // the style came from the user agent stylesheet, then
 996             // skip the value. A style from a user agent stylesheet should
 997             // not override the user set style.
 998             if (style.getOrigin() == StyleOrigin.USER_AGENT) {
 999 
1000                 StyleableProperty styleableProperty = cssMetaData.getStyleableProperty(originatingStyleable);
1001                 // if styleableProperty is null, then we're dealing with a sub-property.
1002                 if (styleableProperty != null && styleableProperty.getStyleOrigin() == StyleOrigin.USER) {
1003                     return SKIP;
1004                 }
1005             }
1006 
1007             // If there was a style found, then we want to check whether the
1008             // value was "inherit". If so, then we will simply inherit.
1009             final ParsedValueImpl cssValue = style.getParsedValueImpl();
1010             if (cssValue != null && "inherit".equals(cssValue.getValue())) {
1011                 style = getInheritedStyle(styleable, property);
1012                 if (style == null) return SKIP;
1013             }
1014         }
1015 
1016 //        System.out.println("lookup " + property +
1017 //                ", selector = \'" + style.selector.toString() + "\'" +
1018 //                ", node = " + node.toString());
1019 
1020         return calculateValue(style, styleable, cssMetaData, styleMap, states,
1021                 originatingStyleable, cachedFont);
1022     }
1023 
1024     /**
1025      * Called when there is no style found.
1026      */
1027     private CalculatedValue handleNoStyleFound(final Styleable styleable,
1028                                                final CssMetaData cssMetaData,
1029                                                final StyleMap styleMap, Set<PseudoClass> pseudoClassStates, Styleable originatingStyleable,


1063     /**
1064      * Called when we must getInheritedStyle a value from a parent node in the scenegraph.
1065      */
1066     private CascadingStyle getInheritedStyle(
1067             final Styleable styleable,
1068             final String property) {
1069 
1070         Styleable parent = styleable != null ? styleable.getStyleableParent() : null;
1071 
1072         while (parent != null) {
1073 
1074             CssStyleHelper parentStyleHelper = parent instanceof Node ? ((Node)parent).styleHelper : null;
1075             if (parentStyleHelper != null) {
1076 
1077                 StyleMap parentStyleMap = parentStyleHelper.getStyleMap(parent);
1078                 Set<PseudoClass> transitionStates = ((Node)parent).pseudoClassStates;
1079                 CascadingStyle cascadingStyle = parentStyleHelper.getStyle(parent, property, parentStyleMap, transitionStates);
1080 
1081                 if (cascadingStyle != null) {
1082 
1083                     final ParsedValueImpl cssValue = cascadingStyle.getParsedValueImpl();
1084 
1085                     if ("inherit".equals(cssValue.getValue())) {
1086                         return getInheritedStyle(parent, property);
1087                     }
1088                     return cascadingStyle;
1089                 }
1090 
1091                 return null;
1092             }
1093 
1094             parent = parent.getStyleableParent();
1095 
1096         }
1097 
1098         return null;
1099     }
1100 
1101 
1102     // helps with self-documenting the code
1103     private static final Set<PseudoClass> NULL_PSEUDO_CLASS_STATE = null;


1131                     }
1132                 }
1133 
1134                 if (styleableParent == null || parentStyleHelper == null) {
1135                     return null;
1136                 }
1137 
1138                 StyleMap parentStyleMap = parentStyleHelper.getStyleMap(styleableParent);
1139                 Set<PseudoClass> styleableParentPseudoClassStates =
1140                     styleableParent instanceof Node
1141                         ? ((Node)styleableParent).pseudoClassStates
1142                         : styleable.getPseudoClassStates();
1143 
1144                 return parentStyleHelper.resolveRef(styleableParent, property,
1145                         parentStyleMap, styleableParentPseudoClassStates);
1146             }
1147         }
1148     }
1149 
1150     // to resolve a lookup, we just need to find the parsed value.
1151     private ParsedValueImpl resolveLookups(
1152             final Styleable styleable,
1153             final ParsedValueImpl parsedValue,
1154             final StyleMap styleMap, Set<PseudoClass> states,
1155             final ObjectProperty<StyleOrigin> whence,
1156             Set<ParsedValue> resolves) {
1157 
1158         //
1159         // either the value itself is a lookup, or the value contain a lookup
1160         //
1161         if (parsedValue.isLookup()) {
1162 
1163             // The value we're looking for should be a Paint, one of the
1164             // containers for linear, radial or ladder, or a derived color.
1165             final Object val = parsedValue.getValue();
1166             if (val instanceof String) {
1167 
1168                 final String sval = ((String) val).toLowerCase(Locale.ROOT);
1169 
1170                 CascadingStyle resolved =
1171                     resolveRef(styleable, sval, styleMap, states);
1172 
1173                 if (resolved != null) {
1174 
1175                     if (resolves.contains(resolved.getParsedValueImpl())) {
1176 
1177                         if (LOGGER.isLoggable(Level.WARNING)) {
1178                             LOGGER.warning("Loop detected in " + resolved.getRule().toString() + " while resolving '" + sval + "'");
1179                         }
1180                         throw new IllegalArgumentException("Loop detected in " + resolved.getRule().toString() + " while resolving '" + sval + "'");
1181 
1182                     } else {
1183                         resolves.add(parsedValue);
1184                     }
1185 
1186                     // The origin of this parsed value is the greatest of
1187                     // any of the resolved reference. If a resolved reference
1188                     // comes from an inline style, for example, then the value
1189                     // calculated from the resolved lookup should have inline
1190                     // as its origin. Otherwise, an inline style could be
1191                     // stored in shared cache.
1192                     final StyleOrigin wOrigin = whence.get();
1193                     final StyleOrigin rOrigin = resolved.getOrigin();
1194                     if (rOrigin != null && (wOrigin == null ||  wOrigin.compareTo(rOrigin) < 0)) {
1195                         whence.set(rOrigin);
1196                     }
1197 
1198                     // the resolved value may itself need to be resolved.
1199                     // For example, if the value "color" resolves to "base",
1200                     // then "base" will need to be resolved as well.
1201                     ParsedValueImpl pv = resolveLookups(styleable, resolved.getParsedValueImpl(), styleMap, states, whence, resolves);
1202 
1203                     if (resolves != null) {
1204                         resolves.remove(parsedValue);
1205                     }
1206 
1207                     return pv;
1208 
1209                 }
1210             }
1211         }
1212 
1213         // If the value doesn't contain any values that need lookup, then bail
1214         if (!parsedValue.isContainsLookups()) {
1215             return parsedValue;
1216         }
1217 
1218         final Object val = parsedValue.getValue();
1219 
1220         if (val instanceof ParsedValueImpl[][]) {
1221 
1222             // If ParsedValueImpl is a layered sequence of values, resolve the lookups for each.
1223             final ParsedValueImpl[][] layers = (ParsedValueImpl[][])val;
1224             ParsedValueImpl[][] resolved = new ParsedValueImpl[layers.length][0];
1225             for (int l=0; l<layers.length; l++) {
1226                 resolved[l] = new ParsedValueImpl[layers[l].length];
1227                 for (int ll=0; ll<layers[l].length; ll++) {
1228                     if (layers[l][ll] == null) continue;
1229                     resolved[l][ll] =
1230                         resolveLookups(styleable, layers[l][ll], styleMap, states, whence, resolves);
1231                 }
1232             }
1233 
1234             resolves.clear();
1235 
1236             return new ParsedValueImpl(resolved, parsedValue.getConverter(), false);
1237 
1238         } else if (val instanceof ParsedValueImpl[]) {
1239 
1240             // If ParsedValueImpl is a sequence of values, resolve the lookups for each.
1241             final ParsedValueImpl[] layer = (ParsedValueImpl[])val;
1242             ParsedValueImpl[] resolved = new ParsedValueImpl[layer.length];
1243             for (int l=0; l<layer.length; l++) {
1244                 if (layer[l] == null) continue;
1245                 resolved[l] =
1246                     resolveLookups(styleable, layer[l], styleMap, states, whence, resolves);
1247             }
1248 
1249             resolves.clear();
1250 
1251             return new ParsedValueImpl(resolved, parsedValue.getConverter(), false);
1252 
1253         }
1254 
1255         return parsedValue;
1256 
1257     }
1258 
1259     private String getUnresolvedLookup(final ParsedValueImpl resolved) {
1260 
1261         Object value = resolved.getValue();
1262 
1263         if (resolved.isLookup() && value instanceof String) {
1264             return (String)value;
1265         }
1266 
1267         if (value instanceof ParsedValueImpl[][]) {
1268             final ParsedValueImpl[][] layers = (ParsedValueImpl[][])value;
1269             for (int l=0; l<layers.length; l++) {
1270                 for (int ll=0; ll<layers[l].length; ll++) {
1271                     if (layers[l][ll] == null) continue;
1272                     String unresolvedLookup = getUnresolvedLookup(layers[l][ll]);
1273                     if (unresolvedLookup != null) return unresolvedLookup;
1274                 }
1275             }
1276 
1277         } else if (value instanceof ParsedValueImpl[]) {
1278         // If ParsedValueImpl is a sequence of values, resolve the lookups for each.
1279             final ParsedValueImpl[] layer = (ParsedValueImpl[])value;
1280             for (int l=0; l<layer.length; l++) {
1281                 if (layer[l] == null) continue;
1282                 String unresolvedLookup = getUnresolvedLookup(layer[l]);
1283                 if (unresolvedLookup != null) return unresolvedLookup;
1284             }
1285         }
1286 
1287         return null;
1288     }
1289 
1290     private String formatUnresolvedLookupMessage(Styleable styleable, CssMetaData cssMetaData, Style style, ParsedValueImpl resolved, ClassCastException cce) {
1291 
1292         // Find value that could not be looked up. If the resolved value does not contain lookups, then the
1293         // ClassCastException is not because of trying to convert a String (which is the missing lookup)
1294         // to some value, but is because the convert method got some wrong value - like a paint when it should be a color.
1295         // See RT-33319 for an example of this.
1296         String missingLookup = resolved != null && resolved.isContainsLookups() ? getUnresolvedLookup(resolved) : null;
1297 
1298         StringBuilder sbuf = new StringBuilder();
1299         if (missingLookup != null) {
1300             sbuf.append("Could not resolve '")
1301                     .append(missingLookup)
1302                     .append("'")
1303                     .append(" while resolving lookups for '")
1304                     .append(cssMetaData.getProperty())
1305                     .append("'");
1306         } else {
1307             sbuf.append("Caught '")
1308                     .append(cce)
1309                     .append("'")
1310                     .append(" while converting value for '")


1355                         .append(styleable.toString());
1356             } else {
1357                 sbuf.append(" from style '")
1358                     .append(String.valueOf(style))
1359                     .append("'");
1360             }
1361         }
1362 
1363         return sbuf.toString();
1364     }
1365 
1366 
1367     private CalculatedValue calculateValue(
1368             final CascadingStyle style,
1369             final Styleable styleable,
1370             final CssMetaData cssMetaData,
1371             final StyleMap styleMap, final Set<PseudoClass> states,
1372             final Styleable originatingStyleable,
1373             final CalculatedValue fontFromCacheEntry) {
1374 
1375         final ParsedValueImpl cssValue = style.getParsedValueImpl();
1376         if (cssValue != null && !("null".equals(cssValue.getValue()) || "none".equals(cssValue.getValue()))) {
1377 
1378             ParsedValueImpl resolved = null;
1379             try {
1380 
1381                 ObjectProperty<StyleOrigin> whence = new SimpleObjectProperty<>(style.getOrigin());
1382                 resolved = resolveLookups(styleable, cssValue, styleMap, states, whence, new HashSet<>());
1383 
1384                 final String property = cssMetaData.getProperty();
1385 
1386                 // The computed value
1387                 Object val = null;
1388                 boolean isFontProperty =
1389                         "-fx-font".equals(property) ||
1390                         "-fx-font-size".equals(property);
1391 
1392                 boolean isRelative = ParsedValueImpl.containsFontRelativeSize(resolved, isFontProperty);
1393 
1394                 //
1395                 // Avoid using a font calculated from a relative size
1396                 // to calculate a font with a relative size.
1397                 // For example:
1398                 // Assume the default font size is 13 and we have a style with


1436                             }
1437 
1438                         }
1439 
1440                     } while(fontForFontRelativeSizes == null &&
1441                             (parent = parent.getStyleableParent()) != null);
1442                 }
1443 
1444                 // did we get a fontValue from the preceding block?
1445                 // if not, get it from our cacheEntry or choose the default
1446                 if (fontForFontRelativeSizes == null) {
1447                     if (fontFromCacheEntry != null && fontFromCacheEntry.isRelative() == false) {
1448                         fontForFontRelativeSizes = (Font)fontFromCacheEntry.getValue();
1449                     } else {
1450                         fontForFontRelativeSizes = Font.getDefault();
1451                     }
1452                 }
1453 
1454                 final StyleConverter cssMetaDataConverter = cssMetaData.getConverter();
1455                 // RT-37727 - handling of properties that are insets is wonky. If the property is -fx-inset, then
1456                 // there isn't an issue because the converter assigns the InsetsConverter to the ParsedValueImpl.
1457                 // But -my-insets will parse as an array of numbers and the parser will assign the Size sequence
1458                 // converter to it. So, if the CssMetaData says it uses InsetsConverter, use the InsetsConverter
1459                 // and not the parser assigned converter.
1460                 if (cssMetaDataConverter == StyleConverter.getInsetsConverter()) {
1461                     if (resolved.getValue() instanceof ParsedValue) {
1462                         // If you give the parser "-my-insets: 5;" you end up with a ParsedValueImpl<ParsedValue<?,Size>, Number>
1463                         // and not a ParsedValueImpl<ParsedValue[], Number[]> so here we wrap the value into an array
1464                         // to make the InsetsConverter happy.
1465                         resolved = new ParsedValueImpl(new ParsedValue[] {(ParsedValue)resolved.getValue()}, null, false);
1466                     }
1467                     val = cssMetaDataConverter.convert(resolved, fontForFontRelativeSizes);
1468                 }
1469                 else if (resolved.getConverter() != null)
1470                     val = resolved.convert(fontForFontRelativeSizes);
1471                 else
1472                     val = cssMetaData.getConverter().convert(resolved, fontForFontRelativeSizes);
1473 
1474                 final StyleOrigin origin = whence.get();
1475                 return new CalculatedValue(val, origin, isRelative);
1476 
1477             } catch (ClassCastException cce) {
1478                 final String msg = formatUnresolvedLookupMessage(styleable, cssMetaData, style.getStyle(),resolved, cce);
1479                 List<CssError> errors = null;
1480                 if ((errors = StyleManager.getErrors()) != null) {
1481                     final CssError error = new CssError.PropertySetError(cssMetaData, styleable, msg);
1482                     errors.add(error);
1483                 }
1484                 if (LOGGER.isLoggable(Level.WARNING)) {
1485                     LOGGER.warning(msg);
1486                     LOGGER.fine("node = " + styleable.toString());
1487                     LOGGER.fine("cssMetaData = " + cssMetaData);
1488                     LOGGER.fine("styles = " + getMatchingStyles(styleable, cssMetaData));
1489                 }
1490                 return SKIP;
1491             } catch (IllegalArgumentException iae) {
1492                 final String msg = formatExceptionMessage(styleable, cssMetaData, style.getStyle(), iae);
1493                 List<CssError> errors = null;
1494                 if ((errors = StyleManager.getErrors()) != null) {
1495                     final CssError error = new CssError.PropertySetError(cssMetaData, styleable, msg);
1496                     errors.add(error);
1497                 }
1498                 if (LOGGER.isLoggable(Level.WARNING)) {
1499                     LOGGER.warning(msg);
1500                     LOGGER.fine("caught: ", iae);
1501                     LOGGER.fine("styleable = " + cssMetaData);
1502                     LOGGER.fine("node = " + styleable.toString());
1503                 }
1504                 return SKIP;
1505             } catch (NullPointerException npe) {
1506                 final String msg = formatExceptionMessage(styleable, cssMetaData, style.getStyle(), npe);
1507                 List<CssError> errors = null;
1508                 if ((errors = StyleManager.getErrors()) != null) {
1509                     final CssError error = new CssError.PropertySetError(cssMetaData, styleable, msg);
1510                     errors.add(error);
1511                 }
1512                 if (LOGGER.isLoggable(Level.WARNING)) {
1513                     LOGGER.warning(msg);
1514                     LOGGER.fine("caught: ", npe);
1515                     LOGGER.fine("styleable = " + cssMetaData);
1516                     LOGGER.fine("node = " + styleable.toString());
1517                 }
1518                 return SKIP;
1519             }
1520 
1521         }
1522         // either cssValue was null or cssValue's value was "null" or "none"
1523         return new CalculatedValue(null, style.getOrigin(), false);
1524 
1525     }
1526 
1527     private static final CssMetaData dummyFontProperty =
1528             new FontCssMetaData<Node>("-fx-font", Font.getDefault()) {
1529 


1715         CascadingStyle fontShorthand = getStyle(styleable, property, styleMap, states);
1716 
1717         // don't look past current node for font shorthand if user set the font
1718         if (fontShorthand == null && origin != StyleOrigin.USER) {
1719 
1720             Styleable parent = styleable != null ? styleable.getStyleableParent() : null;
1721 
1722             while (parent != null) {
1723 
1724                 CssStyleHelper parentStyleHelper = parent instanceof Node ? ((Node)parent).styleHelper : null;
1725                 if (parentStyleHelper != null) {
1726 
1727                     distance += 1;
1728 
1729                     StyleMap parentStyleMap = parentStyleHelper.getStyleMap(parent);
1730                     Set<PseudoClass> transitionStates = ((Node)parent).pseudoClassStates;
1731                     CascadingStyle cascadingStyle = parentStyleHelper.getStyle(parent, property, parentStyleMap, transitionStates);
1732 
1733                     if (cascadingStyle != null) {
1734 
1735                         final ParsedValueImpl cssValue = cascadingStyle.getParsedValueImpl();
1736 
1737                         if ("inherit".equals(cssValue.getValue()) == false) {
1738                             fontShorthand = cascadingStyle;
1739                             break;
1740                         }
1741                     }
1742 
1743                 }
1744 
1745                 parent = parent.getStyleableParent();
1746 
1747             }
1748 
1749         }
1750 
1751         if (fontShorthand != null) {
1752 
1753             //
1754             // If we don't have an existing font, or if the origin of the
1755             // existing font is less than that of the shorthand, then


1973 
1974             CssStyleHelper parentStyleHelper = parent instanceof Node ? ((Node)parent).styleHelper : null;
1975             if (parentStyleHelper != null) {
1976 
1977                 nlooks -= 1;
1978 
1979                 StyleMap parentStyleMap = parentStyleHelper.getStyleMap((parent));
1980                 Set<PseudoClass> transitionStates = ((Node)parent).pseudoClassStates;
1981                 CascadingStyle cascadingStyle = parentStyleHelper.getStyle(parent, property, parentStyleMap, transitionStates);
1982 
1983                 if (cascadingStyle != null) {
1984 
1985                     // If we are closer to the node than the font shorthand, then font shorthand doesn't matter.
1986                     // If the font shorthand and this style are the same distance, then we need to compare.
1987                     if (fontShorthand != null && nlooks == 0) {
1988                         if (fontShorthand.compareTo(cascadingStyle) < 0) {
1989                             return null;
1990                         }
1991                     }
1992 
1993                     final ParsedValueImpl cssValue = cascadingStyle.getParsedValueImpl();
1994 
1995                     if ("inherit".equals(cssValue.getValue()) == false) {
1996                         return cascadingStyle;
1997                     }
1998                 }
1999 
2000             }
2001 
2002             parent = parent.getStyleableParent();
2003 
2004         }
2005 
2006         return null;
2007     }
2008 
2009 
2010     /**
2011      * Called from Node impl_getMatchingStyles
2012      * @param styleable
2013      * @param styleableProperty


2072             final Style style = styleList.get(n).getStyle();
2073             if (!matchingStyles.contains(style)) matchingStyles.add(style);
2074         }
2075 
2076         return matchingStyles;
2077     }
2078 
2079     private void getMatchingStyles(final Styleable node, final CssMetaData styleableProperty, final List<CascadingStyle> styleList, boolean matchState) {
2080 
2081         if (node != null) {
2082 
2083             String property = styleableProperty.getProperty();
2084             Node _node = node instanceof Node ? (Node)node : null;
2085             final StyleMap smap = getStyleMap(_node);
2086             if (smap == null) return;
2087 
2088             if (matchState) {
2089                 CascadingStyle cascadingStyle = getStyle(node, styleableProperty.getProperty(), smap, _node.pseudoClassStates);
2090                 if (cascadingStyle != null) {
2091                     styleList.add(cascadingStyle);
2092                     final ParsedValueImpl parsedValue = cascadingStyle.getParsedValueImpl();
2093                     getMatchingLookupStyles(node, parsedValue, styleList, matchState);
2094                 }
2095             }  else {
2096 
2097                 Map<String, List<CascadingStyle>> cascadingStyleMap = smap.getCascadingStyles();
2098                 // StyleMap.getCascadingStyles() does not return null
2099                 List<CascadingStyle> styles = cascadingStyleMap.get(property);
2100 
2101                 if (styles != null) {
2102                     styleList.addAll(styles);
2103                     for (int n=0, nMax=styles.size(); n<nMax; n++) {
2104                         final CascadingStyle style = styles.get(n);
2105                         final ParsedValueImpl parsedValue = style.getParsedValueImpl();
2106                         getMatchingLookupStyles(node, parsedValue, styleList, matchState);
2107                     }
2108                 }
2109             }
2110 
2111             if (styleableProperty.isInherits()) {
2112                 Styleable parent = node.getStyleableParent();
2113                 while (parent != null) {
2114                     CssStyleHelper parentHelper = parent instanceof Node
2115                             ? ((Node)parent).styleHelper
2116                             : null;
2117                     if (parentHelper != null) {
2118                         parentHelper.getMatchingStyles(parent, styleableProperty, styleList, matchState);
2119                     }
2120                     parent = parent.getStyleableParent();
2121                 }
2122             }
2123 
2124         }
2125 
2126     }
2127 
2128     // Pretty much a duplicate of resolveLookups, but without the state
2129     private void getMatchingLookupStyles(final Styleable node, final ParsedValueImpl parsedValue, final List<CascadingStyle> styleList, boolean matchState) {
2130 
2131         if (parsedValue.isLookup()) {
2132 
2133             Object value = parsedValue.getValue();
2134 
2135             if (value instanceof String) {
2136 
2137                 final String property = (String)value;
2138                 // gather up any and all styles that contain this value as a property
2139                 Styleable parent = node;
2140                 do {
2141 
2142                     final Node _parent = parent instanceof Node ? (Node)parent : null;
2143                     final CssStyleHelper helper = _parent != null
2144                             ? _parent.styleHelper
2145                             : null;
2146                     if (helper != null) {
2147 
2148                         StyleMap styleMap = helper.getStyleMap(parent);
2149                         if (styleMap == null || styleMap.isEmpty()) continue;


2153                         if (matchState) {
2154                             CascadingStyle cascadingStyle = helper.resolveRef(_parent, property, styleMap, _parent.pseudoClassStates);
2155                             if (cascadingStyle != null) {
2156                                 styleList.add(cascadingStyle);
2157                             }
2158                         } else {
2159                             final Map<String, List<CascadingStyle>> smap = styleMap.getCascadingStyles();
2160                             // getCascadingStyles does not return null
2161                             List<CascadingStyle> styles = smap.get(property);
2162 
2163                             if (styles != null) {
2164                                 styleList.addAll(styles);
2165                             }
2166 
2167                         }
2168 
2169                         final int end = styleList.size();
2170 
2171                         for (int index=start; index<end; index++) {
2172                             final CascadingStyle style = styleList.get(index);
2173                             getMatchingLookupStyles(parent, style.getParsedValueImpl(), styleList, matchState);
2174                         }
2175                     }
2176 
2177                 } while ((parent = parent.getStyleableParent()) != null);
2178 
2179             }
2180         }
2181 
2182         // If the value doesn't contain any values that need lookup, then bail
2183         if (!parsedValue.isContainsLookups()) {
2184             return;
2185         }
2186 
2187         final Object val = parsedValue.getValue();
2188         if (val instanceof ParsedValueImpl[][]) {
2189         // If ParsedValueImpl is a layered sequence of values, resolve the lookups for each.
2190             final ParsedValueImpl[][] layers = (ParsedValueImpl[][])val;
2191             for (int l=0; l<layers.length; l++) {
2192                 for (int ll=0; ll<layers[l].length; ll++) {
2193                     if (layers[l][ll] == null) continue;
2194                         getMatchingLookupStyles(node, layers[l][ll], styleList, matchState);
2195                 }
2196             }
2197 
2198         } else if (val instanceof ParsedValueImpl[]) {
2199         // If ParsedValueImpl is a sequence of values, resolve the lookups for each.
2200             final ParsedValueImpl[] layer = (ParsedValueImpl[])val;
2201             for (int l=0; l<layer.length; l++) {
2202                 if (layer[l] == null) continue;
2203                     getMatchingLookupStyles(node, layer[l], styleList, matchState);
2204             }
2205         }
2206 
2207     }
2208 
2209 }


  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package javafx.scene;
  26 
  27 import java.util.ArrayList;
  28 import java.util.Collections;
  29 import java.util.HashMap;
  30 import java.util.HashSet;
  31 import java.util.List;
  32 import java.util.Locale;
  33 import java.util.Map;
  34 import java.util.Map.Entry;
  35 import java.util.Set;
  36 
  37 import javafx.beans.property.ObjectProperty;
  38 import javafx.beans.property.SimpleObjectProperty;
  39 import javafx.beans.value.WritableValue;
  40 import javafx.css.CascadingStyle;
  41 import javafx.css.CssMetaData;
  42 import javafx.css.CssParser;
  43 import javafx.css.FontCssMetaData;
  44 import javafx.css.ParsedValue;
  45 import javafx.css.PseudoClass;
  46 import javafx.css.Rule;
  47 import javafx.css.Selector;
  48 import javafx.css.Style;
  49 import javafx.css.StyleConverter;
  50 import javafx.css.StyleOrigin;
  51 import javafx.css.Styleable;
  52 import javafx.css.StyleableProperty;
  53 import javafx.css.Stylesheet;
  54 import javafx.scene.text.Font;
  55 import javafx.scene.text.FontPosture;
  56 import javafx.scene.text.FontWeight;
  57 

  58 import com.sun.javafx.css.CalculatedValue;


  59 import com.sun.javafx.css.ParsedValueImpl;
  60 import com.sun.javafx.css.PseudoClassState;



  61 import com.sun.javafx.css.StyleCache;
  62 import com.sun.javafx.css.StyleCacheEntry;

  63 import com.sun.javafx.css.StyleManager;
  64 import com.sun.javafx.css.StyleMap;
  65 import javafx.css.converter.FontConverter;
  66 import com.sun.javafx.util.Logging;
  67 import com.sun.javafx.util.Utils;
  68 
  69 import sun.util.logging.PlatformLogger.Level;
  70 import sun.util.logging.PlatformLogger;
  71 
  72 import static com.sun.javafx.css.CalculatedValue.*;
  73 
  74 /**
  75  * The StyleHelper is a helper class used for applying CSS information to Nodes.
  76  */
  77 final class CssStyleHelper {
  78 
  79     private static final PlatformLogger LOGGER = com.sun.javafx.util.Logging.getCSSLogger();
  80 
  81     private CssStyleHelper() {
  82         this.triggerStates = new PseudoClassState();
  83     }
  84 
  85     /**
  86      * Creates a new StyleHelper.
  87      */
  88     static CssStyleHelper createStyleHelper(final Node node) {
  89 
  90         // need to know how far we are to root in order to init arrays.


 616 
 617         final StyleCacheEntry.Key cacheEntryKey = new StyleCacheEntry.Key(transitionStates, fontForRelativeSizes);
 618         StyleCacheEntry cacheEntry = sharedCache.getStyleCacheEntry(cacheEntryKey);
 619 
 620         // if the cacheEntry already exists, take the fastpath
 621         final boolean fastpath = cacheEntry != null;
 622 
 623         if (cacheEntry == null) {
 624             cacheEntry = new StyleCacheEntry();
 625             sharedCache.addStyleCacheEntry(cacheEntryKey, cacheEntry);
 626         }
 627 
 628         final List<CssMetaData<? extends Styleable,  ?>> styleables = node.getCssMetaData();
 629 
 630         // Used in the for loop below, and a convenient place to stop when debugging.
 631         final int max = styleables.size();
 632 
 633         final boolean isForceSlowpath = cacheContainer.forceSlowpath;
 634         cacheContainer.forceSlowpath = false;
 635 



 636         // For each property that is settable, we need to do a lookup and
 637         // transition to that value.
 638         for(int n=0; n<max; n++) {
 639 
 640             @SuppressWarnings("unchecked") // this is a widening conversion
 641             final CssMetaData<Styleable,Object> cssMetaData =
 642                     (CssMetaData<Styleable,Object>)styleables.get(n);
 643 
 644             // Don't bother looking up styles that don't inherit.
 645             if (inheritOnly && cssMetaData.isInherits() == false) {
 646                 continue;
 647             }
 648 
 649             // Skip the lookup if we know there isn't a chance for this property
 650             // to be set (usually due to a "bind").
 651             if (!cssMetaData.isSettable(node)) continue;
 652 
 653             final String property = cssMetaData.getProperty();
 654 
 655             CalculatedValue calculatedValue = cacheEntry.get(property);


 769                                 String.valueOf(value) + ", originOfCalculatedValue=" + originOfCalculatedValue);
 770                     }
 771 
 772                     styleableProperty.applyStyle(originOfCalculatedValue, value);
 773 
 774                     if (cacheContainer.cssSetProperties.containsKey(cssMetaData) == false) {
 775                         // track this property
 776                         CalculatedValue initialValue = new CalculatedValue(currentValue, originOfCurrentValue, false);
 777                         cacheContainer.cssSetProperties.put(cssMetaData, initialValue);
 778                     }
 779 
 780                 }
 781 
 782             } catch (Exception e) {
 783 
 784                 StyleableProperty styleableProperty = cssMetaData.getStyleableProperty(node);
 785 
 786                 final String msg = String.format("Failed to set css [%s] on [%s] due to '%s'\n",
 787                         cssMetaData.getProperty(), styleableProperty, e.getMessage());
 788 
 789                 List<CssParser.ParseError> errors = null;
 790                 if ((errors = StyleManager.getErrors()) != null) {
 791                     final CssParser.ParseError error = new CssParser.ParseError.PropertySetError(cssMetaData, node, msg);
 792                     errors.add(error);
 793                 }
 794 
 795                 PlatformLogger logger = Logging.getCSSLogger();
 796                 if (logger.isLoggable(Level.WARNING)) {
 797                     logger.warning(msg);
 798                 }
 799 
 800                 // RT-27155: if setting value raises exception, reset value
 801                 // the value to initial and thereafter skip setting the property
 802                 cacheEntry.put(property, SKIP);
 803 
 804                 CalculatedValue cachedValue = null;
 805                 if (cacheContainer != null && cacheContainer.cssSetProperties != null) {
 806                     cachedValue = cacheContainer.cssSetProperties.get(cssMetaData);
 807                 }
 808                 Object value = (cachedValue != null) ? cachedValue.getValue() : cssMetaData.getInitialValue(node);
 809                 StyleOrigin origin = (cachedValue != null) ? cachedValue.getOrigin() : null;
 810                 try {
 811                     styleableProperty.applyStyle(origin, value);
 812                 } catch (Exception ebad) {
 813                     // This would be bad.
 814                     if (logger.isLoggable(Level.SEVERE)) {
 815                         logger.severe(String.format("Could not reset [%s] on [%s] due to %s\n" ,
 816                                 cssMetaData.getProperty(), styleableProperty, e.getMessage()));
 817                     }
 818                 }
 819 
 820             }
 821 
 822         }




 823     }
 824 
 825     /**
 826      * Gets the CSS CascadingStyle for the property of this node in these pseudo-class
 827      * states. A null style may be returned if there is no style information
 828      * for this combination of input parameters.
 829      *
 830      *
 831      * @param styleable
 832      * @param property
 833      * @param styleMap
 834      * @param states   @return
 835      * */
 836     private CascadingStyle getStyle(final Styleable styleable, final String property, final StyleMap styleMap, final Set<PseudoClass> states){
 837 
 838         if (styleMap == null || styleMap.isEmpty()) return null;
 839 
 840         final Map<String, List<CascadingStyle>> cascadingStyleMap = styleMap.getCascadingStyles();
 841         if (cascadingStyleMap == null || cascadingStyleMap.isEmpty()) return null;
 842 


 941                                 ? origin.compareTo(constituent.getOrigin()) < 0
 942                                 : constituent.getOrigin() != null) {
 943                             origin = constituent.getOrigin();
 944                         }
 945 
 946                         // if the constiuent uses relative sizes, then
 947                         // isRelative is true;
 948                         isRelative = isRelative || constituent.isRelative();
 949 
 950                     }
 951                 }
 952 
 953                 // If there are no subkeys which apply...
 954                 if (subs == null || subs.isEmpty()) {
 955                     return handleNoStyleFound(styleable, cssMetaData,
 956                             styleMap, states, originatingStyleable, cachedFont);
 957                 }
 958 
 959                 try {
 960                     final StyleConverter keyType = cssMetaData.getConverter();
 961                     Object ret = keyType.convert(subs);

 962                     return new CalculatedValue(ret, origin, isRelative);




 963                 } catch (ClassCastException cce) {
 964                     final String msg = formatExceptionMessage(styleable, cssMetaData, null, cce);
 965                     List<CssParser.ParseError> errors = null;
 966                     if ((errors = StyleManager.getErrors()) != null) {
 967                         final CssParser.ParseError error = new CssParser.ParseError.PropertySetError(cssMetaData, styleable, msg);
 968                         errors.add(error);
 969                     }
 970                     if (LOGGER.isLoggable(Level.WARNING)) {
 971                         LOGGER.warning(msg);
 972                         LOGGER.fine("caught: ", cce);
 973                         LOGGER.fine("styleable = " + cssMetaData);
 974                         LOGGER.fine("node = " + styleable.toString());
 975                     }
 976                     return SKIP;
 977                 }
 978             }
 979 
 980         } else { // style != null
 981 
 982             // RT-10522:
 983             // If the user set the property and there is a style and
 984             // the style came from the user agent stylesheet, then
 985             // skip the value. A style from a user agent stylesheet should
 986             // not override the user set style.
 987             if (style.getOrigin() == StyleOrigin.USER_AGENT) {
 988 
 989                 StyleableProperty styleableProperty = cssMetaData.getStyleableProperty(originatingStyleable);
 990                 // if styleableProperty is null, then we're dealing with a sub-property.
 991                 if (styleableProperty != null && styleableProperty.getStyleOrigin() == StyleOrigin.USER) {
 992                     return SKIP;
 993                 }
 994             }
 995 
 996             // If there was a style found, then we want to check whether the
 997             // value was "inherit". If so, then we will simply inherit.
 998             final ParsedValue cssValue = style.getParsedValue();
 999             if (cssValue != null && "inherit".equals(cssValue.getValue())) {
1000                 style = getInheritedStyle(styleable, property);
1001                 if (style == null) return SKIP;
1002             }
1003         }
1004 
1005 //        System.out.println("lookup " + property +
1006 //                ", selector = \'" + style.selector.toString() + "\'" +
1007 //                ", node = " + node.toString());
1008 
1009         return calculateValue(style, styleable, cssMetaData, styleMap, states,
1010                 originatingStyleable, cachedFont);
1011     }
1012 
1013     /**
1014      * Called when there is no style found.
1015      */
1016     private CalculatedValue handleNoStyleFound(final Styleable styleable,
1017                                                final CssMetaData cssMetaData,
1018                                                final StyleMap styleMap, Set<PseudoClass> pseudoClassStates, Styleable originatingStyleable,


1052     /**
1053      * Called when we must getInheritedStyle a value from a parent node in the scenegraph.
1054      */
1055     private CascadingStyle getInheritedStyle(
1056             final Styleable styleable,
1057             final String property) {
1058 
1059         Styleable parent = styleable != null ? styleable.getStyleableParent() : null;
1060 
1061         while (parent != null) {
1062 
1063             CssStyleHelper parentStyleHelper = parent instanceof Node ? ((Node)parent).styleHelper : null;
1064             if (parentStyleHelper != null) {
1065 
1066                 StyleMap parentStyleMap = parentStyleHelper.getStyleMap(parent);
1067                 Set<PseudoClass> transitionStates = ((Node)parent).pseudoClassStates;
1068                 CascadingStyle cascadingStyle = parentStyleHelper.getStyle(parent, property, parentStyleMap, transitionStates);
1069 
1070                 if (cascadingStyle != null) {
1071 
1072                     final ParsedValue cssValue = cascadingStyle.getParsedValue();
1073 
1074                     if ("inherit".equals(cssValue.getValue())) {
1075                         return getInheritedStyle(parent, property);
1076                     }
1077                     return cascadingStyle;
1078                 }
1079 
1080                 return null;
1081             }
1082 
1083             parent = parent.getStyleableParent();
1084 
1085         }
1086 
1087         return null;
1088     }
1089 
1090 
1091     // helps with self-documenting the code
1092     private static final Set<PseudoClass> NULL_PSEUDO_CLASS_STATE = null;


1120                     }
1121                 }
1122 
1123                 if (styleableParent == null || parentStyleHelper == null) {
1124                     return null;
1125                 }
1126 
1127                 StyleMap parentStyleMap = parentStyleHelper.getStyleMap(styleableParent);
1128                 Set<PseudoClass> styleableParentPseudoClassStates =
1129                     styleableParent instanceof Node
1130                         ? ((Node)styleableParent).pseudoClassStates
1131                         : styleable.getPseudoClassStates();
1132 
1133                 return parentStyleHelper.resolveRef(styleableParent, property,
1134                         parentStyleMap, styleableParentPseudoClassStates);
1135             }
1136         }
1137     }
1138 
1139     // to resolve a lookup, we just need to find the parsed value.
1140     private ParsedValue resolveLookups(
1141             final Styleable styleable,
1142             final ParsedValue parsedValue,
1143             final StyleMap styleMap, Set<PseudoClass> states,
1144             final ObjectProperty<StyleOrigin> whence,
1145             Set<ParsedValue> resolves) {
1146 
1147         //
1148         // either the value itself is a lookup, or the value contain a lookup
1149         //
1150         if (parsedValue.isLookup()) {
1151 
1152             // The value we're looking for should be a Paint, one of the
1153             // containers for linear, radial or ladder, or a derived color.
1154             final Object val = parsedValue.getValue();
1155             if (val instanceof String) {
1156 
1157                 final String sval = ((String) val).toLowerCase(Locale.ROOT);
1158 
1159                 CascadingStyle resolved =
1160                     resolveRef(styleable, sval, styleMap, states);
1161 
1162                 if (resolved != null) {
1163 
1164                     if (resolves.contains(resolved.getParsedValue())) {
1165 
1166                         if (LOGGER.isLoggable(Level.WARNING)) {
1167                             LOGGER.warning("Loop detected in " + resolved.getRule().toString() + " while resolving '" + sval + "'");
1168                         }
1169                         throw new IllegalArgumentException("Loop detected in " + resolved.getRule().toString() + " while resolving '" + sval + "'");
1170 
1171                     } else {
1172                         resolves.add(parsedValue);
1173                     }
1174 
1175                     // The origin of this parsed value is the greatest of
1176                     // any of the resolved reference. If a resolved reference
1177                     // comes from an inline style, for example, then the value
1178                     // calculated from the resolved lookup should have inline
1179                     // as its origin. Otherwise, an inline style could be
1180                     // stored in shared cache.
1181                     final StyleOrigin wOrigin = whence.get();
1182                     final StyleOrigin rOrigin = resolved.getOrigin();
1183                     if (rOrigin != null && (wOrigin == null ||  wOrigin.compareTo(rOrigin) < 0)) {
1184                         whence.set(rOrigin);
1185                     }
1186 
1187                     // the resolved value may itself need to be resolved.
1188                     // For example, if the value "color" resolves to "base",
1189                     // then "base" will need to be resolved as well.
1190                     ParsedValue pv = resolveLookups(styleable, resolved.getParsedValue(), styleMap, states, whence, resolves);
1191 
1192                     if (resolves != null) {
1193                         resolves.remove(parsedValue);
1194                     }
1195 
1196                     return pv;
1197 
1198                 }
1199             }
1200         }
1201 
1202         // If the value doesn't contain any values that need lookup, then bail
1203         if (!parsedValue.isContainsLookups()) {
1204             return parsedValue;
1205         }
1206 
1207         final Object val = parsedValue.getValue();
1208 
1209         if (val instanceof ParsedValue[][]) {
1210 
1211             // If ParsedValue is a layered sequence of values, resolve the lookups for each.
1212             final ParsedValue[][] layers = (ParsedValue[][])val;
1213             ParsedValue[][] resolved = new ParsedValue[layers.length][0];
1214             for (int l=0; l<layers.length; l++) {
1215                 resolved[l] = new ParsedValue[layers[l].length];
1216                 for (int ll=0; ll<layers[l].length; ll++) {
1217                     if (layers[l][ll] == null) continue;
1218                     resolved[l][ll] =
1219                         resolveLookups(styleable, layers[l][ll], styleMap, states, whence, resolves);
1220                 }
1221             }
1222 
1223             resolves.clear();
1224 
1225             return new ParsedValueImpl(resolved, parsedValue.getConverter(), false);
1226 
1227         } else if (val instanceof ParsedValueImpl[]) {
1228 
1229             // If ParsedValue is a sequence of values, resolve the lookups for each.
1230             final ParsedValue[] layer = (ParsedValue[])val;
1231             ParsedValue[] resolved = new ParsedValue[layer.length];
1232             for (int l=0; l<layer.length; l++) {
1233                 if (layer[l] == null) continue;
1234                 resolved[l] =
1235                     resolveLookups(styleable, layer[l], styleMap, states, whence, resolves);
1236             }
1237 
1238             resolves.clear();
1239 
1240             return new ParsedValueImpl(resolved, parsedValue.getConverter(), false);
1241 
1242         }
1243 
1244         return parsedValue;
1245 
1246     }
1247 
1248     private String getUnresolvedLookup(final ParsedValue resolved) {
1249 
1250         Object value = resolved.getValue();
1251 
1252         if (resolved.isLookup() && value instanceof String) {
1253             return (String)value;
1254         }
1255 
1256         if (value instanceof ParsedValue[][]) {
1257             final ParsedValue[][] layers = (ParsedValue[][])value;
1258             for (int l=0; l<layers.length; l++) {
1259                 for (int ll=0; ll<layers[l].length; ll++) {
1260                     if (layers[l][ll] == null) continue;
1261                     String unresolvedLookup = getUnresolvedLookup(layers[l][ll]);
1262                     if (unresolvedLookup != null) return unresolvedLookup;
1263                 }
1264             }
1265 
1266         } else if (value instanceof ParsedValue[]) {
1267         // If ParsedValue is a sequence of values, resolve the lookups for each.
1268             final ParsedValue[] layer = (ParsedValue[])value;
1269             for (int l=0; l<layer.length; l++) {
1270                 if (layer[l] == null) continue;
1271                 String unresolvedLookup = getUnresolvedLookup(layer[l]);
1272                 if (unresolvedLookup != null) return unresolvedLookup;
1273             }
1274         }
1275 
1276         return null;
1277     }
1278 
1279     private String formatUnresolvedLookupMessage(Styleable styleable, CssMetaData cssMetaData, Style style, ParsedValue resolved, ClassCastException cce) {
1280 
1281         // Find value that could not be looked up. If the resolved value does not contain lookups, then the
1282         // ClassCastException is not because of trying to convert a String (which is the missing lookup)
1283         // to some value, but is because the convert method got some wrong value - like a paint when it should be a color.
1284         // See RT-33319 for an example of this.
1285         String missingLookup = resolved != null && resolved.isContainsLookups() ? getUnresolvedLookup(resolved) : null;
1286 
1287         StringBuilder sbuf = new StringBuilder();
1288         if (missingLookup != null) {
1289             sbuf.append("Could not resolve '")
1290                     .append(missingLookup)
1291                     .append("'")
1292                     .append(" while resolving lookups for '")
1293                     .append(cssMetaData.getProperty())
1294                     .append("'");
1295         } else {
1296             sbuf.append("Caught '")
1297                     .append(cce)
1298                     .append("'")
1299                     .append(" while converting value for '")


1344                         .append(styleable.toString());
1345             } else {
1346                 sbuf.append(" from style '")
1347                     .append(String.valueOf(style))
1348                     .append("'");
1349             }
1350         }
1351 
1352         return sbuf.toString();
1353     }
1354 
1355 
1356     private CalculatedValue calculateValue(
1357             final CascadingStyle style,
1358             final Styleable styleable,
1359             final CssMetaData cssMetaData,
1360             final StyleMap styleMap, final Set<PseudoClass> states,
1361             final Styleable originatingStyleable,
1362             final CalculatedValue fontFromCacheEntry) {
1363 
1364         final ParsedValue cssValue = style.getParsedValue();
1365         if (cssValue != null && !("null".equals(cssValue.getValue()) || "none".equals(cssValue.getValue()))) {
1366 
1367             ParsedValue resolved = null;
1368             try {
1369 
1370                 ObjectProperty<StyleOrigin> whence = new SimpleObjectProperty<>(style.getOrigin());
1371                 resolved = resolveLookups(styleable, cssValue, styleMap, states, whence, new HashSet<>());
1372 
1373                 final String property = cssMetaData.getProperty();
1374 
1375                 // The computed value
1376                 Object val = null;
1377                 boolean isFontProperty =
1378                         "-fx-font".equals(property) ||
1379                         "-fx-font-size".equals(property);
1380 
1381                 boolean isRelative = ParsedValueImpl.containsFontRelativeSize(resolved, isFontProperty);
1382 
1383                 //
1384                 // Avoid using a font calculated from a relative size
1385                 // to calculate a font with a relative size.
1386                 // For example:
1387                 // Assume the default font size is 13 and we have a style with


1425                             }
1426 
1427                         }
1428 
1429                     } while(fontForFontRelativeSizes == null &&
1430                             (parent = parent.getStyleableParent()) != null);
1431                 }
1432 
1433                 // did we get a fontValue from the preceding block?
1434                 // if not, get it from our cacheEntry or choose the default
1435                 if (fontForFontRelativeSizes == null) {
1436                     if (fontFromCacheEntry != null && fontFromCacheEntry.isRelative() == false) {
1437                         fontForFontRelativeSizes = (Font)fontFromCacheEntry.getValue();
1438                     } else {
1439                         fontForFontRelativeSizes = Font.getDefault();
1440                     }
1441                 }
1442 
1443                 final StyleConverter cssMetaDataConverter = cssMetaData.getConverter();
1444                 // RT-37727 - handling of properties that are insets is wonky. If the property is -fx-inset, then
1445                 // there isn't an issue because the converter assigns the InsetsConverter to the ParsedValue.
1446                 // But -my-insets will parse as an array of numbers and the parser will assign the Size sequence
1447                 // converter to it. So, if the CssMetaData says it uses InsetsConverter, use the InsetsConverter
1448                 // and not the parser assigned converter.
1449                 if (cssMetaDataConverter == StyleConverter.getInsetsConverter()) {
1450                     if (resolved.getValue() instanceof ParsedValue) {
1451                         // If you give the parser "-my-insets: 5;" you end up with a ParsedValue<ParsedValue<?,Size>, Number>
1452                         // and not a ParsedValue<ParsedValue[], Number[]> so here we wrap the value into an array
1453                         // to make the InsetsConverter happy.
1454                         resolved = new ParsedValueImpl(new ParsedValue[] {(ParsedValue)resolved.getValue()}, null, false);
1455                     }
1456                     val = cssMetaDataConverter.convert(resolved, fontForFontRelativeSizes);
1457                 }
1458                 else if (resolved.getConverter() != null)
1459                     val = resolved.convert(fontForFontRelativeSizes);
1460                 else
1461                     val = cssMetaData.getConverter().convert(resolved, fontForFontRelativeSizes);
1462 
1463                 final StyleOrigin origin = whence.get();
1464                 return new CalculatedValue(val, origin, isRelative);
1465 
1466             } catch (ClassCastException cce) {
1467                 final String msg = formatUnresolvedLookupMessage(styleable, cssMetaData, style.getStyle(),resolved, cce);
1468                 List<CssParser.ParseError> errors = null;
1469                 if ((errors = StyleManager.getErrors()) != null) {
1470                     final CssParser.ParseError error = new CssParser.ParseError.PropertySetError(cssMetaData, styleable, msg);
1471                     errors.add(error);
1472                 }
1473                 if (LOGGER.isLoggable(Level.WARNING)) {
1474                     LOGGER.warning(msg);
1475                     LOGGER.fine("node = " + styleable.toString());
1476                     LOGGER.fine("cssMetaData = " + cssMetaData);
1477                     LOGGER.fine("styles = " + getMatchingStyles(styleable, cssMetaData));
1478                 }
1479                 return SKIP;
1480             } catch (IllegalArgumentException iae) {
1481                 final String msg = formatExceptionMessage(styleable, cssMetaData, style.getStyle(), iae);
1482                 List<CssParser.ParseError> errors = null;
1483                 if ((errors = StyleManager.getErrors()) != null) {
1484                     final CssParser.ParseError error = new CssParser.ParseError.PropertySetError(cssMetaData, styleable, msg);
1485                     errors.add(error);
1486                 }
1487                 if (LOGGER.isLoggable(Level.WARNING)) {
1488                     LOGGER.warning(msg);
1489                     LOGGER.fine("caught: ", iae);
1490                     LOGGER.fine("styleable = " + cssMetaData);
1491                     LOGGER.fine("node = " + styleable.toString());
1492                 }
1493                 return SKIP;
1494             } catch (NullPointerException npe) {
1495                 final String msg = formatExceptionMessage(styleable, cssMetaData, style.getStyle(), npe);
1496                 List<CssParser.ParseError> errors = null;
1497                 if ((errors = StyleManager.getErrors()) != null) {
1498                     final CssParser.ParseError error = new CssParser.ParseError.PropertySetError(cssMetaData, styleable, msg);
1499                     errors.add(error);
1500                 }
1501                 if (LOGGER.isLoggable(Level.WARNING)) {
1502                     LOGGER.warning(msg);
1503                     LOGGER.fine("caught: ", npe);
1504                     LOGGER.fine("styleable = " + cssMetaData);
1505                     LOGGER.fine("node = " + styleable.toString());
1506                 }
1507                 return SKIP;
1508             }
1509 
1510         }
1511         // either cssValue was null or cssValue's value was "null" or "none"
1512         return new CalculatedValue(null, style.getOrigin(), false);
1513 
1514     }
1515 
1516     private static final CssMetaData dummyFontProperty =
1517             new FontCssMetaData<Node>("-fx-font", Font.getDefault()) {
1518 


1704         CascadingStyle fontShorthand = getStyle(styleable, property, styleMap, states);
1705 
1706         // don't look past current node for font shorthand if user set the font
1707         if (fontShorthand == null && origin != StyleOrigin.USER) {
1708 
1709             Styleable parent = styleable != null ? styleable.getStyleableParent() : null;
1710 
1711             while (parent != null) {
1712 
1713                 CssStyleHelper parentStyleHelper = parent instanceof Node ? ((Node)parent).styleHelper : null;
1714                 if (parentStyleHelper != null) {
1715 
1716                     distance += 1;
1717 
1718                     StyleMap parentStyleMap = parentStyleHelper.getStyleMap(parent);
1719                     Set<PseudoClass> transitionStates = ((Node)parent).pseudoClassStates;
1720                     CascadingStyle cascadingStyle = parentStyleHelper.getStyle(parent, property, parentStyleMap, transitionStates);
1721 
1722                     if (cascadingStyle != null) {
1723 
1724                         final ParsedValue cssValue = cascadingStyle.getParsedValue();
1725 
1726                         if ("inherit".equals(cssValue.getValue()) == false) {
1727                             fontShorthand = cascadingStyle;
1728                             break;
1729                         }
1730                     }
1731 
1732                 }
1733 
1734                 parent = parent.getStyleableParent();
1735 
1736             }
1737 
1738         }
1739 
1740         if (fontShorthand != null) {
1741 
1742             //
1743             // If we don't have an existing font, or if the origin of the
1744             // existing font is less than that of the shorthand, then


1962 
1963             CssStyleHelper parentStyleHelper = parent instanceof Node ? ((Node)parent).styleHelper : null;
1964             if (parentStyleHelper != null) {
1965 
1966                 nlooks -= 1;
1967 
1968                 StyleMap parentStyleMap = parentStyleHelper.getStyleMap((parent));
1969                 Set<PseudoClass> transitionStates = ((Node)parent).pseudoClassStates;
1970                 CascadingStyle cascadingStyle = parentStyleHelper.getStyle(parent, property, parentStyleMap, transitionStates);
1971 
1972                 if (cascadingStyle != null) {
1973 
1974                     // If we are closer to the node than the font shorthand, then font shorthand doesn't matter.
1975                     // If the font shorthand and this style are the same distance, then we need to compare.
1976                     if (fontShorthand != null && nlooks == 0) {
1977                         if (fontShorthand.compareTo(cascadingStyle) < 0) {
1978                             return null;
1979                         }
1980                     }
1981 
1982                     final ParsedValue cssValue = cascadingStyle.getParsedValue();
1983 
1984                     if ("inherit".equals(cssValue.getValue()) == false) {
1985                         return cascadingStyle;
1986                     }
1987                 }
1988 
1989             }
1990 
1991             parent = parent.getStyleableParent();
1992 
1993         }
1994 
1995         return null;
1996     }
1997 
1998 
1999     /**
2000      * Called from Node impl_getMatchingStyles
2001      * @param styleable
2002      * @param styleableProperty


2061             final Style style = styleList.get(n).getStyle();
2062             if (!matchingStyles.contains(style)) matchingStyles.add(style);
2063         }
2064 
2065         return matchingStyles;
2066     }
2067 
2068     private void getMatchingStyles(final Styleable node, final CssMetaData styleableProperty, final List<CascadingStyle> styleList, boolean matchState) {
2069 
2070         if (node != null) {
2071 
2072             String property = styleableProperty.getProperty();
2073             Node _node = node instanceof Node ? (Node)node : null;
2074             final StyleMap smap = getStyleMap(_node);
2075             if (smap == null) return;
2076 
2077             if (matchState) {
2078                 CascadingStyle cascadingStyle = getStyle(node, styleableProperty.getProperty(), smap, _node.pseudoClassStates);
2079                 if (cascadingStyle != null) {
2080                     styleList.add(cascadingStyle);
2081                     final ParsedValue parsedValue = cascadingStyle.getParsedValue();
2082                     getMatchingLookupStyles(node, parsedValue, styleList, matchState);
2083                 }
2084             }  else {
2085 
2086                 Map<String, List<CascadingStyle>> cascadingStyleMap = smap.getCascadingStyles();
2087                 // StyleMap.getCascadingStyles() does not return null
2088                 List<CascadingStyle> styles = cascadingStyleMap.get(property);
2089 
2090                 if (styles != null) {
2091                     styleList.addAll(styles);
2092                     for (int n=0, nMax=styles.size(); n<nMax; n++) {
2093                         final CascadingStyle style = styles.get(n);
2094                         final ParsedValue parsedValue = style.getParsedValue();
2095                         getMatchingLookupStyles(node, parsedValue, styleList, matchState);
2096                     }
2097                 }
2098             }
2099 
2100             if (styleableProperty.isInherits()) {
2101                 Styleable parent = node.getStyleableParent();
2102                 while (parent != null) {
2103                     CssStyleHelper parentHelper = parent instanceof Node
2104                             ? ((Node)parent).styleHelper
2105                             : null;
2106                     if (parentHelper != null) {
2107                         parentHelper.getMatchingStyles(parent, styleableProperty, styleList, matchState);
2108                     }
2109                     parent = parent.getStyleableParent();
2110                 }
2111             }
2112 
2113         }
2114 
2115     }
2116 
2117     // Pretty much a duplicate of resolveLookups, but without the state
2118     private void getMatchingLookupStyles(final Styleable node, final ParsedValue parsedValue, final List<CascadingStyle> styleList, boolean matchState) {
2119 
2120         if (parsedValue.isLookup()) {
2121 
2122             Object value = parsedValue.getValue();
2123 
2124             if (value instanceof String) {
2125 
2126                 final String property = (String)value;
2127                 // gather up any and all styles that contain this value as a property
2128                 Styleable parent = node;
2129                 do {
2130 
2131                     final Node _parent = parent instanceof Node ? (Node)parent : null;
2132                     final CssStyleHelper helper = _parent != null
2133                             ? _parent.styleHelper
2134                             : null;
2135                     if (helper != null) {
2136 
2137                         StyleMap styleMap = helper.getStyleMap(parent);
2138                         if (styleMap == null || styleMap.isEmpty()) continue;


2142                         if (matchState) {
2143                             CascadingStyle cascadingStyle = helper.resolveRef(_parent, property, styleMap, _parent.pseudoClassStates);
2144                             if (cascadingStyle != null) {
2145                                 styleList.add(cascadingStyle);
2146                             }
2147                         } else {
2148                             final Map<String, List<CascadingStyle>> smap = styleMap.getCascadingStyles();
2149                             // getCascadingStyles does not return null
2150                             List<CascadingStyle> styles = smap.get(property);
2151 
2152                             if (styles != null) {
2153                                 styleList.addAll(styles);
2154                             }
2155 
2156                         }
2157 
2158                         final int end = styleList.size();
2159 
2160                         for (int index=start; index<end; index++) {
2161                             final CascadingStyle style = styleList.get(index);
2162                             getMatchingLookupStyles(parent, style.getParsedValue(), styleList, matchState);
2163                         }
2164                     }
2165 
2166                 } while ((parent = parent.getStyleableParent()) != null);
2167 
2168             }
2169         }
2170 
2171         // If the value doesn't contain any values that need lookup, then bail
2172         if (!parsedValue.isContainsLookups()) {
2173             return;
2174         }
2175 
2176         final Object val = parsedValue.getValue();
2177         if (val instanceof ParsedValue[][]) {
2178         // If ParsedValue is a layered sequence of values, resolve the lookups for each.
2179             final ParsedValue[][] layers = (ParsedValue[][])val;
2180             for (int l=0; l<layers.length; l++) {
2181                 for (int ll=0; ll<layers[l].length; ll++) {
2182                     if (layers[l][ll] == null) continue;
2183                         getMatchingLookupStyles(node, layers[l][ll], styleList, matchState);
2184                 }
2185             }
2186 
2187         } else if (val instanceof ParsedValue[]) {
2188         // If ParsedValue is a sequence of values, resolve the lookups for each.
2189             final ParsedValue[] layer = (ParsedValue[])val;
2190             for (int l=0; l<layer.length; l++) {
2191                 if (layer[l] == null) continue;
2192                     getMatchingLookupStyles(node, layer[l], styleList, matchState);
2193             }
2194         }
2195 
2196     }
2197 
2198 }