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