1 /* 2 * Copyright (c) 2010, 2014, 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 com.sun.javafx.css; 27 28 import javafx.css.ParsedValue; 29 import javafx.css.StyleConverter; 30 import javafx.scene.paint.Color; 31 import javafx.scene.text.Font; 32 33 import java.io.DataInputStream; 34 import java.io.DataOutputStream; 35 import java.io.IOException; 36 import java.net.MalformedURLException; 37 import java.net.URL; 38 39 /** 40 * Implementation details behind a {@link ParsedValueImpl}. 41 */ 42 public class ParsedValueImpl<V, T> extends ParsedValue<V,T> { 43 44 /** 45 * If value references another property, then the real value needs to 46 * be looked up. 47 */ 48 final private boolean lookup; 49 public final boolean isLookup() { return lookup; } 50 51 /** 52 * If value is itself a ParsedValueImpl or sequence of values, and should any of 53 * those values need to be looked up, then this flag is set. This 54 * does not mean that this particular value needs to be looked up, but 55 * that this value contains a value that needs to be looked up. 56 */ 57 final private boolean containsLookups; 58 public final boolean isContainsLookups() { return containsLookups; } 59 60 private static boolean getContainsLookupsFlag(Object obj) { 61 62 // Assume the value does not contain lookups 63 boolean containsLookupsFlag = false; 64 65 if (obj instanceof Size) { 66 containsLookupsFlag = false; 67 } 68 69 else if(obj instanceof ParsedValueImpl) { 70 ParsedValueImpl value = (ParsedValueImpl)obj; 71 containsLookupsFlag = value.lookup || value.containsLookups; 72 } 73 74 else if(obj instanceof ParsedValueImpl[]) { 75 ParsedValueImpl[] values = (ParsedValueImpl[])obj; 76 for(int v=0; 77 // Bail if value contains lookups 78 // Continue iterating as long as one of the flags is false 79 v<values.length && !containsLookupsFlag; 80 v++) 81 { 82 if (values[v] != null) { 83 containsLookupsFlag = 84 containsLookupsFlag 85 || values[v].lookup 86 || values[v].containsLookups; 87 } 88 } 89 90 } else if(obj instanceof ParsedValueImpl[][]) { 91 ParsedValueImpl[][] values = (ParsedValueImpl[][])obj; 92 for(int l=0; 93 l<values.length && !containsLookupsFlag; 94 l++) 95 { 96 if (values[l] != null) { 97 for(int v=0; 98 v<values[l].length && !containsLookupsFlag; 99 v++) 100 { 101 if (values[l][v] != null) { 102 containsLookupsFlag = 103 containsLookupsFlag 104 || values[l][v].lookup 105 || values[l][v].containsLookups; 106 } 107 } 108 } 109 } 110 } 111 112 return containsLookupsFlag; 113 } 114 115 public static boolean containsFontRelativeSize(ParsedValue parsedValue, boolean percentUnitsAreRelative) { 116 117 // Assume the value does not need a font for conversion 118 boolean needsFont = false; 119 120 Object obj = parsedValue.getValue(); 121 122 if (obj instanceof Size) { 123 Size size = (Size)obj; 124 // percent is only relative for font and font-size properties 125 needsFont = size.getUnits() == SizeUnits.PERCENT 126 ? percentUnitsAreRelative 127 : size.isAbsolute() == false; 128 } 129 130 else if(obj instanceof ParsedValue) { 131 ParsedValue value = (ParsedValueImpl)obj; 132 needsFont = containsFontRelativeSize(value, percentUnitsAreRelative); 133 } 134 135 else if(obj instanceof ParsedValue[]) { 136 ParsedValue[] values = (ParsedValue[])obj; 137 for(int v=0; 138 v<values.length && !needsFont; 139 v++) 140 { 141 if (values[v] == null) continue; 142 needsFont = containsFontRelativeSize(values[v], percentUnitsAreRelative); 143 } 144 145 } else if(obj instanceof ParsedValueImpl[][]) { 146 ParsedValueImpl[][] values = (ParsedValueImpl[][])obj; 147 for(int l=0; 148 l<values.length && !needsFont; 149 l++) 150 { 151 if (values[l] == null) continue; 152 for(int v=0; 153 v<values[l].length && !needsFont; 154 v++) 155 { 156 if (values[l][v] == null) continue; 157 needsFont = containsFontRelativeSize(values[l][v], percentUnitsAreRelative); 158 } 159 } 160 } 161 162 return needsFont; 163 } 164 165 /** 166 * Create an instance of ParsedValueImpl where the value type V is converted to 167 * the target type T using the given Type converter. If the value needs 168 * If type is null, then it is assumed that the value type V and the target 169 * type T are the same (do not need converted). If lookup is true, then 170 * the value is another property. 171 */ 172 public ParsedValueImpl(V value, StyleConverter<V, T> converter, boolean lookup) { 173 super(value, converter); 174 this.lookup = lookup; 175 this.containsLookups = lookup || getContainsLookupsFlag(value); 176 } 177 178 /** 179 * Create an instance of ParsedValueImpl where the value type V is converted to 180 * the target type T using the given Type converter. If the value needs 181 * If type is null, then it is assumed that the value type V and the target 182 * type T are the same (do not need converted). 183 */ 184 public ParsedValueImpl(V value, StyleConverter<V, T> type) { 185 this(value, type, false); 186 } 187 188 189 public T convert(Font font) { 190 return (T)((converter != null) ? converter.convert(this, font) : value); 191 } 192 193 private static int indent = 0; 194 195 private static String spaces() { 196 return new String(new char[indent]).replace('\0', ' '); 197 } 198 199 private static void indent() { 200 indent += 2; 201 } 202 203 private static void outdent() { 204 indent = Math.max(0, indent-2); 205 } 206 207 @Override public String toString() { 208 final String newline = System.lineSeparator(); 209 StringBuilder sbuf = new StringBuilder(); 210 sbuf.append(spaces()) 211 .append((lookup? "<Value lookup=\"true\">" : "<Value>")) 212 .append(newline); 213 indent(); 214 if (value != null) { 215 appendValue(sbuf, value, "value"); 216 } else { 217 appendValue(sbuf, "null", "value"); 218 } 219 sbuf.append(spaces()) 220 .append("<converter>") 221 .append(converter) 222 .append("</converter>") 223 .append(newline); 224 outdent(); 225 sbuf.append(spaces()).append("</Value>"); 226 return sbuf.toString(); 227 } 228 229 private void appendValue(StringBuilder sbuf, Object value, String tag) { 230 final String newline = System.lineSeparator(); 231 if (value instanceof ParsedValueImpl[][]) { 232 ParsedValueImpl[][] layers = (ParsedValueImpl[][])value; 233 sbuf.append(spaces()) 234 .append('<') 235 .append(tag) 236 .append(" layers=\"") 237 .append(layers.length) 238 .append("\">") 239 .append(newline); 240 indent(); 241 for (ParsedValueImpl[] layer : layers) { 242 sbuf.append(spaces()) 243 .append("<layer>") 244 .append(newline); 245 indent(); 246 if (layer == null) { 247 sbuf.append(spaces()).append("null").append(newline); 248 continue; 249 } 250 for(ParsedValueImpl val : layer) { 251 if (val == null) { 252 sbuf.append(spaces()).append("null").append(newline); 253 } else { 254 sbuf.append(val); 255 } 256 } 257 outdent(); 258 sbuf.append(spaces()) 259 .append("</layer>") 260 .append(newline); 261 } 262 outdent(); 263 sbuf.append(spaces()).append("</").append(tag).append('>').append(newline); 264 265 } else if (value instanceof ParsedValueImpl[]) { 266 ParsedValueImpl[] values = (ParsedValueImpl[])value; 267 sbuf.append(spaces()) 268 .append('<') 269 .append(tag) 270 .append(" values=\"") 271 .append(values.length) 272 .append("\">") 273 .append(newline); 274 indent(); 275 for(ParsedValueImpl val : values) { 276 if (val == null) { 277 sbuf.append(spaces()).append("null").append(newline); 278 } else { 279 sbuf.append(val); 280 } 281 } 282 outdent(); 283 sbuf.append(spaces()).append("</").append(tag).append('>').append(newline); 284 } else if (value instanceof ParsedValueImpl) { 285 sbuf.append(spaces()).append('<').append(tag).append('>').append(newline); 286 indent(); 287 sbuf.append(value); 288 outdent(); 289 sbuf.append(spaces()).append("</").append(tag).append('>').append(newline); 290 } else { 291 sbuf.append(spaces()).append('<').append(tag).append('>'); 292 sbuf.append(value); 293 sbuf.append("</").append(tag).append('>').append(newline); 294 } 295 } 296 297 @Override public boolean equals(Object obj) { 298 299 if (obj == this) return true; 300 301 if (obj == null || obj.getClass() != this.getClass()) { 302 return false; 303 } 304 305 final ParsedValueImpl other = (ParsedValueImpl)obj; 306 307 if (this.hash != other.hash) return false; 308 309 if (this.value instanceof ParsedValueImpl[][]) { 310 311 if (!(other.value instanceof ParsedValueImpl[][])) return false; 312 313 final ParsedValueImpl[][] thisValues = (ParsedValueImpl[][])this.value; 314 final ParsedValueImpl[][] otherValues = (ParsedValueImpl[][])other.value; 315 316 // this.value and other.value are known to be non-null 317 // due to instanceof 318 if (thisValues.length != otherValues.length) return false; 319 320 for (int i = 0; i < thisValues.length; i++) { 321 322 // if thisValues[i] is null, then otherValues[i] must be null 323 // if thisValues[i] is not null, then otherValues[i] must 324 // not be null 325 if ((thisValues[i] == null) && (otherValues[i] == null)) continue; 326 else if ((thisValues[i] == null) || (otherValues[i] == null)) return false; 327 328 if (thisValues[i].length != otherValues[i].length) return false; 329 330 for (int j = 0; j < thisValues[i].length; j++) { 331 332 final ParsedValueImpl thisValue = thisValues[i][j]; 333 final ParsedValueImpl otherValue = otherValues[i][j]; 334 335 if (thisValue != null 336 ? !thisValue.equals(otherValue) 337 : otherValue != null) 338 return false; 339 } 340 } 341 return true; 342 343 } else if (this.value instanceof ParsedValueImpl[]) { 344 345 if (!(other.value instanceof ParsedValueImpl[])) return false; 346 347 final ParsedValueImpl[] thisValues = (ParsedValueImpl[])this.value; 348 final ParsedValueImpl[] otherValues = (ParsedValueImpl[])other.value; 349 350 // this.value and other.value are known to be non-null 351 // due to instanceof 352 if (thisValues.length != otherValues.length) return false; 353 354 for (int i = 0; i < thisValues.length; i++) { 355 356 final ParsedValueImpl thisValue = thisValues[i]; 357 final ParsedValueImpl otherValue = otherValues[i]; 358 359 if ((thisValue != null) 360 ? !thisValue.equals(otherValue) 361 : otherValue != null) 362 return false; 363 } 364 return true; 365 366 } else { 367 368 // RT-24614 - "CENTER" should equal "center" 369 if (this.value instanceof String && other.value instanceof String) { 370 return this.value.toString().equalsIgnoreCase(other.value.toString()); 371 } 372 373 return (this.value != null 374 ? this.value.equals(other.value) 375 : other.value == null); 376 } 377 378 // Converter could be null, but the values could still match. 379 // It makes sense that ParsedValueImpl<String,String>("abc", null) should equal 380 // ParsedValueImpl<String,String>("abc", StringConverter.getInstance()) 381 // (converter == null ? other.converter == null : converter.equals(other.converter)); 382 383 } 384 385 private int hash = Integer.MIN_VALUE; 386 @Override public int hashCode() { 387 if (hash == Integer.MIN_VALUE) { 388 hash = 17; 389 if (value instanceof ParsedValueImpl[][]) { 390 ParsedValueImpl[][] values = (ParsedValueImpl[][])value; 391 for (int i = 0; i < values.length; i++) { 392 for (int j = 0; j < values[i].length; j++) { 393 final ParsedValueImpl val = values[i][j]; 394 hash = 37 * hash + ((val != null && val.value != null) ? val.value.hashCode() : 0); 395 } 396 } 397 } else if (value instanceof ParsedValueImpl[]) { 398 ParsedValueImpl[] values = (ParsedValueImpl[])value; 399 for (int i = 0; i < values.length; i++) { 400 if (values[i] == null || values[i].value == null) continue; 401 final ParsedValueImpl val = values[i]; 402 hash = 37 * hash + ((val != null && val.value != null) ? val.value.hashCode() : 0); 403 } 404 } else { 405 hash = 37 * hash + (value != null ? value.hashCode() : 0); 406 } 407 408 // Converter could be null, but the values could still match. 409 // It makes sense that ParsedValueImpl<String,String>("abc", null) should equal 410 // ParsedValueImpl<String,String>("abc", StringConverter.getInstance()) 411 // hash = 37 * hash + ((converter != null) ? converter.hashCode() : 1237); 412 } 413 return hash; 414 } 415 416 417 final static private byte NULL_VALUE = 0; 418 final static private byte VALUE = 1; 419 final static private byte VALUE_ARRAY = 2; 420 final static private byte ARRAY_OF_VALUE_ARRAY = 3; 421 final static private byte STRING = 4; 422 final static private byte COLOR = 5; 423 final static private byte ENUM = 6; 424 final static private byte BOOLEAN = 7; 425 final static private byte URL = 8; 426 final static private byte SIZE = 9; 427 428 429 public final void writeBinary(DataOutputStream os, StringStore stringStore) 430 throws IOException { 431 432 os.writeBoolean(lookup); 433 434 if (converter instanceof StyleConverterImpl) { 435 os.writeBoolean(true); 436 ((StyleConverterImpl)converter).writeBinary(os, stringStore); 437 } else { 438 os.writeBoolean(false); 439 if (converter != null) { 440 System.err.println("cannot writeBinary " + converter.getClass().getName()); 441 } 442 } 443 444 if (value instanceof ParsedValue) { 445 os.writeByte(VALUE); 446 final ParsedValue pv = (ParsedValue)value; 447 if (pv instanceof ParsedValueImpl) { 448 ((ParsedValueImpl)pv).writeBinary(os, stringStore); 449 } else { 450 final ParsedValueImpl impl = new ParsedValueImpl(pv.getValue(), pv.getConverter()); 451 impl.writeBinary(os, stringStore); 452 } 453 454 } else if (value instanceof ParsedValue[]) { 455 os.writeByte(VALUE_ARRAY); 456 final ParsedValue[] values = (ParsedValue[])value; 457 if (values != null) { 458 os.writeByte(VALUE); 459 } else { 460 os.writeByte(NULL_VALUE); 461 } 462 final int nValues = (values != null) ? values.length : 0; 463 os.writeInt(nValues); 464 for (int v=0; v<nValues; v++) { 465 if (values[v] != null) { 466 os.writeByte(VALUE); 467 final ParsedValue pv = values[v]; 468 if (pv instanceof ParsedValueImpl) { 469 ((ParsedValueImpl)pv).writeBinary(os, stringStore); 470 } else { 471 final ParsedValueImpl impl = new ParsedValueImpl(pv.getValue(), pv.getConverter()); 472 impl.writeBinary(os, stringStore); 473 } 474 } else { 475 os.writeByte(NULL_VALUE); 476 } 477 } 478 479 } else if (value instanceof ParsedValue[][]) { 480 os.writeByte(ARRAY_OF_VALUE_ARRAY); 481 final ParsedValue[][] layers = (ParsedValue[][])value; 482 if (layers != null) { 483 os.writeByte(VALUE); 484 } else { 485 os.writeByte(NULL_VALUE); 486 } 487 final int nLayers = (layers != null) ? layers.length : 0; 488 os.writeInt(nLayers); 489 for (int l=0; l<nLayers; l++) { 490 final ParsedValue[] values = layers[l]; 491 if (values != null) { 492 os.writeByte(VALUE); 493 } else { 494 os.writeByte(NULL_VALUE); 495 } 496 final int nValues = (values != null) ? values.length : 0; 497 os.writeInt(nValues); 498 for (int v=0; v<nValues; v++) { 499 if (values[v] != null) { 500 os.writeByte(VALUE); 501 final ParsedValue pv = values[v]; 502 if (pv instanceof ParsedValueImpl) { 503 ((ParsedValueImpl)pv).writeBinary(os, stringStore); 504 } else { 505 final ParsedValueImpl impl = new ParsedValueImpl(pv.getValue(), pv.getConverter()); 506 impl.writeBinary(os, stringStore); 507 } 508 } else { 509 os.writeByte(NULL_VALUE); 510 } 511 } 512 } 513 514 } else if (value instanceof Color) { 515 final Color c = (Color)value; 516 os.writeByte(COLOR); 517 os.writeLong(Double.doubleToLongBits(c.getRed())); 518 os.writeLong(Double.doubleToLongBits(c.getGreen())); 519 os.writeLong(Double.doubleToLongBits(c.getBlue())); 520 os.writeLong(Double.doubleToLongBits(c.getOpacity())); 521 522 } else if (value instanceof Enum) { 523 final Enum e = (Enum)value; 524 final int nameIndex = stringStore.addString(e.name()); 525 os.writeByte(ENUM); 526 os.writeShort(nameIndex); 527 528 } else if (value instanceof Boolean) { 529 final Boolean b = (Boolean)value; 530 os.writeByte(BOOLEAN); 531 os.writeBoolean(b); 532 533 } else if (value instanceof Size) { 534 final Size size = (Size)value; 535 os.writeByte(SIZE); 536 537 final double sz = size.getValue(); 538 final long val = Double.doubleToLongBits(sz); 539 os.writeLong(val); 540 541 final int index = stringStore.addString(size.getUnits().name()); 542 os.writeShort(index); 543 544 } else if (value instanceof String) { 545 os.writeByte(STRING); 546 final int index = stringStore.addString((String)value); 547 os.writeShort(index); 548 549 } else if (value instanceof URL) { 550 os.writeByte(URL); 551 final int index = stringStore.addString(value.toString()); 552 os.writeShort(index); 553 554 } else if (value == null) { 555 os.writeByte(NULL_VALUE); 556 557 } else { 558 throw new InternalError("cannot writeBinary " + this); 559 } 560 } 561 562 public static ParsedValueImpl readBinary(int bssVersion, DataInputStream is, String[] strings) 563 throws IOException { 564 565 final boolean lookup = is.readBoolean(); 566 final boolean hasType = is.readBoolean(); 567 568 final StyleConverter converter = (hasType) ? StyleConverterImpl.readBinary(is, strings) : null; 569 570 final int valType = is.readByte(); 571 572 if (valType == VALUE) { 573 final ParsedValueImpl value = ParsedValueImpl.readBinary(bssVersion, is, strings); 574 return new ParsedValueImpl(value, converter, lookup); 575 576 } else if (valType == VALUE_ARRAY) { 577 if (bssVersion >= 4) { 578 // This byte was used to denote whether or not array was all nulls. 579 // But really, just need to know nVals 580 is.readByte(); 581 } 582 final int nVals = is.readInt(); 583 final ParsedValueImpl[] values = (nVals > 0) 584 ? new ParsedValueImpl[nVals] 585 : null; 586 for (int v=0; v<nVals; v++) { 587 int vtype = is.readByte(); 588 if (vtype == VALUE) { 589 values[v] = ParsedValueImpl.readBinary(bssVersion, is, strings); 590 } else { 591 values[v] = null; 592 } 593 } 594 return new ParsedValueImpl(values, converter, lookup); 595 596 } else if (valType == ARRAY_OF_VALUE_ARRAY) { 597 if (bssVersion >= 4) { 598 // This byte was used to denote whether or not array was all nulls. 599 // But really, just need to know nLayers 600 is.readByte(); 601 } 602 603 final int nLayers = is.readInt(); 604 final ParsedValueImpl[][] layers = nLayers > 0 ? new ParsedValueImpl[nLayers][0] : null; 605 606 for (int l=0; l<nLayers; l++) { 607 if (bssVersion >= 4) { 608 // was used to denote whether or not array was all nulls 609 // but really just need to know nVals 610 is.readByte(); 611 } 612 final int nVals = is.readInt(); 613 614 layers[l] = nVals > 0 ? new ParsedValueImpl[nVals] : null; 615 616 for (int v=0; v<nVals; v++) { 617 int vtype = is.readByte(); 618 if (vtype == VALUE) { 619 layers[l][v] = ParsedValueImpl.readBinary(bssVersion, is, strings); 620 } else { 621 layers[l][v] = null; 622 } 623 } 624 625 } 626 627 return new ParsedValueImpl(layers, converter, lookup); 628 629 } else if (valType == COLOR) { 630 final double r = Double.longBitsToDouble(is.readLong()); 631 final double g = Double.longBitsToDouble(is.readLong()); 632 final double b = Double.longBitsToDouble(is.readLong()); 633 final double a = Double.longBitsToDouble(is.readLong()); 634 return new ParsedValueImpl<Color,Color>(Color.color(r, g, b, a), converter, lookup); 635 636 } else if (valType == ENUM) { 637 final int nameIndex = is.readShort(); 638 final String ename = strings[nameIndex]; 639 640 // Note: this block should be entered _only_ if version 2 641 if (bssVersion == 2) { 642 // RT-31022 643 // Once upon a time, the enum's class name was added to the 644 // StringStore and the class name's index was written to the 645 // stream. Then the writeShort of the class name's index was 646 // removed but the binary css version wasn't incremented. 647 // So if we're trying to read a version 2 stream, then we'll 648 // read this short value. If the stream is actually a the 649 // version without this short value, then the data will get 650 // out of sync with the deserialization code and an exception 651 // will be thrown, at which point we can try a different 652 // version. 653 // 654 int bad = is.readShort(); 655 if (bad >= strings.length) throw new IllegalArgumentException("bad version " + bssVersion); 656 } 657 658 ParsedValueImpl value = new ParsedValueImpl(ename, converter, lookup); 659 return value; 660 661 } else if (valType == BOOLEAN) { 662 Boolean b = is.readBoolean(); 663 return new ParsedValueImpl<Boolean,Boolean>(b, converter, lookup); 664 665 } else if (valType == SIZE) { 666 double val = Double.longBitsToDouble(is.readLong()); 667 SizeUnits units = SizeUnits.PX; 668 String unitStr = strings[is.readShort()]; 669 try { 670 units = Enum.valueOf(SizeUnits.class, unitStr); 671 } catch (IllegalArgumentException iae) { 672 System.err.println(iae.toString()); 673 } catch (NullPointerException npe) { 674 System.err.println(npe.toString()); 675 } 676 return new ParsedValueImpl<Size,Size>(new Size(val,units), converter, lookup); 677 678 } else if (valType == STRING) { 679 String str = strings[is.readShort()]; 680 return new ParsedValueImpl(str, converter, lookup); 681 682 } else if (valType == URL) { 683 String str = strings[is.readShort()]; 684 try { 685 URL url = new URL(str); 686 return new ParsedValueImpl(url, converter, lookup); 687 } catch (MalformedURLException malf) { 688 throw new InternalError("Excpeption in Value.readBinary: " + malf); 689 } 690 691 } else if (valType == NULL_VALUE) { 692 return new ParsedValueImpl(null, converter, lookup); 693 694 } else { 695 throw new InternalError("unknown type: " + valType); 696 } 697 } 698 }