1 /*
   2  * Copyright (c) 2012, 2014, Oracle and/or its affiliates.
   3  * All rights reserved. Use is subject to license terms.
   4  *
   5  * This file is available and licensed under the following license:
   6  *
   7  * Redistribution and use in source and binary forms, with or without
   8  * modification, are permitted provided that the following conditions
   9  * are met:
  10  *
  11  *  - Redistributions of source code must retain the above copyright
  12  *    notice, this list of conditions and the following disclaimer.
  13  *  - Redistributions in binary form must reproduce the above copyright
  14  *    notice, this list of conditions and the following disclaimer in
  15  *    the documentation and/or other materials provided with the distribution.
  16  *  - Neither the name of Oracle Corporation nor the names of its
  17  *    contributors may be used to endorse or promote products derived
  18  *    from this software without specific prior written permission.
  19  *
  20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  23  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  24  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  25  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  26  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  27  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  28  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  30  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  31  */
  32 package com.oracle.javafx.scenebuilder.kit.editor.panel.css;
  33 
  34 import com.oracle.javafx.scenebuilder.kit.editor.panel.inspector.editors.EditorUtils;
  35 import com.oracle.javafx.scenebuilder.kit.metadata.util.ColorEncoder;
  36 import com.oracle.javafx.scenebuilder.kit.util.Deprecation;
  37 import com.oracle.javafx.scenebuilder.kit.util.MathUtils;
  38 import com.sun.javafx.css.Declaration;
  39 import com.sun.javafx.css.Rule;
  40 import com.sun.javafx.css.Size;
  41 import com.sun.javafx.css.converters.PaintConverter;
  42 import com.sun.javafx.css.converters.PaintConverter.LinearGradientConverter;
  43 import com.sun.javafx.css.parser.DeriveColorConverter;
  44 import com.sun.javafx.css.parser.DeriveSizeConverter;
  45 import com.sun.javafx.css.parser.LadderConverter;
  46 import java.lang.reflect.Array;
  47 import java.util.ArrayList;
  48 import java.util.Collection;
  49 import java.util.List;
  50 import java.util.Locale;
  51 import java.util.Map;
  52 import javafx.css.ParsedValue;
  53 import javafx.geometry.Insets;
  54 import javafx.geometry.Side;
  55 import javafx.scene.effect.Effect;
  56 import javafx.scene.image.Image;
  57 import javafx.scene.layout.Background;
  58 import javafx.scene.layout.BackgroundFill;
  59 import javafx.scene.layout.BackgroundImage;
  60 import javafx.scene.layout.BackgroundSize;
  61 import javafx.scene.layout.Border;
  62 import javafx.scene.layout.BorderImage;
  63 import javafx.scene.layout.BorderStroke;
  64 import javafx.scene.layout.BorderWidths;
  65 import javafx.scene.layout.CornerRadii;
  66 import javafx.scene.paint.Color;
  67 import javafx.scene.paint.Paint;
  68 import javafx.scene.text.Font;
  69 
  70 /**
  71  *
  72  * @treatAsPrivate
  73  */
  74 public class CssValueConverter {
  75 
  76     private static final List<String> STRING_VALUE = new ArrayList<>();
  77     private static final List<String> SINGLE_WHEN_EQUALITY = new ArrayList<>();
  78 
  79     private CssValueConverter() {
  80         assert false;
  81     }
  82 
  83     @SuppressWarnings("rawtypes")
  84     public static Object convert(ParsedValue pv) {
  85         Object value = null;
  86         if (pv == null) {
  87             return null;
  88         }
  89 
  90         if (pv.getConverter() != null) {
  91             try {
  92                 @SuppressWarnings("unchecked")
  93                 Object converted = pv.getConverter().convert(pv, null);
  94                 value = converted;
  95             } catch (RuntimeException ex) {
  96                 // OK, can't be resolved, a lookup possibly
  97             }
  98         } else {
  99             value = pv.getValue();
 100         }
 101         if (value instanceof ParsedValue) {
 102             value = convert((ParsedValue) value);
 103         }
 104         return value;
 105     }
 106 
 107     // Retrieve a CSS String from a value set thanks to CSS
 108     public static String toCssString(String property, Rule rule, Object fxValue) {
 109         try {
 110             return getValue(property, rule, fxValue);
 111         } catch (IllegalArgumentException ex) {
 112             return getValue(property, null, fxValue);
 113         }
 114     }
 115 
 116     public static String toCssString(String property, Object fxValue) {
 117         return getValue(property, null, fxValue);
 118     }
 119 
 120     public static String toCssString(Object fxValue) {
 121         return getValue(null, null, fxValue);
 122     }
 123 
 124     // Retrieve the value for a sub property.
 125     public static Object getSubPropertyValue(String property, Object value) {
 126         if (value instanceof Collection) {
 127             Collection<?> values = (Collection<?>) value;
 128             List<Object> subValues = new ArrayList<>();
 129             for (Object bf : values) {
 130                 subValues.add(getSubPropertyValue(property, bf));
 131             }
 132             return subValues;
 133         } else if (value != null && value.getClass().isArray()) {
 134             Object newArray = Array.newInstance(value.getClass().getComponentType(), Array.getLength(value));
 135             for (int i = 0; i < Array.getLength(value); i++) {
 136                 Array.set(newArray, i, getSubPropertyValue(property, Array.get(value, i)));
 137             }
 138             return newArray;
 139 
 140             //
 141             // Background
 142             //
 143         } else if (value instanceof Background) {
 144             Background background = (Background) value;
 145             if (background.getFills() != null) {
 146                 return getSubPropertyValue(property, background.getFills());
 147             } else if (background.getImages() != null) {
 148                 return getSubPropertyValue(property, background.getImages());
 149             }
 150         } else if (value instanceof BackgroundFill) {
 151             return subBackgroundFill(property, (BackgroundFill) value);
 152         } else if (value instanceof BackgroundImage) {
 153             return subBackgroundImage(property, (BackgroundImage) value);
 154 
 155             //
 156             // Border
 157             //
 158         } else if (value instanceof Border) {
 159             Border border = (Border) value;
 160             if (border.getStrokes() != null) {
 161                 return getSubPropertyValue(property, border.getStrokes());
 162             } else if (border.getImages() != null) {
 163                 return getSubPropertyValue(property, border.getImages());
 164             }
 165         } else if (value instanceof BorderStroke) {
 166             return subBorderStroke(property, (BorderStroke) value);
 167         } else if (value instanceof BorderImage) {
 168             return subBorderImage(property, (BorderImage) value);
 169 
 170             //
 171             // Font
 172             //
 173         } else if (value instanceof Font) {
 174             return subFont(property, (Font) value);
 175         }
 176         return getValue(property, null, value);
 177     }
 178 
 179     static {
 180         STRING_VALUE.add("-fx-skin");//NOI18N
 181         STRING_VALUE.add("-fx-shape");//NOI18N
 182     }
 183 
 184     private static String format(String property, String value) {
 185         if (STRING_VALUE.contains(property)) {
 186             return "\"" + value + "\"";//NOI18N
 187         } else {
 188             return value;
 189         }
 190     }
 191 
 192     // FX to String value transformation entry point.
 193     private static String getValue(String property, Rule r, Object eventValue) throws IllegalArgumentException {
 194         if (r == null) {
 195             return format(property, retrieveValue(property, eventValue));
 196         }
 197 
 198         for (Declaration d : r.getDeclarations()) {
 199             if (d.getProperty().equals(property)) {
 200                 if (property.equals("-fx-background-radius") || property.equals("-fx-border-radius")) { //NOI18N
 201                     return format(property, getRadiusCssString(property, d.getParsedValue()));
 202                 } else {
 203                     return format(property, getCssString(property, d.getParsedValue()));
 204                 }
 205             }
 206         }
 207         throw new IllegalArgumentException("Can't compute a value");//NOI18N
 208     }
 209 
 210     static {
 211         SINGLE_WHEN_EQUALITY.add("-fx-padding"); //NOI18N
 212         SINGLE_WHEN_EQUALITY.add("-fx-background-radius"); //NOI18N
 213         SINGLE_WHEN_EQUALITY.add("-fx-background-insets"); //NOI18N
 214         SINGLE_WHEN_EQUALITY.add("-fx-border-color"); //NOI18N
 215         SINGLE_WHEN_EQUALITY.add("-fx-border-radius"); //NOI18N
 216         SINGLE_WHEN_EQUALITY.add("-fx-border-insets"); //NOI18N
 217         SINGLE_WHEN_EQUALITY.add("-fx-border-image-insets"); //NOI18N
 218         SINGLE_WHEN_EQUALITY.add("-fx-border-image-slice"); //NOI18N
 219         SINGLE_WHEN_EQUALITY.add("-fx-border-image-width"); //NOI18N
 220 
 221     }
 222 
 223     private static boolean singleForEquality(String prop) {
 224         return SINGLE_WHEN_EQUALITY.contains(prop);
 225     }
 226 
 227     // The difference between retrieveValue and getCssStringValue
 228     // is that the ParsedValue is converted in the retrieveValue case, and is not converted in the 
 229     // getCssStringValue
 230     // When converting, we loose the CSS textual format present in the CSS source,
 231     // for instance the lookup information, or 'em' unit.
 232     @SuppressWarnings("rawtypes")
 233     private static String getCssString(String property, ParsedValue value) {
 234 
 235         // TODO : this method should be rewritten in a cleaner way...
 236         if (value == null) {
 237             return "null"; //NOI18N
 238         }
 239 
 240         // I don't like that but this is needed to only have a conversion for gradient.
 241         // The gradient.toString is much better than the ParsedValue.toString.
 242         if (value.getConverter() instanceof LinearGradientConverter
 243                 || value.getConverter() instanceof PaintConverter.RadialGradientConverter) {
 244 
 245             try {
 246                 @SuppressWarnings("unchecked")//NOI18N
 247                 Object converted = value.getConverter().convert(value, null);
 248 
 249                 return toCssString(converted);
 250             } catch (RuntimeException ex) {
 251             }
 252         }
 253 
 254         Object obj = value.getValue();
 255         if (obj instanceof ParsedValue) {
 256             return getCssString(property, (ParsedValue) obj);
 257         }
 258         StringBuilder builder = new StringBuilder();
 259         boolean isDerive = value.getConverter() instanceof DeriveColorConverter || value.getConverter() instanceof DeriveSizeConverter;
 260         boolean isLadder = value.getConverter() instanceof LadderConverter;
 261         if (isDerive) {
 262             builder.append("derive("); //NOI18N
 263         }
 264         if (isLadder) {
 265             builder.append("ladder("); //NOI18N
 266         }
 267         if (obj instanceof ParsedValue[]) {
 268             ParsedValue[] array = (ParsedValue[]) obj;
 269             boolean isArrayValue = false;
 270             if (array.length >= 1) {
 271                 ParsedValue pval = array[0];
 272                 Object val = null;
 273                 if (pval != null) {
 274                     if (pval.getConverter() instanceof LinearGradientConverter
 275                             || pval.getConverter() instanceof PaintConverter.RadialGradientConverter) {
 276                         val = null;
 277                     } else {
 278                         val = pval.getValue();
 279                     }
 280                 }
 281                 isArrayValue = val != null && val.getClass().isArray();
 282             }
 283             boolean singleForEquality = singleForEquality(property) && !isArrayValue;
 284             StringBuilder b = new StringBuilder();
 285             if (singleForEquality) {
 286                 String latest = null;
 287                 boolean areEquals = true;
 288                 List<String> values = new ArrayList<>(array.length);
 289                 for (ParsedValue v : array) {
 290                     String current = getCssString(property, v);
 291                     values.add(current);
 292                     areEquals &= (latest == null || current.equals(latest));
 293                     latest = current;
 294                 }
 295                 if (areEquals) {
 296                     String val = values.get(0);
 297                     val = removeDotZeroPxPercent(val);
 298                     b.append(val);
 299                 } else {
 300                     for (int i = 0; i < values.size(); i++) {
 301                         b.append(values.get(i));
 302                         if (i < array.length - 1) {
 303                             b.append(" "); //NOI18N
 304                         }
 305                     }
 306                 }
 307             } else {
 308                 for (int i = 0; i < array.length; i++) {
 309                     ParsedValue v = array[i];
 310                     String val = getCssString(property, v);
 311                     val = removeDotZeroPxPercent(val);
 312                     b.append(val);
 313                     if ((i < array.length - 1) && val.length() > 0) {
 314                         b.append(", "); //NOI18N
 315                     }
 316                 }
 317             }
 318 
 319             builder.append(b.toString());
 320         } else {
 321             if (obj instanceof ParsedValue[][]) {
 322                 ParsedValue[][] arr = (ParsedValue[][]) obj;
 323                 for (int i = 0; i < arr.length; i++) {
 324                     String val = retrieveValue(property, arr[i]);
 325                     builder.append(val);
 326                     if ((i < arr.length - 1) && val.length() > 0) {
 327                         builder.append(", "); //NOI18N
 328                     }
 329                 }
 330             } else {
 331                 builder.append(retrieveValue(property, obj));
 332             }
 333         }
 334         if (isDerive || isLadder) {
 335             builder.append(")"); //NOI18N
 336         }
 337         return builder.toString();
 338     }
 339 
 340     @SuppressWarnings("rawtypes")
 341     private static String getRadiusCssString(String property, ParsedValue value) {
 342         // TODO : Ideally should be included in the generic getCssString() method
 343         
 344         // See  http://www.w3.org/TR/css3-background/#the-border-radius 
 345         
 346         assert property.equals("-fx-background-radius") || property.equals("-fx-border-radius"); //NOI18N
 347         StringBuilder sbAll = new StringBuilder();
 348         Object obj = value.getValue();
 349         if (!(obj instanceof ParsedValue[])) {
 350             return null;
 351         }
 352         ParsedValue[] pvArray = (ParsedValue[]) obj;
 353         int index = 0;
 354         for (ParsedValue pvItem : pvArray) {
 355             // We have a CornerRadii representation here:
 356             // double dimension array:
 357             // 1- horizontal/vertical radii
 358             // 2- value per corner
 359             // If all the values are identical, only a single value is used.
 360             obj = pvItem.getValue();
 361             if (!(obj instanceof ParsedValue[][])) {
 362                 return null;
 363             }
 364             ParsedValue[][] pvArray2 = (ParsedValue[][]) obj;
 365             StringBuilder sbCornerRadii = new StringBuilder();
 366             Size initSize = null;
 367             boolean areEquals = true;
 368             int index2 = 0;
 369             for (ParsedValue[] pvArray1 : pvArray2) {
 370                 // horizontal or vertical list 
 371                 for (ParsedValue pvItem2 : pvArray1) {
 372                     obj = pvItem2.getValue();
 373                     if (!(obj instanceof Size)) {
 374                         return null;
 375                     }
 376                     Size size = (Size) obj;
 377                     sbCornerRadii.append(size).append(" "); //NOI18N
 378                     if (initSize == null) {
 379                         initSize = size;
 380                     } else if (!initSize.equals(size)) {
 381                         areEquals = false;
 382                     }
 383                 }
 384                 if (index2 != pvArray2.length - 1) {
 385                     // Separator between the horizontal / vertical lists
 386                     sbCornerRadii.append(" / "); //NOI18N
 387                 }
 388                 index2++;
 389             }
 390             if (areEquals) {
 391                 sbAll.append(initSize);
 392             } else {
 393                 sbAll.append(sbCornerRadii.toString().trim());
 394             }
 395             if (index != pvArray.length - 1) {
 396                 sbAll.append(", "); //NOI18N
 397             }
 398             index++;
 399         }
 400         return removeDotZeroPxPercent(sbAll.toString());
 401     }
 402 
 403     private static String retrieveValue(String property, Object eventValue) {
 404         if (eventValue instanceof ParsedValue) {
 405             eventValue = convert((ParsedValue<?, ?>) eventValue);
 406         }
 407 
 408         if (eventValue == null) {
 409             return "null"; //NOI18N
 410         }
 411         StringBuilder builder = new StringBuilder();
 412         if (eventValue instanceof List) {
 413             List<?> values = (List<?>) eventValue;
 414             int length = values.size();
 415             for (int i = 0; i < length; i++) {
 416                 String val = retrieveValue(property, values.get(i));
 417                 builder.append(val);
 418                 if ((i < length - 1) && val.length() > 0) {
 419                     builder.append(", "); //NOI18N
 420                 }
 421             }
 422         } else if (eventValue.getClass().isArray()) {
 423             int length = Array.getLength(eventValue);
 424             for (int i = 0; i < length; i++) {
 425                 String val = retrieveValue(property, Array.get(eventValue, i));
 426                 builder.append(val);
 427                 if ((i < length - 1) && val.length() > 0) {
 428                     builder.append(", "); //NOI18N
 429                 }
 430             }
 431         } else if (eventValue instanceof Background) {
 432             Background background = (Background) eventValue;
 433             if (background.getFills() != null) {
 434                 return retrieveValue(property, background.getFills());
 435             } else if (background.getImages() != null) {
 436                 return retrieveValue(property, background.getImages());
 437             }
 438         } else if (eventValue instanceof Border) {
 439             Border border = (Border) eventValue;
 440             if (border.getStrokes() != null) {
 441                 return retrieveValue(property, border.getStrokes());
 442             } else if (border.getImages() != null) {
 443                 return retrieveValue(property, border.getImages());
 444             }
 445         } else if (eventValue instanceof BackgroundFill) {
 446             builder.append(backgroundFillToString(property, (BackgroundFill) eventValue));
 447         } else if (eventValue instanceof CornerRadii) {
 448             builder.append(cornerRadiiToString(property, (CornerRadii) eventValue));
 449         } else if (eventValue instanceof BackgroundImage) {
 450             builder.append(backgroundImageToString(property, (BackgroundImage) eventValue));
 451         } else if (eventValue instanceof BorderStroke) {
 452             builder.append(borderStrokeToString(property, (BorderStroke) eventValue));
 453         } else if (eventValue instanceof BorderImage) {
 454             builder.append(borderImageToString(property, (BorderImage) eventValue));
 455         } else if (eventValue instanceof Font) {
 456             builder.append(fontToString(property, (Font) eventValue));
 457         } else if (eventValue instanceof Paint) {
 458             builder.append(paintToString((Paint) eventValue).toLowerCase(Locale.ROOT));
 459         } else if (eventValue instanceof Insets) {
 460             builder.append(insetsValue((Insets) eventValue));
 461         } else if (eventValue instanceof Effect) {
 462             builder.append(effectValue((Effect) eventValue));
 463         } else {
 464             String str = EditorUtils.valAsStr(eventValue);
 465             if (str == null) {
 466                 str = "null"; //NOI18N
 467             } else {
 468                 str = str.replaceAll("\n", " ");//NOI18N
 469                 // Remove memory address if any
 470                 str = str.split("@")[0]; //NOI18N
 471                 str = removeDotZeroPxPercent(str);
 472             }
 473             builder.append(str);
 474         }
 475         return builder.toString();
 476     }
 477 
 478     private static String getColorAsWebString(Color c) {
 479         int red = (int) Math.round(c.getRed() * 255.0);
 480         int green = (int) Math.round(c.getGreen() * 255.0);
 481         int blue = (int) Math.round(c.getBlue() * 255.0);
 482         int alpha = (int) Math.round(c.getOpacity() * 255.0);
 483         if (alpha == 255) {
 484             return String.format("#%02x%02x%02x", red, green, blue); //NOI18N
 485         } else {
 486             return String.format("#%02x%02x%02x%02x", red, green, blue, alpha); //NOI18N
 487         }
 488     }
 489 
 490     private static String getColorAsString(Color color) {
 491         if (isStandardColor(color)) {
 492             return getStandardColorAsString(color);
 493         } else {
 494             return getColorAsWebString(color);
 495         }
 496     }
 497 
 498     private static boolean isStandardColor(Color c) {
 499         return standardColors.containsKey(c);
 500     }
 501     static Map<Color, String> standardColors = ColorEncoder.getStandardColorNames();
 502 
 503     private static String getStandardColorAsString(Color c) {
 504         return standardColors.get(c);
 505     }
 506 
 507     private static String backgroundFillToString(String property, BackgroundFill bf) {
 508         if (property == null) {
 509             return bf.toString();
 510         }
 511         StringBuilder builder = new StringBuilder();
 512         if (property.equals("-fx-background-color")) { //NOI18N
 513             Paint p = bf.getFill();
 514             builder.append(paintToString(p));
 515         } else {
 516             if (property.equals("-fx-background-insets")) { //NOI18N
 517                 builder.append(insetsValue(bf.getInsets()));
 518             } else {
 519                 //the top right, bottom right, bottom left, and top left
 520                 if (property.equals("-fx-background-radius")) { //NOI18N
 521                     handleCornerRadii(bf.getRadii(), builder);
 522                 }
 523             }
 524         }
 525         return builder.toString();
 526     }
 527 
 528     private static String cornerRadiiToString(String property, CornerRadii cr) {
 529         if (property == null) {
 530             return cr.toString();
 531         }
 532         StringBuilder builder = new StringBuilder();
 533         handleCornerRadii(cr, builder);
 534         return builder.toString();
 535     }
 536 
 537     private static String backgroundImageToString(String property, BackgroundImage bi) {
 538         if (property == null) {
 539             return bi.toString();
 540         }
 541         StringBuilder builder = new StringBuilder();
 542         if (property.equals("-fx-background-image")) { //NOI18N
 543             Image p = bi.getImage();
 544             builder.append(Deprecation.getUrl(p));
 545         } else {
 546             if (property.equals("-fx-background-position")) {             //NOI18N
 547                 double left = 0, right = 0, top = 0, bottom = 0;
 548                 if (bi.getPosition().getHorizontalSide() == Side.LEFT) {
 549                     left = bi.getPosition().getHorizontalPosition();
 550                 } else {
 551                     right = bi.getPosition().getHorizontalPosition();
 552                 }
 553                 if (bi.getPosition().getVerticalSide() == Side.TOP) {
 554                     top = bi.getPosition().getVerticalPosition();
 555                 } else {
 556                     bottom = bi.getPosition().getVerticalPosition();
 557                 }
 558                 builder.append("left:"); //NOI18N
 559                 builder.append(EditorUtils.valAsStr(left));
 560                 builder.append(" right:"); //NOI18N
 561                 builder.append(EditorUtils.valAsStr(right));
 562                 builder.append(" top:"); //NOI18N
 563                 builder.append(EditorUtils.valAsStr(top));
 564                 builder.append(" bottom:"); //NOI18N
 565                 builder.append(EditorUtils.valAsStr(bottom));
 566             } else {
 567                 if (property.equals("-fx-background-repeat")) {          //NOI18N
 568                     if (bi.getRepeatX() != null) {
 569                         builder.append(bi.getRepeatX().toString());
 570                     } else {
 571                         if (bi.getRepeatY() != null) {
 572                             builder.append(bi.getRepeatY().toString());
 573                         } else {
 574                             builder.append("unknown repeat"); //NOI18N
 575                         }
 576                     }
 577                 } else {
 578                     if (property.equals("-fx-background-size")) { //NOI18N
 579                         BackgroundSize bs = bi.getSize();
 580                         if (bs.isContain()) {
 581                             builder.append("contain"); //NOI18N
 582                         } else {
 583                             if (bs.isCover()) {
 584                                 builder.append("cover"); //NOI18N
 585                             } else {
 586                                 if (bs.getWidth() == BackgroundSize.AUTO) {
 587                                     builder.append("width: auto"); //NOI18N
 588                                 } else {
 589                                     builder.append("width: ").append(EditorUtils.valAsStr(bs.getWidth())); //NOI18N
 590                                 }
 591                                 if (bs.getHeight() == BackgroundSize.AUTO) {
 592                                     builder.append("height: auto"); //NOI18N
 593                                 } else {
 594                                     builder.append("height: ").append(EditorUtils.valAsStr(bs.getHeight())); //NOI18N
 595                                 }
 596                             }
 597                         }
 598                     }
 599                 }
 600             }
 601         }
 602         return builder.toString();
 603     }
 604 
 605     private static String borderImageToString(String property, BorderImage bi) {
 606         if (property == null) {
 607             return bi.toString();
 608         }
 609         StringBuilder builder = new StringBuilder();
 610         if (property.equals("-fx-border-image")) { //NOI18N
 611             Image p = bi.getImage();
 612             builder.append(Deprecation.getUrl(p));
 613         } else {
 614             if (property.equals("-fx-background-position")) {             //NOI18N
 615 
 616             } else {
 617                 if (property.equals("-fx-border-image-repeat")) {          //NOI18N
 618                     if (bi.getRepeatX() != null) {
 619                         builder.append(bi.getRepeatX().toString());
 620                     } else {
 621                         if (bi.getRepeatY() != null) {
 622                             builder.append(bi.getRepeatY().toString());
 623                         } else {
 624                             builder.append("unknown repeat"); //NOI18N
 625                         }
 626                     }
 627                 } else {
 628                     if (property.equals("-fx-border-image-insets")) { //NOI18N
 629                         builder.append(insetsValue(bi.getInsets()));
 630                     } else {
 631                         if (property.equals("-fx-border-image-width")) { //NOI18N
 632                             BorderWidths bw = bi.getWidths();
 633                             if (MathUtils.equals(bw.getTop(), bw.getBottom())
 634                                     && MathUtils.equals(bw.getLeft(), bw.getRight())) {
 635                                 builder.append(EditorUtils.valAsStr(bw.getTop()));
 636                             } else {
 637                                 builder.append(EditorUtils.valAsStr(bw.getTop())).append(" "). //NOI18N
 638                                         append(EditorUtils.valAsStr(bw.getRight())).append(" "). //NOI18N
 639                                         append(EditorUtils.valAsStr(bw.getBottom())).append(" "). //NOI18N
 640                                         append(EditorUtils.valAsStr(bw.getLeft()));
 641                             }
 642                         } else {
 643                             if (property.equals("-fx-border-image-slice")) { //NOI18N
 644                                 BorderWidths bw = bi.getSlices();
 645                                 if (MathUtils.equals(bw.getTop(), bw.getBottom())
 646                                         && MathUtils.equals(bw.getLeft(), bw.getRight())) {
 647                                     builder.append(EditorUtils.valAsStr(bw.getTop()));
 648                                 } else {
 649                                     builder.append(EditorUtils.valAsStr(bw.getTop())).append(" "). //NOI18N
 650                                             append(EditorUtils.valAsStr(bw.getRight())).append(" "). //NOI18N
 651                                             append(EditorUtils.valAsStr(bw.getBottom())).append(" "). //NOI18N
 652                                             append(EditorUtils.valAsStr(bw.getLeft()));
 653                                 }
 654                             }
 655                         }
 656                     }
 657                 }
 658             }
 659         }
 660         return builder.toString();
 661     }
 662 
 663     private static String borderStrokeToString(String property, BorderStroke bs) {
 664         if (property == null) {
 665             return bs.toString();
 666         }
 667         StringBuilder builder = new StringBuilder();
 668         //top, right, bottom, and left 
 669         if (property.equals("-fx-border-color")) { //NOI18N
 670             if (bs.getTopStroke().equals(bs.getBottomStroke())
 671                     && bs.getRightStroke().equals(bs.getBottomStroke())
 672                     && bs.getLeftStroke().equals(bs.getBottomStroke())) {
 673                 builder.append(paintToString(bs.getBottomStroke()));
 674             } else {
 675                 builder.append(paintToString(bs.getTopStroke())).append(" "); //NOI18N
 676                 builder.append(paintToString(bs.getRightStroke())).append(" "); //NOI18N
 677                 builder.append(paintToString(bs.getBottomStroke())).append(" "); //NOI18N
 678                 builder.append(paintToString(bs.getLeftStroke()));
 679             }
 680         } else {
 681             if (property.equals("-fx-border-insets")) { //NOI18N
 682                 builder.append(insetsValue(bs.getInsets()));
 683             } else {
 684                 //the top right, bottom right, bottom left, and top left
 685                 if (property.equals("-fx-border-radius")) { //NOI18N
 686                     handleCornerRadii(bs.getRadii(), builder);
 687                 } else {
 688                     if (property.equals("-fx-border-style")) { //NOI18N
 689                         builder.append(bs.getTopStyle().toString()).append(", "); //NOI18N
 690                         builder.append(bs.getRightStyle().toString()).append(", "); //NOI18N
 691                         builder.append(bs.getBottomStyle().toString()).append(", "); //NOI18N
 692                         builder.append(bs.getLeftStyle().toString());
 693                     } else {
 694                         if (property.equals("-fx-border-width")) { //NOI18N
 695                             BorderWidths bw = bs.getWidths();
 696                             if (MathUtils.equals(bw.getTop(), bw.getBottom())
 697                                     && MathUtils.equals(bw.getRight(), bw.getBottom())
 698                                     && MathUtils.equals(bw.getLeft(), bw.getBottom())) {
 699                                 builder.append(EditorUtils.valAsStr(bw.getBottom()));
 700                             } else {
 701                                 builder.append(EditorUtils.valAsStr(bw.getTop())).append(" "). //NOI18N
 702                                         append(EditorUtils.valAsStr(bw.getRight())).append(" "). //NOI18N
 703                                         append(EditorUtils.valAsStr(bw.getBottom())).append(" "). //NOI18N
 704                                         append(EditorUtils.valAsStr(bw.getLeft()));
 705                             }
 706                         }
 707                     }
 708                 }
 709             }
 710         }
 711         return builder.toString();
 712     }
 713 
 714     private static String paintToString(Paint p) {
 715         if (p instanceof Color) {
 716             return getColorAsString((Color) p).toLowerCase(Locale.ROOT);
 717         } else {
 718             String gradient = p.toString();
 719             // Workaround for RT-22910
 720             gradient = gradient.replaceAll("0x", "#");//NOI18N
 721             gradient = removeDotZeroPxPercent(gradient);
 722             return gradient;
 723         }
 724     }
 725 
 726     private static String fontToString(String property, Font font) {
 727         if (property == null) {
 728             return removeAllDotZero(font.toString());
 729         }
 730         StringBuilder builder = new StringBuilder();
 731         if (property.equals("-fx-font")) { //NOI18N
 732             String size = EditorUtils.valAsStr(font.getSize()); //NOI18N
 733             String previewStr = font.getFamily() + " " + size + "px" //NOI18N
 734                     + (!font.getName().equals(font.getFamily())
 735                     && !"Regular".equals(font.getStyle()) //NOI18N
 736                     ? " (" + font.getStyle() + ")" : ""); //NOI18N
 737             builder.append(previewStr);
 738         } else {
 739             if (property.equals("-fx-font-size")) { //NOI18N
 740                 double p = font.getSize();
 741                 builder.append(EditorUtils.valAsStr(p)).append("px"); //NOI18N
 742 
 743             } else {
 744                 if (property.equals("-fx-font-family")) { //NOI18N
 745                     builder.append(font.getFamily());
 746                 } else {
 747                     if (property.equals("-fx-font-weight")) { //NOI18N
 748                         // There is no such property.
 749                         builder.append(removeAllDotZero(font.toString()));
 750                     } else {
 751                         if (property.equals("-fx-font-style")) { //NOI18N
 752                             builder.append(font.getStyle());
 753                         }
 754                     }
 755                 }
 756             }
 757         }
 758         return builder.toString();
 759     }
 760 
 761     private static String insetsValue(Insets insets) {
 762         if (MathUtils.equals(insets.getBottom(), insets.getLeft())
 763                 && MathUtils.equals(insets.getRight(), insets.getLeft())
 764                 && MathUtils.equals(insets.getTop(), insets.getLeft())) {
 765             return EditorUtils.valAsStr(insets.getLeft());
 766         } else {
 767             return EditorUtils.valAsStr(insets.getTop()) + " " + EditorUtils.valAsStr(insets.getRight()) //NOI18N
 768                     + " " + EditorUtils.valAsStr(insets.getBottom()) + " " + EditorUtils.valAsStr(insets.getLeft()); //NOI18N
 769         }
 770     }
 771 
 772     private static String effectValue(Effect effect) {
 773         StringBuilder strBuild = new StringBuilder();
 774         Effect adding = effect;
 775         while (adding != null) {
 776             strBuild.append(adding.getClass().getSimpleName());
 777             adding = getEffectInput(adding);
 778             if (adding != null) {
 779                 strBuild.append(", "); //NOI18N
 780             }
 781         }
 782         return strBuild.toString();
 783     }
 784 
 785     private static Object subBackgroundFill(String property, BackgroundFill bf) {
 786         if (property == null) {
 787             return bf;
 788         }
 789         if (property.equals("-fx-background-color")) { //NOI18N
 790             return bf.getFill();
 791         } else {
 792             if (property.equals("-fx-background-insets")) { //NOI18N
 793                 return bf.getInsets();
 794             } else {
 795                 return backgroundFillToString(property, bf);
 796             }
 797         }
 798     }
 799 
 800     private static Object subBackgroundImage(String property, BackgroundImage bi) {
 801         if (property == null) {
 802             return bi;
 803         }
 804         if (property.equals("-fx-background-image")) { //NOI18N
 805             return bi.getImage();
 806         } else {
 807             return backgroundImageToString(property, bi);
 808         }
 809     }
 810 
 811     private static Object subBorderImage(String property, BorderImage bi) {
 812         if (property == null) {
 813             return bi;
 814         }
 815         if (property.equals("-fx-border-image")) { //NOI18N
 816             return bi.getImage();
 817         } else {
 818             return borderImageToString(property, bi);
 819         }
 820     }
 821 
 822     private static Object subBorderStroke(String property, BorderStroke bs) {
 823         if (property == null) {
 824             return bs;
 825         }
 826         //top, right, bottom, and left 
 827         if (property.equals("-fx-border-color")) { //NOI18N
 828             if (bs.getTopStroke().equals(bs.getBottomStroke())
 829                     && bs.getRightStroke().equals(bs.getBottomStroke())
 830                     && bs.getLeftStroke().equals(bs.getBottomStroke())) {
 831                 return bs.getBottomStroke();
 832             } else {
 833                 Paint[] p = new Paint[4];
 834                 p[0] = bs.getTopStroke();
 835                 p[1] = bs.getRightStroke();
 836                 p[2] = bs.getBottomStroke();
 837                 p[3] = bs.getLeftStroke();
 838                 return p;
 839             }
 840         } else {
 841             if (property.equals("-fx-border-insets")) { //NOI18N
 842                 return bs.getInsets();
 843             } else {
 844                 return borderStrokeToString(property, bs);
 845             }
 846         }
 847     }
 848 
 849     private static Object subFont(String property, Font font) {
 850         if (property == null) {
 851             return font;
 852         }
 853         if (property.equals("-fx-font-size")) { //NOI18N
 854             return EditorUtils.valAsStr(font.getSize());
 855         } else {
 856             if (property.equals("-fx-font-style")) { //NOI18N
 857                 return font.getStyle();
 858             } else {
 859                 if (property.equals("-fx-font-family")) { //NOI18N
 860                     return font.getFamily();
 861                 } else {
 862                     if (property.equals("-fx-font-weight")) { //NOI18N
 863                         // No font weight
 864                         return font.getFamily() + " " + font.getStyle(); //NOI18N
 865                     } else {
 866                         return font;
 867                     }
 868                 }
 869             }
 870         }
 871     }
 872 
 873     private static String removeDotZeroPxPercent(String str) {
 874         // Remove ".0" in strings, for "px" and "%" notations
 875         str = str.replaceAll("\\.0px", "px"); //NOI18N
 876         str = str.replaceAll("\\.0em", "em"); //NOI18N
 877         str = str.replaceAll("\\.0\\%", "%"); //NOI18N
 878         return str;
 879     }
 880 
 881     private static String removeAllDotZero(String str) {
 882         // Remove all ".0" in string
 883         str = str.replaceAll("\\.0", ""); //NOI18N
 884         return str;
 885     }
 886 
 887     private static void handleCornerRadii(CornerRadii cr, StringBuilder builder) {
 888         // Each radius has a vertical and horizontal radius
 889         // See  http://www.w3.org/TR/css3-background/#the-border-radius 
 890 
 891         double topLeftH = cr.getTopLeftHorizontalRadius();
 892         double topLeftV = cr.getTopLeftVerticalRadius();
 893         double topRightH = cr.getTopRightHorizontalRadius();
 894         double topRightV = cr.getTopRightVerticalRadius();
 895         double bottomLeftH = cr.getBottomLeftHorizontalRadius();
 896         double bottomLeftV = cr.getBottomLeftVerticalRadius();
 897         double bottomRightH = cr.getBottomRightHorizontalRadius();
 898         double bottomRightV = cr.getBottomRightVerticalRadius();
 899 
 900         if (MathUtils.equals(topLeftH, topLeftV) && MathUtils.equals(topRightH, topRightV)
 901                 && MathUtils.equals(bottomLeftH, bottomLeftV) && MathUtils.equals(bottomRightH, bottomRightV)) {
 902             if (MathUtils.equals(topLeftH, topRightH) && MathUtils.equals(topRightH, bottomLeftH)
 903                     && MathUtils.equals(bottomLeftH, bottomRightH)) {
 904                 // Same radius for all => single value
 905                 builder.append(EditorUtils.valAsStr(topLeftH));
 906             } else {
 907                 // Same value for vertical and horizontal radii 
 908                 // => 4 values for topLeft, topRight, bottomLeft, bottomRight
 909                 builder.append(EditorUtils.valAsStr(topLeftH)).append(" "). //NOI18N
 910                         append(EditorUtils.valAsStr(topRightH)).append(" "). //NOI18N
 911                         append(EditorUtils.valAsStr(bottomRightH)).append(" "). //NOI18N
 912                         append(EditorUtils.valAsStr(bottomLeftH));
 913             }
 914         } else {
 915             // Separate value for each.
 916             // Syntax: "horizontal values / vertical values"
 917             builder.append(EditorUtils.valAsStr(topLeftH)).append(" "). //NOI18N
 918                     append(EditorUtils.valAsStr(topRightH)).append(" "). //NOI18N
 919                     append(EditorUtils.valAsStr(bottomRightH)).append(" "). //NOI18N
 920                     append(EditorUtils.valAsStr(bottomLeftH)).
 921                     append(" / ").//NOI18N
 922                     append(EditorUtils.valAsStr(topLeftV)).append(" "). //NOI18N
 923                     append(EditorUtils.valAsStr(topRightV)).append(" "). //NOI18N
 924                     append(EditorUtils.valAsStr(bottomRightV)).append(" "). //NOI18N
 925                     append(EditorUtils.valAsStr(bottomLeftV));
 926         }
 927     }
 928 
 929 //    @SuppressWarnings({"BroadCatchBlock", "TooBroadCatch"}) //NOI18N
 930     private static Effect getEffectInput(Effect effect) {
 931         Effect found = null;
 932         try {
 933             found = (Effect) effect.getClass().getMethod("getInput").invoke(effect); //NOI18N
 934         } catch (Throwable e) {
 935             // DO NOT use multi-catch syntax here, this generates a FindBugs Warning (because of SecurityException catching)
 936 //                e.printStackTrace();
 937             try {
 938                 found = (Effect) effect.getClass().getMethod("getContentInput").invoke(effect); //NOI18N
 939             } catch (Throwable ee) {
 940                 // DO NOT use multi-catch syntax here, this generates a FindBugs Warning (because of SecurityException catching)
 941 //                    ee.printStackTrace();
 942             }
 943         }
 944         return found;
 945     }
 946 }