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