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 }