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>&nbsp;&nbsp;&nbsp;&nbsp;
  77  * <code>public Color convert(ParsedValueImpl&lt;String,Color&gt; 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 &quot;true&quot; or &quot;false&quot; 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 [&lt;length&gt; |
 162      * &lt;percentage&gt;]{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 }