1 /* 2 * Copyright (c) 2010, 2018, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package javafx.css; 27 28 import javafx.css.converter.BooleanConverter; 29 import javafx.css.converter.ColorConverter; 30 import javafx.css.converter.DeriveColorConverter; 31 import javafx.css.converter.DeriveSizeConverter; 32 import javafx.css.converter.DurationConverter; 33 import javafx.css.converter.EffectConverter; 34 import javafx.css.converter.EnumConverter; 35 import javafx.css.converter.FontConverter; 36 import javafx.css.converter.InsetsConverter; 37 import javafx.css.converter.LadderConverter; 38 import javafx.css.converter.PaintConverter; 39 import javafx.css.converter.SizeConverter; 40 import javafx.css.converter.StopConverter; 41 import javafx.css.converter.StringConverter; 42 import javafx.css.converter.URLConverter; 43 import javafx.geometry.Insets; 44 import javafx.scene.effect.Effect; 45 import javafx.scene.paint.Color; 46 import javafx.scene.paint.Paint; 47 import javafx.scene.text.Font; 48 import javafx.util.Duration; 49 50 import com.sun.javafx.scene.layout.region.CornerRadiiConverter; 51 import com.sun.javafx.util.Logging; 52 import com.sun.javafx.logging.PlatformLogger; 53 import com.sun.javafx.logging.PlatformLogger.Level; 54 55 import java.io.DataInputStream; 56 import java.io.DataOutputStream; 57 import java.io.IOException; 58 import java.util.ArrayList; 59 import java.util.HashMap; 60 import java.util.List; 61 import java.util.Map; 62 import java.util.WeakHashMap; 63 64 65 /** 66 * StyleConverter converts {@code ParsedValue<F,T>} 67 * from type {@code F} to type {@code T}. The 68 * {@link CssMetaData} API requires a {@code StyleConverter} which is used 69 * when computing a value for the {@code StyleableProperty}. There are 70 * a number of predefined converters which are accessible by the static 71 * methods of this class. 72 * 73 * {@code F} is the type of the parsed value and {@code T} is the converted type of 74 * the ParsedValueImpl. For example, a converter from String to Color would 75 * be declared 76 * <p> 77 * {@code public Color convert(ParsedValueImpl<String,Color> value, Font font)} 78 * </p> 79 * 80 * @param <F> the type of the parsed value 81 * @param <T> the converted type of the ParsedValueImpl 82 * 83 * @see ParsedValue 84 * @see StyleableProperty 85 * @since JavaFX 8.0 86 */ 87 public class StyleConverter<F, T> { 88 89 /** 90 * Convert from the parsed CSS value to the target property type. 91 * 92 * @param value The {@link ParsedValue} to convert 93 * @param font The {@link Font} to use when converting a 94 * <a href="http://www.w3.org/TR/css3-values/#relative-lengths">relative</a> 95 * value. 96 * @return the converted target property type 97 */ 98 @SuppressWarnings("unchecked") 99 public T convert(ParsedValue<F,T> value, Font font) { 100 // unchecked! 101 return (T) value.getValue(); 102 } 103 104 /** 105 * Return a {@code StyleConverter} that converts {@literal "true" or "false"} to {@code Boolean}. 106 * @return A {@code StyleConverter} that converts {@literal "true" or "false"} to {@code Boolean} 107 * @see Boolean#valueOf(java.lang.String) 108 */ 109 public static StyleConverter<String,Boolean> getBooleanConverter() { 110 return BooleanConverter.getInstance(); 111 } 112 113 /** 114 * Return a {@code StyleConverter} that converts a String representation of 115 * a duration to a {@link Duration}. 116 * @return A {@code StyleConverter} that converts a String 117 * representation of a duration to a {@link Duration} 118 * 119 * @since JavaFX 8u40 120 */ 121 public static StyleConverter<?,Duration> getDurationConverter() { 122 return DurationConverter.getInstance(); 123 } 124 125 /** 126 * Return a {@code StyleConverter} that converts a String representation of 127 * a web color to a {@code Color}. 128 * @return A {@code StyleConverter} that converts a String 129 * representation of a web color to a {@code Color} 130 * @see Color#web(java.lang.String) 131 */ 132 public static StyleConverter<String,Color> getColorConverter() { 133 return ColorConverter.getInstance(); 134 } 135 136 /** 137 * Return a {@code StyleConverter} that converts a parsed representation 138 * of an {@code Effect} to an {@code Effect} 139 * @return A {@code StyleConverter} that converts a parsed representation 140 * of an {@code Effect} to an {@code Effect} 141 * @see Effect 142 */ 143 public static StyleConverter<ParsedValue[], Effect> getEffectConverter() { 144 return EffectConverter.getInstance(); 145 } 146 147 /** 148 * Return a {@code StyleConverter} that converts a String representation 149 * of an {@code Enum} to an {@code Enum}. 150 * @param <E> the type of the {@code Enum} 151 * @param enumClass the enum Class 152 * @return A {@code StyleConverter} that converts a String representation 153 * of an {@code Enum} to an {@code Enum} 154 * @see Enum#valueOf(java.lang.Class, java.lang.String) 155 */ 156 public static <E extends Enum<E>> StyleConverter<String, ? extends Enum<?>> getEnumConverter(Class<E> enumClass) { 157 // TODO: reuse EnumConverter instances 158 EnumConverter<E> converter; 159 converter = new EnumConverter<>(enumClass); 160 return converter; 161 } 162 163 /** 164 * Return a {@code StyleConverter} that converts a parsed representation 165 * of a {@code Font} to an {@code Font}. 166 * @return A {@code StyleConverter} that converts a parsed representation 167 * of a {@code Font} to an {@code Font} 168 * @see Font#font(java.lang.String, javafx.scene.text.FontWeight, javafx.scene.text.FontPosture, double) 169 */ 170 public static StyleConverter<ParsedValue[], Font> getFontConverter() { 171 return FontConverter.getInstance(); 172 } 173 174 /** 175 * Return a {@code StyleConverter} that converts a {@literal [<length> | 176 * <percentage>]}{1,4} to an {@code Insets}. 177 * @return A {@code StyleConverter} that converts a {@literal [<length> | 178 * <percentage>]}{1,4} to an {@code Insets} 179 */ 180 public static StyleConverter<ParsedValue[], Insets> getInsetsConverter() { 181 return InsetsConverter.getInstance(); 182 } 183 184 /** 185 * Return a {@code StyleConverter} that converts a parsed representation 186 * of a {@code Paint} to a {@code Paint}. 187 * @return A {@code StyleConverter} that converts a parsed representation 188 * of a {@code Paint} to a {@code Paint} 189 */ 190 public static StyleConverter<ParsedValue<?, Paint>, Paint> getPaintConverter() { 191 return PaintConverter.getInstance(); 192 } 193 194 /** 195 * CSS length and number values are parsed into a Size object that is 196 * converted to a Number before the value is applied. If the property is 197 * a {@code Number} type other than {@code Double}, the set method 198 * of ({@code CssMetaData} can be overridden to convert the {@code Number} 199 * to the correct type. For example, if the property is an {@code IntegerProperty}: 200 * <pre><code> 201 * {@literal @}Override public void set(MyNode node, Number value, Origin origin) { 202 * if (value != null) { 203 * super.set(node, value.intValue(), origin); 204 * } else { 205 * super.set(node, value, origin); 206 * } 207 * } 208 * </code></pre> 209 * @return A {@code StyleConverter} that converts a parsed representation 210 * of a CSS length or number value to a {@code Number} that is an instance 211 * of {@code Double} 212 */ 213 public static StyleConverter<?, Number> getSizeConverter() { 214 return SizeConverter.getInstance(); 215 } 216 217 /** 218 * A converter for quoted strings which may have embedded unicode characters. 219 * @return A {@code StyleConverter} that converts a representation of a 220 * CSS string value to a {@code String} 221 */ 222 public static StyleConverter<String,String> getStringConverter() { 223 return StringConverter.getInstance(); 224 } 225 226 /** 227 * A converter for URL strings. 228 * @return A {@code StyleConverter} that converts a representation of a 229 * CSS URL value to a {@code String} 230 */ 231 public static StyleConverter<ParsedValue[], String> getUrlConverter() { 232 return URLConverter.getInstance(); 233 } 234 235 236 237 238 /** 239 * Convert from the constituent values to the target property type. 240 * Implemented by Types that have Keys with subKeys. 241 * 242 * @param convertedValues the constituent values 243 * @return the target property type 244 * @since 9 245 */ 246 public T convert(Map<CssMetaData<? extends Styleable, ?>,Object> convertedValues) { 247 return null; 248 } 249 250 /** 251 * Write binary data. 252 * @param os the data output stream 253 * @param sstore the string store 254 * @throws java.io.IOException the exception 255 * @since 9 256 */ 257 public void writeBinary(DataOutputStream os, StringStore sstore) 258 throws IOException { 259 260 String cname = getClass().getName(); 261 int index = sstore.addString(cname); 262 os.writeShort(index); 263 } 264 265 private static Map<ParsedValue, Object> cache; 266 267 /** 268 * Clear the cache. 269 * @since 9 270 */ 271 public static void clearCache() { 272 if (cache != null) { 273 cache.clear(); 274 } 275 } 276 277 /** 278 * Get the cached value for the specified key. 279 * @param key the key 280 * @return the cached value 281 * @since 9 282 */ 283 protected T getCachedValue(ParsedValue key) { 284 if (cache != null) { 285 return (T)cache.get(key); 286 } 287 return null; 288 } 289 290 /** 291 * Cache the value for the specified key. 292 * @param key the key 293 * @param value the value 294 * @since 9 295 */ 296 protected void cacheValue(ParsedValue key, Object value) { 297 if (cache == null) cache = new WeakHashMap<>(); 298 cache.put(key, value); 299 } 300 301 // map of StyleConverter class name to StyleConverter 302 private static Map<String,StyleConverter<?, ?>> tmap; 303 304 /** 305 * Read binary data stream. 306 * @param is the data input stream 307 * @param strings the strings 308 * @return the style converter 309 * @throws java.io.IOException the exception 310 * @since 9 311 */ 312 @SuppressWarnings("rawtypes") 313 public static StyleConverter<?,?> readBinary(DataInputStream is, String[] strings) 314 throws IOException { 315 316 int index = is.readShort(); 317 String cname = strings[index]; 318 319 if (cname == null || cname.isEmpty()) return null; 320 321 if (cname.startsWith("com.sun.javafx.css.converters.")) { 322 // JavaFX 9: converter classes were moved from 323 // com.sun.javafx.css.converters.* to javafx.css.converter.* 324 // Note: the word 'converters' has become 'converter'. 325 cname = "javafx.css.converter." + cname.substring("com.sun.javafx.css.converters.".length()); 326 } 327 if (cname.startsWith("javafx.css.converter.EnumConverter")) { 328 return (StyleConverter)javafx.css.converter.EnumConverter.readBinary(is, strings); 329 } 330 331 // Make a new entry in tmap, if necessary 332 if (tmap == null || !tmap.containsKey(cname)) { 333 StyleConverter<?,?> converter = getInstance(cname); 334 if (converter == null) { 335 final PlatformLogger logger = Logging.getCSSLogger(); 336 if (logger.isLoggable(Level.SEVERE)) { 337 logger.severe("could not deserialize " + cname); 338 } 339 } 340 if (converter == null) { 341 System.err.println("could not deserialize " + cname); 342 } 343 if (tmap == null) tmap = new HashMap<String,StyleConverter<?,?>>(); 344 tmap.put(cname, converter); 345 return converter; 346 } 347 return tmap.get(cname); 348 } 349 350 // package for unit test purposes 351 static StyleConverter<?,?> getInstance(final String converterClass) { 352 353 StyleConverter<?,?> styleConverter = null; 354 355 switch(converterClass) { 356 case "javafx.css.converter.BooleanConverter" : 357 styleConverter = javafx.css.converter.BooleanConverter.getInstance(); 358 break; 359 case "javafx.css.converter.ColorConverter" : 360 styleConverter = javafx.css.converter.ColorConverter.getInstance(); 361 break; 362 case "javafx.css.converter.CursorConverter" : 363 styleConverter = javafx.css.converter.CursorConverter.getInstance(); 364 break; 365 case "javafx.css.converter.EffectConverter" : 366 styleConverter = javafx.css.converter.EffectConverter.getInstance(); 367 break; 368 case "javafx.css.converter.EffectConverter$DropShadowConverter" : 369 styleConverter = javafx.css.converter.EffectConverter.DropShadowConverter.getInstance(); 370 break; 371 case "javafx.css.converter.EffectConverter$InnerShadowConverter" : 372 styleConverter = javafx.css.converter.EffectConverter.InnerShadowConverter.getInstance(); 373 break; 374 case "javafx.css.converter.FontConverter" : 375 styleConverter = javafx.css.converter.FontConverter.getInstance(); 376 break; 377 case "javafx.css.converter.FontConverter$FontStyleConverter" : 378 case "javafx.css.converter.FontConverter$StyleConverter" : 379 styleConverter = javafx.css.converter.FontConverter.FontStyleConverter.getInstance(); 380 break; 381 case "javafx.css.converter.FontConverter$FontWeightConverter" : 382 case "javafx.css.converter.FontConverter$WeightConverter" : 383 styleConverter = javafx.css.converter.FontConverter.FontWeightConverter.getInstance(); 384 break; 385 case "javafx.css.converter.FontConverter$FontSizeConverter" : 386 case "javafx.css.converter.FontConverter$SizeConverter" : 387 styleConverter = javafx.css.converter.FontConverter.FontSizeConverter.getInstance(); 388 break; 389 390 case "javafx.css.converter.InsetsConverter" : 391 styleConverter = javafx.css.converter.InsetsConverter.getInstance(); 392 break; 393 case "javafx.css.converter.InsetsConverter$SequenceConverter" : 394 styleConverter = javafx.css.converter.InsetsConverter.SequenceConverter.getInstance(); 395 break; 396 397 case "javafx.css.converter.PaintConverter" : 398 styleConverter = javafx.css.converter.PaintConverter.getInstance(); 399 break; 400 case "javafx.css.converter.PaintConverter$SequenceConverter" : 401 styleConverter = javafx.css.converter.PaintConverter.SequenceConverter.getInstance(); 402 break; 403 case "javafx.css.converter.PaintConverter$LinearGradientConverter" : 404 styleConverter = javafx.css.converter.PaintConverter.LinearGradientConverter.getInstance(); 405 break; 406 case "javafx.css.converter.PaintConverter$RadialGradientConverter" : 407 styleConverter = javafx.css.converter.PaintConverter.RadialGradientConverter.getInstance(); 408 break; 409 410 case "javafx.css.converter.SizeConverter" : 411 styleConverter = javafx.css.converter.SizeConverter.getInstance(); 412 break; 413 case "javafx.css.converter.SizeConverter$SequenceConverter" : 414 styleConverter = javafx.css.converter.SizeConverter.SequenceConverter.getInstance(); 415 break; 416 417 case "javafx.css.converter.StringConverter" : 418 styleConverter = javafx.css.converter.StringConverter.getInstance(); 419 break; 420 case "javafx.css.converter.StringConverter$SequenceConverter" : 421 styleConverter = javafx.css.converter.StringConverter.SequenceConverter.getInstance(); 422 break; 423 case "javafx.css.converter.URLConverter" : 424 styleConverter = javafx.css.converter.URLConverter.getInstance(); 425 break; 426 case "javafx.css.converter.URLConverter$SequenceConverter" : 427 styleConverter = javafx.css.converter.URLConverter.SequenceConverter.getInstance(); 428 break; 429 430 // Region stuff - including 2.x class names 431 case "com.sun.javafx.scene.layout.region.BackgroundPositionConverter" : 432 case "com.sun.javafx.scene.layout.region.BackgroundImage$BackgroundPositionConverter" : 433 styleConverter = com.sun.javafx.scene.layout.region.BackgroundPositionConverter.getInstance(); 434 break; 435 case "com.sun.javafx.scene.layout.region.BackgroundSizeConverter" : 436 case "com.sun.javafx.scene.layout.region.BackgroundImage$BackgroundSizeConverter" : 437 styleConverter = com.sun.javafx.scene.layout.region.BackgroundSizeConverter.getInstance(); 438 break; 439 case "com.sun.javafx.scene.layout.region.BorderImageSliceConverter" : 440 case "com.sun.javafx.scene.layout.region.BorderImage$SliceConverter" : 441 styleConverter = com.sun.javafx.scene.layout.region.BorderImageSliceConverter.getInstance(); 442 break; 443 case "com.sun.javafx.scene.layout.region.BorderImageWidthConverter" : 444 styleConverter = com.sun.javafx.scene.layout.region.BorderImageWidthConverter.getInstance(); 445 break; 446 case "com.sun.javafx.scene.layout.region.BorderImageWidthsSequenceConverter" : 447 styleConverter = com.sun.javafx.scene.layout.region.BorderImageWidthsSequenceConverter.getInstance(); 448 break; 449 case "com.sun.javafx.scene.layout.region.BorderStrokeStyleSequenceConverter" : 450 case "com.sun.javafx.scene.layout.region.StrokeBorder$BorderStyleSequenceConverter" : 451 styleConverter = com.sun.javafx.scene.layout.region.BorderStrokeStyleSequenceConverter.getInstance(); 452 break; 453 case "com.sun.javafx.scene.layout.region.BorderStyleConverter" : 454 case "com.sun.javafx.scene.layout.region.StrokeBorder$BorderStyleConverter" : 455 styleConverter = com.sun.javafx.scene.layout.region.BorderStyleConverter.getInstance(); 456 break; 457 case "com.sun.javafx.scene.layout.region.LayeredBackgroundPositionConverter" : 458 case "com.sun.javafx.scene.layout.region.BackgroundImage$LayeredBackgroundPositionConverter" : 459 styleConverter = com.sun.javafx.scene.layout.region.LayeredBackgroundPositionConverter.getInstance(); 460 break; 461 case "com.sun.javafx.scene.layout.region.LayeredBackgroundSizeConverter" : 462 case "com.sun.javafx.scene.layout.region.BackgroundImage$LayeredBackgroundSizeConverter" : 463 styleConverter = com.sun.javafx.scene.layout.region.LayeredBackgroundSizeConverter.getInstance(); 464 break; 465 case "com.sun.javafx.scene.layout.region.LayeredBorderPaintConverter" : 466 case "com.sun.javafx.scene.layout.region.StrokeBorder$LayeredBorderPaintConverter" : 467 styleConverter = com.sun.javafx.scene.layout.region.LayeredBorderPaintConverter.getInstance(); 468 break; 469 case "com.sun.javafx.scene.layout.region.LayeredBorderStyleConverter" : 470 case "com.sun.javafx.scene.layout.region.StrokeBorder$LayeredBorderStyleConverter" : 471 styleConverter = com.sun.javafx.scene.layout.region.LayeredBorderStyleConverter.getInstance(); 472 break; 473 case "com.sun.javafx.scene.layout.region.RepeatStructConverter" : 474 case "com.sun.javafx.scene.layout.region.BackgroundImage$BackgroundRepeatConverter" : 475 case "com.sun.javafx.scene.layout.region.BorderImage$RepeatConverter" : 476 styleConverter = com.sun.javafx.scene.layout.region.RepeatStructConverter.getInstance(); 477 break; 478 case "com.sun.javafx.scene.layout.region.SliceSequenceConverter" : 479 case "com.sun.javafx.scene.layout.region.BorderImage$SliceSequenceConverter" : 480 styleConverter = com.sun.javafx.scene.layout.region.SliceSequenceConverter.getInstance(); 481 break; 482 case "com.sun.javafx.scene.layout.region.StrokeBorderPaintConverter" : 483 case "com.sun.javafx.scene.layout.region.StrokeBorder$BorderPaintConverter" : 484 styleConverter = com.sun.javafx.scene.layout.region.StrokeBorderPaintConverter.getInstance(); 485 break; 486 case "com.sun.javafx.scene.layout.region.Margins$Converter" : 487 styleConverter = com.sun.javafx.scene.layout.region.Margins.Converter.getInstance(); 488 break; 489 case "com.sun.javafx.scene.layout.region.Margins$SequenceConverter" : 490 styleConverter = com.sun.javafx.scene.layout.region.Margins.SequenceConverter.getInstance(); 491 break; 492 case "javafx.scene.layout.CornerRadiiConverter" : // Fix for RT-39665 493 case "com.sun.javafx.scene.layout.region.CornerRadiiConverter" : 494 styleConverter = CornerRadiiConverter.getInstance(); 495 break; 496 497 // parser stuff 498 case "javafx.css.converter.DeriveColorConverter": 499 case "com.sun.javafx.css.parser.DeriveColorConverter" : 500 styleConverter = DeriveColorConverter.getInstance(); 501 break; 502 case "javafx.css.converter.DeriveSizeConverter": 503 case "com.sun.javafx.css.parser.DeriveSizeConverter" : 504 styleConverter = DeriveSizeConverter.getInstance(); 505 break; 506 case "javafx.css.converter.LadderConverter": 507 case "com.sun.javafx.css.parser.LadderConverter" : 508 styleConverter = LadderConverter.getInstance(); 509 break; 510 case "javafx.css.converter.StopConverter": 511 case "com.sun.javafx.css.parser.StopConverter" : 512 styleConverter = StopConverter.getInstance(); 513 break; 514 515 default : 516 final PlatformLogger logger = Logging.getCSSLogger(); 517 if (logger.isLoggable(Level.SEVERE)) { 518 logger.severe("StyleConverter : converter Class is null for : "+converterClass); 519 } 520 break; 521 } 522 523 return styleConverter; 524 } 525 526 527 /** 528 * The StringStore class 529 * @since 9 530 */ 531 public static class StringStore { 532 private final Map<String,Integer> stringMap = new HashMap<String,Integer>(); 533 public final List<String> strings = new ArrayList<String>(); 534 535 public int addString(String s) { 536 Integer index = stringMap.get(s); 537 if (index == null) { 538 index = strings.size(); 539 strings.add(s); 540 stringMap.put(s,index); 541 } 542 return index; 543 } 544 545 public void writeBinary(DataOutputStream os) throws IOException { 546 os.writeShort(strings.size()); 547 if (stringMap.containsKey(null)) { 548 Integer index = stringMap.get(null); 549 os.writeShort(index); 550 } else { 551 os.writeShort(-1); 552 } 553 for (int n=0; n<strings.size(); n++) { 554 String s = strings.get(n); 555 if (s == null) continue; 556 os.writeUTF(s); 557 } 558 } 559 560 // TODO: this isn't parallel with writeBinary 561 public static String[] readBinary(DataInputStream is) throws IOException { 562 int nStrings = is.readShort(); 563 int nullIndex = is.readShort(); 564 String[] strings = new String[nStrings]; 565 java.util.Arrays.fill(strings, null); 566 for (int n=0; n<nStrings; n++) { 567 if (n == nullIndex) continue; 568 strings[n] = is.readUTF(); 569 } 570 return strings; 571 } 572 } 573 }