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 }