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.Size; 30 import javafx.css.SizeUnits; 31 import javafx.css.StyleConverter; 32 import javafx.css.StyleConverter.StringStore; 33 import javafx.scene.paint.Color; 34 import javafx.scene.text.Font; 35 36 import java.io.DataInputStream; 37 import java.io.DataOutputStream; 38 import java.io.IOException; 39 import java.net.MalformedURLException; 40 import java.net.URL; 41 42 /** 43 * Implementation details behind a {@link ParsedValueImpl}. 44 */ 45 public class ParsedValueImpl<V, T> extends ParsedValue<V,T> { 46 47 /** 48 * If value references another property, then the real value needs to 49 * be looked up. 50 */ 51 final private boolean lookup; 52 @Override public final boolean isLookup() { return lookup; } 53 54 /** 55 * If value is itself a ParsedValueImpl or sequence of values, and should any of 56 * those values need to be looked up, then this flag is set. This 57 * does not mean that this particular value needs to be looked up, but 58 * that this value contains a value that needs to be looked up. 59 */ 60 final private boolean containsLookups; 61 @Override public final boolean isContainsLookups() { return containsLookups; } 62 63 private static boolean getContainsLookupsFlag(Object obj) { 64 65 // Assume the value does not contain lookups 66 boolean containsLookupsFlag = false; 67 68 if (obj instanceof Size) { 69 containsLookupsFlag = false; 70 } 71 72 else if(obj instanceof ParsedValueImpl) { 73 ParsedValueImpl value = (ParsedValueImpl)obj; 74 containsLookupsFlag = value.lookup || value.containsLookups; 75 } 76 77 else if(obj instanceof ParsedValueImpl[]) { 78 ParsedValueImpl[] values = (ParsedValueImpl[])obj; 79 for(int v=0; 80 // Bail if value contains lookups 81 // Continue iterating as long as one of the flags is false 82 v<values.length && !containsLookupsFlag; 83 v++) 84 { 85 if (values[v] != null) { 86 containsLookupsFlag = 87 containsLookupsFlag 88 || values[v].lookup 89 || values[v].containsLookups; 90 } 91 } 92 93 } else if(obj instanceof ParsedValueImpl[][]) { 94 ParsedValueImpl[][] values = (ParsedValueImpl[][])obj; 95 for(int l=0; 96 l<values.length && !containsLookupsFlag; 97 l++) 98 { 99 if (values[l] != null) { 100 for(int v=0; 101 v<values[l].length && !containsLookupsFlag; 102 v++) 103 { 104 if (values[l][v] != null) { 105 containsLookupsFlag = 106 containsLookupsFlag 107 || values[l][v].lookup 108 || values[l][v].containsLookups; 109 } 110 } 111 } 112 } 113 } 114 115 return containsLookupsFlag; 116 } 117 118 public static boolean containsFontRelativeSize(ParsedValue parsedValue, boolean percentUnitsAreRelative) { 119 120 // Assume the value does not need a font for conversion 121 boolean needsFont = false; 122 123 Object obj = parsedValue.getValue(); 124 125 if (obj instanceof Size) { 126 Size size = (Size)obj; 127 // percent is only relative for font and font-size properties 128 needsFont = size.getUnits() == SizeUnits.PERCENT 129 ? percentUnitsAreRelative 130 : size.isAbsolute() == false; 131 } 132 133 else if(obj instanceof ParsedValue) { 134 ParsedValue value = (ParsedValueImpl)obj; 135 needsFont = containsFontRelativeSize(value, percentUnitsAreRelative); 136 } 137 138 else if(obj instanceof ParsedValue[]) { 139 ParsedValue[] values = (ParsedValue[])obj; 140 for(int v=0; 141 v<values.length && !needsFont; 142 v++) 143 { 144 if (values[v] == null) continue; 145 needsFont = containsFontRelativeSize(values[v], percentUnitsAreRelative); 146 } 147 148 } else if(obj instanceof ParsedValueImpl[][]) { 149 ParsedValueImpl[][] values = (ParsedValueImpl[][])obj; 150 for(int l=0; 151 l<values.length && !needsFont; 152 l++) 153 { 154 if (values[l] == null) continue; 155 for(int v=0; 156 v<values[l].length && !needsFont; 157 v++) 158 { 159 if (values[l][v] == null) continue; 160 needsFont = containsFontRelativeSize(values[l][v], percentUnitsAreRelative); 161 } 162 } 163 } 164 165 return needsFont; 166 } 167 168 /** 169 * Create an instance of ParsedValueImpl where the value type V is converted to 170 * the target type T using the given Type converter. If the value needs 171 * If type is null, then it is assumed that the value type V and the target 172 * type T are the same (do not need converted). If lookup is true, then 173 * the value is another property. 174 */ 175 public ParsedValueImpl(V value, StyleConverter<V, T> converter, boolean lookup) { 176 super(value, converter); 177 this.lookup = lookup; 178 this.containsLookups = lookup || getContainsLookupsFlag(value); 179 } 180 181 /** 182 * Create an instance of ParsedValueImpl where the value type V is converted to 183 * the target type T using the given Type converter. If the value needs 184 * If type is null, then it is assumed that the value type V and the target 185 * type T are the same (do not need converted). 186 */ 187 public ParsedValueImpl(V value, StyleConverter<V, T> type) { 188 this(value, type, false); 189 } 190 191 192 public T convert(Font font) { 193 return (T)((converter != null) ? converter.convert(this, font) : value); 194 } 195 196 private static int indent = 0; 197 198 private static String spaces() { 199 return new String(new char[indent]).replace('\0', ' '); 200 } 201 202 private static void indent() { 203 indent += 2; 204 } 205 206 private static void outdent() { 207 indent = Math.max(0, indent-2); 208 } 209 210 @Override public String toString() { 211 final String newline = System.lineSeparator(); 212 StringBuilder sbuf = new StringBuilder(); 213 sbuf.append(spaces()) 214 .append((lookup? "<Value lookup=\"true\">" : "<Value>")) 215 .append(newline); 216 indent(); 217 if (value != null) { 218 appendValue(sbuf, value, "value"); 219 } else { 220 appendValue(sbuf, "null", "value"); 221 } 222 sbuf.append(spaces()) 223 .append("<converter>") 224 .append(converter) 225 .append("</converter>") 226 .append(newline); 227 outdent(); 228 sbuf.append(spaces()).append("</Value>"); 229 return sbuf.toString(); 230 } 231 232 private void appendValue(StringBuilder sbuf, Object value, String tag) { 233 final String newline = System.lineSeparator(); 234 if (value instanceof ParsedValueImpl[][]) { 235 ParsedValueImpl[][] layers = (ParsedValueImpl[][])value; 236 sbuf.append(spaces()) 237 .append('<') 238 .append(tag) 239 .append(" layers=\"") 240 .append(layers.length) 241 .append("\">") 242 .append(newline); 243 indent(); 244 for (ParsedValueImpl[] layer : layers) { 245 sbuf.append(spaces()) 246 .append("<layer>") 247 .append(newline); 248 indent(); 249 if (layer == null) { 250 sbuf.append(spaces()).append("null").append(newline); 251 continue; 252 } 253 for(ParsedValueImpl val : layer) { 254 if (val == null) { 255 sbuf.append(spaces()).append("null").append(newline); 256 } else { 257 sbuf.append(val); 258 } 259 } 260 outdent(); 261 sbuf.append(spaces()) 262 .append("</layer>") 263 .append(newline); 264 } 265 outdent(); 266 sbuf.append(spaces()).append("</").append(tag).append('>').append(newline); 267 268 } else if (value instanceof ParsedValueImpl[]) { 269 ParsedValueImpl[] values = (ParsedValueImpl[])value; 270 sbuf.append(spaces()) 271 .append('<') 272 .append(tag) 273 .append(" values=\"") 274 .append(values.length) 275 .append("\">") 276 .append(newline); 277 indent(); 278 for(ParsedValueImpl val : values) { 279 if (val == null) { 280 sbuf.append(spaces()).append("null").append(newline); 281 } else { 282 sbuf.append(val); 283 } 284 } 285 outdent(); 286 sbuf.append(spaces()).append("</").append(tag).append('>').append(newline); 287 } else if (value instanceof ParsedValueImpl) { 288 sbuf.append(spaces()).append('<').append(tag).append('>').append(newline); 289 indent(); 290 sbuf.append(value); 291 outdent(); 292 sbuf.append(spaces()).append("</").append(tag).append('>').append(newline); 293 } else { 294 sbuf.append(spaces()).append('<').append(tag).append('>'); 295 sbuf.append(value); 296 sbuf.append("</").append(tag).append('>').append(newline); 297 } 298 } 299 300 @Override public boolean equals(Object obj) { 301 302 if (obj == this) return true; 303 304 if (obj == null || obj.getClass() != this.getClass()) { 305 return false; 306 } 307 308 final ParsedValueImpl other = (ParsedValueImpl)obj; 309 310 if (this.hash != other.hash) return false; 311 312 if (this.value instanceof ParsedValueImpl[][]) { 313 314 if (!(other.value instanceof ParsedValueImpl[][])) return false; 315 316 final ParsedValueImpl[][] thisValues = (ParsedValueImpl[][])this.value; 317 final ParsedValueImpl[][] otherValues = (ParsedValueImpl[][])other.value; 318 319 // this.value and other.value are known to be non-null 320 // due to instanceof 321 if (thisValues.length != otherValues.length) return false; 322 323 for (int i = 0; i < thisValues.length; i++) { 324 325 // if thisValues[i] is null, then otherValues[i] must be null 326 // if thisValues[i] is not null, then otherValues[i] must 327 // not be null 328 if ((thisValues[i] == null) && (otherValues[i] == null)) continue; 329 else if ((thisValues[i] == null) || (otherValues[i] == null)) return false; 330 331 if (thisValues[i].length != otherValues[i].length) return false; 332 333 for (int j = 0; j < thisValues[i].length; j++) { 334 335 final ParsedValueImpl thisValue = thisValues[i][j]; 336 final ParsedValueImpl otherValue = otherValues[i][j]; 337 338 if (thisValue != null 339 ? !thisValue.equals(otherValue) 340 : otherValue != null) 341 return false; 342 } 343 } 344 return true; 345 346 } else if (this.value instanceof ParsedValueImpl[]) { 347 348 if (!(other.value instanceof ParsedValueImpl[])) return false; 349 350 final ParsedValueImpl[] thisValues = (ParsedValueImpl[])this.value; 351 final ParsedValueImpl[] otherValues = (ParsedValueImpl[])other.value; 352 353 // this.value and other.value are known to be non-null 354 // due to instanceof 355 if (thisValues.length != otherValues.length) return false; 356 357 for (int i = 0; i < thisValues.length; i++) { 358 359 final ParsedValueImpl thisValue = thisValues[i]; 360 final ParsedValueImpl otherValue = otherValues[i]; 361 362 if ((thisValue != null) 363 ? !thisValue.equals(otherValue) 364 : otherValue != null) 365 return false; 366 } 367 return true; 368 369 } else { 370 371 // RT-24614 - "CENTER" should equal "center" 372 if (this.value instanceof String && other.value instanceof String) { 373 return this.value.toString().equalsIgnoreCase(other.value.toString()); 374 } 375 376 return (this.value != null 377 ? this.value.equals(other.value) 378 : other.value == null); 379 } 380 381 // Converter could be null, but the values could still match. 382 // It makes sense that ParsedValueImpl<String,String>("abc", null) should equal 383 // ParsedValueImpl<String,String>("abc", StringConverter.getInstance()) 384 // (converter == null ? other.converter == null : converter.equals(other.converter)); 385 386 } 387 388 private int hash = Integer.MIN_VALUE; 389 @Override public int hashCode() { 390 if (hash == Integer.MIN_VALUE) { 391 hash = 17; 392 if (value instanceof ParsedValueImpl[][]) { 393 ParsedValueImpl[][] values = (ParsedValueImpl[][])value; 394 for (int i = 0; i < values.length; i++) { 395 for (int j = 0; j < values[i].length; j++) { 396 final ParsedValueImpl val = values[i][j]; 397 hash = 37 * hash + ((val != null && val.value != null) ? val.value.hashCode() : 0); 398 } 399 } 400 } else if (value instanceof ParsedValueImpl[]) { 401 ParsedValueImpl[] values = (ParsedValueImpl[])value; 402 for (int i = 0; i < values.length; i++) { 403 if (values[i] == null || values[i].value == null) continue; 404 final ParsedValueImpl val = values[i]; 405 hash = 37 * hash + ((val != null && val.value != null) ? val.value.hashCode() : 0); 406 } 407 } else { 408 hash = 37 * hash + (value != null ? value.hashCode() : 0); 409 } 410 411 // Converter could be null, but the values could still match. 412 // It makes sense that ParsedValueImpl<String,String>("abc", null) should equal 413 // ParsedValueImpl<String,String>("abc", StringConverter.getInstance()) 414 // hash = 37 * hash + ((converter != null) ? converter.hashCode() : 1237); 415 } 416 return hash; 417 } 418 419 420 final static private byte NULL_VALUE = 0; 421 final static private byte VALUE = 1; 422 final static private byte VALUE_ARRAY = 2; 423 final static private byte ARRAY_OF_VALUE_ARRAY = 3; 424 final static private byte STRING = 4; 425 final static private byte COLOR = 5; 426 final static private byte ENUM = 6; 427 final static private byte BOOLEAN = 7; 428 final static private byte URL = 8; 429 final static private byte SIZE = 9; 430 431 432 public final void writeBinary(DataOutputStream os, StringStore stringStore) 433 throws IOException { 434 435 os.writeBoolean(lookup); 436 437 if (converter != null) { 438 os.writeBoolean(true); 439 converter.writeBinary(os, stringStore); 440 } else { 441 os.writeBoolean(false); 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) ? StyleConverter.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("Exception 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 }