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