1 /*
   2  * Copyright (c) 2000, 2009, 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 sun.util.calendar;
  27 
  28 import  java.io.File;
  29 import  java.io.FileInputStream;
  30 import  java.io.FileNotFoundException;
  31 import  java.io.IOException;
  32 import  java.lang.ref.SoftReference;
  33 import  java.nio.file.FileSystems;
  34 import  java.security.AccessController;
  35 import  java.security.PrivilegedAction;
  36 import  java.security.PrivilegedActionException;
  37 import  java.security.PrivilegedExceptionAction;
  38 import  java.util.ArrayList;
  39 import  java.util.HashMap;
  40 import  java.util.List;
  41 import  java.util.Map;
  42 
  43 /**
  44  * <code>ZoneInfoFile</code> reads Zone information files in the
  45  * &lt;java.home&gt;/lib/zi directory and provides time zone
  46  * information in the form of a {@link ZoneInfo} object. Also, it
  47  * reads the ZoneInfoMappings file to obtain time zone IDs information
  48  * that is used by the {@link ZoneInfo} class. The directory layout
  49  * and data file formats are as follows.
  50  *
  51  * <p><strong>Directory layout</strong><p>
  52  *
  53  * All zone data files and ZoneInfoMappings are put under the
  54  * &lt;java.home&gt;/lib/zi directory. A path name for a given time
  55  * zone ID is a concatenation of &lt;java.home&gt;/lib/zi/ and the
  56  * time zone ID. (The file separator is replaced with the platform
  57  * dependent value. e.g., '\' for Win32.) An example layout will look
  58  * like as follows.
  59  * <blockquote>
  60  * <pre>
  61  * &lt;java.home&gt;/lib/zi/Africa/Addis_Ababa
  62  *                   /Africa/Dakar
  63  *                   /America/Los_Angeles
  64  *                   /Asia/Singapore
  65  *                   /EET
  66  *                   /Europe/Oslo
  67  *                   /GMT
  68  *                   /Pacific/Galapagos
  69  *                       ...
  70  *                   /ZoneInfoMappings
  71  * </pre>
  72  * </blockquote>
  73  *
  74  * A zone data file has specific information of each zone.
  75  * <code>ZoneInfoMappings</code> has global information of zone IDs so
  76  * that the information can be obtained without instantiating all time
  77  * zones.
  78  *
  79  * <p><strong>File format</strong><p>
  80  *
  81  * Two binary-file formats based on a simple Tag-Length-Value format are used
  82  * to describe TimeZone information. The generic format of a data file is:
  83  * <blockquote>
  84  * <pre>
  85  *    DataFile {
  86  *      u1              magic[7];
  87  *      u1              version;
  88  *      data_item       data[];
  89  *    }
  90  * </pre>
  91  * </blockquote>
  92  * where <code>magic</code> is a magic number identifying a file
  93  * format, <code>version</code> is the format version number, and
  94  * <code>data</code> is one or more <code>data_item</code>s. The
  95  * <code>data_item</code> structure is:
  96  * <blockquote>
  97  * <pre>
  98  *    data_item {
  99  *      u1              tag;
 100  *      u2              length;
 101  *      u1              value[length];
 102  *    }
 103  * </pre>
 104  * </blockquote>
 105  * where <code>tag</code> indicates the data type of the item,
 106  * <code>length</code> is a byte count of the following
 107  * <code>value</code> that is the content of item data.
 108  * <p>
 109  * All data is stored in the big-endian order. There is no boundary
 110  * alignment between date items.
 111  *
 112  * <p><strong>1. ZoneInfo data file</strong><p>
 113  *
 114  * Each ZoneInfo data file consists of the following members.
 115  * <br>
 116  * <blockquote>
 117  * <pre>
 118  *    ZoneInfoDataFile {
 119  *      u1              magic[7];
 120  *      u1              version;
 121  *      SET OF<sup>1</sup> {
 122  *        transition            transitions<sup>2</sup>;
 123  *        offset_table          offsets<sup>2</sup>;
 124  *        simpletimezone        stzparams<sup>2</sup>;
 125  *        raw_offset            rawoffset;
 126  *        dstsaving             dst;
 127  *        checksum              crc32;
 128  *        gmtoffsetwillchange   gmtflag<sup>2</sup>;
 129  *      }
 130  *   }
 131  *   1: an unordered collection of zero or one occurrences of each item
 132  *   2: optional item
 133  * </pre>
 134  * </blockquote>
 135  * <code>magic</code> is a byte-string constant identifying the
 136  * ZoneInfo data file.  This field must be <code>"javazi&#92;0"</code>
 137  * defined as {@link #JAVAZI_LABEL}.
 138  * <p>
 139  * <code>version</code> is the version number of the file format. This
 140  * will be used for compatibility check. This field must be
 141  * <code>0x01</code> in this version.
 142  * <p>
 143  * <code>transition</code>, <code>offset_table</code> and
 144  * <code>simpletimezone</code> have information of time transition
 145  * from the past to the future.  Therefore, these structures don't
 146  * exist if the zone didn't change zone names and haven't applied DST in
 147  * the past, and haven't planned to apply it.  (e.g. Asia/Tokyo zone)
 148  * <p>
 149  * <code>raw_offset</code>, <code>dstsaving</code> and <code>checksum</code>
 150  * exist in every zoneinfo file. They are used by TimeZone.class indirectly.
 151  *
 152  * <p><strong>1.1 <code>transition</code> structure</strong><p><a name="transition"></a>
 153  * <blockquote>
 154  * <pre>
 155  *    transition {
 156  *      u1      tag;              // 0x04 : constant
 157  *      u2      length;           // byte length of whole values
 158  *      s8      value[length/8];  // transitions in `long'
 159  *    }
 160  * </pre>
 161  * </blockquote>
 162  * See {@link ZoneInfo#transitions ZoneInfo.transitions} about the value.
 163  *
 164  * <p><strong>1.2 <code>offset_table</code> structure</strong><p>
 165  * <blockquote>
 166  * <pre>
 167  *    offset_table {
 168  *      u1      tag;              // 0x05 : constant
 169  *      u2      length;           // byte length of whole values
 170  *      s4      value[length/4];  // offset values in `int'
 171  *    }
 172  * </pre>
 173  * </blockquote>
 174  *
 175  * <p><strong>1.3 <code>simpletimezone</code> structure</strong><p>
 176  * See {@link ZoneInfo#simpleTimeZoneParams ZoneInfo.simpleTimeZoneParams}
 177  * about the value.
 178  * <blockquote>
 179  * <pre>
 180  *    simpletimezone {
 181  *      u1      tag;              // 0x06 : constant
 182  *      u2      length;           // byte length of whole values
 183  *      s4      value[length/4];  // SimpleTimeZone parameters
 184  *    }
 185  * </pre>
 186  * </blockquote>
 187  * See {@link ZoneInfo#offsets ZoneInfo.offsets} about the value.
 188  *
 189  * <p><strong>1.4 <code>raw_offset</code> structure</strong><p>
 190  * <blockquote>
 191  * <pre>
 192  *    raw_offset {
 193  *      u1      tag;              // 0x01 : constant
 194  *      u2      length;           // must be 4.
 195  *      s4      value;            // raw GMT offset [millisecond]
 196  *    }
 197  * </pre>
 198  * </blockquote>
 199  * See {@link ZoneInfo#rawOffset ZoneInfo.rawOffset} about the value.
 200  *
 201  * <p><strong>1.5 <code>dstsaving</code> structure</strong><p>
 202  * Value has dstSaving in seconds.
 203  * <blockquote>
 204  * <pre>
 205  *    dstsaving {
 206  *      u1      tag;              // 0x02 : constant
 207  *      u2      length;           // must be 2.
 208  *      s2      value;            // DST save value [second]
 209  *    }
 210  * </pre>
 211  * </blockquote>
 212  * See {@link ZoneInfo#dstSavings ZoneInfo.dstSavings} about value.
 213  *
 214  * <p><strong>1.6 <code>checksum</code> structure</strong><p>
 215  * <blockquote>
 216  * <pre>
 217  *    checksum {
 218  *      u1      tag;              // 0x03 : constant
 219  *      u2      length;           // must be 4.
 220  *      s4      value;            // CRC32 value of transitions
 221  *    }
 222  * </pre>
 223  * </blockquote>
 224  * See {@link ZoneInfo#checksum ZoneInfo.checksum}.
 225  *
 226  * <p><strong>1.7 <code>gmtoffsetwillchange</code> structure</strong><p>
 227  * This record has a flag value for {@link ZoneInfo#rawOffsetWillChange}.
 228  * If this record is not present in a zoneinfo file, 0 is assumed for
 229  * the value.
 230  * <blockquote>
 231  * <pre>
 232  *    gmtoffsetwillchange {
 233  *      u1      tag;             // 0x07 : constant
 234  *      u2      length;          // must be 1.
 235  *      u1      value;           // 1: if the GMT raw offset will change
 236  *                               // in the future, 0, otherwise.
 237  *     }
 238  * </pre>
 239  * </blockquote>
 240  *
 241  *
 242  * <p><strong>2. ZoneInfoMappings file</strong><p>
 243  *
 244  * The ZoneInfoMappings file consists of the following members.
 245  * <br>
 246  * <blockquote>
 247  * <pre>
 248  *    ZoneInfoMappings {
 249  *      u1      magic[7];
 250  *      u1      version;
 251  *      SET OF {
 252  *        versionName                   version;
 253  *        zone_id_table                 zoneIDs;
 254  *        raw_offset_table              rawoffsets;
 255  *        raw_offset_index_table        rawoffsetindices;
 256  *        alias_table                   aliases;
 257  *        excluded_list                 excludedList;
 258  *      }
 259  *   }
 260  * </pre>
 261  * </blockquote>
 262  *
 263  * <code>magic</code> is a byte-string constant which has the file type.
 264  * This field must be <code>"javazm&#92;0"</code> defined as {@link #JAVAZM_LABEL}.
 265  * <p>
 266  * <code>version</code> is the version number of this file
 267  * format. This will be used for compatibility check. This field must
 268  * be <code>0x01</code> in this version.
 269  * <p>
 270  * <code>versionName</code> shows which version of Olson's data has been used
 271  * to generate this ZoneInfoMappings. (e.g. <code>tzdata2000g</code>) <br>
 272  * This field is for trouble-shooting and isn't usually used in runtime.
 273  * <p>
 274  * <code>zone_id_table</code>, <code>raw_offset_index_table</code> and
 275  * <code>alias_table</code> are general information of supported
 276  * zones.
 277  *
 278  * <p><strong>2.1 <code>zone_id_table</code> structure</strong><p>
 279  * The list of zone IDs included in the zi database. The list does
 280  * <em>not</em> include zone IDs, if any, listed in excludedList.
 281  * <br>
 282  * <blockquote>
 283  * <pre>
 284  *    zone_id_table {
 285  *      u1      tag;              // 0x40 : constant
 286  *      u2      length;           // byte length of whole values
 287  *      u2      zone_id_count;
 288  *      zone_id value[zone_id_count];
 289  *    }
 290  *
 291  *    zone_id {
 292  *      u1      byte_length;      // byte length of id
 293  *      u1      id[byte_length];  // zone name string
 294  *    }
 295  * </pre>
 296  * </blockquote>
 297  *
 298  * <p><strong>2.2 <code>raw_offset_table</code> structure</strong><p>
 299  * <br>
 300  * <blockquote>
 301  * <pre>
 302  *    raw_offset_table {
 303  *      u1      tag;              // 0x41 : constant
 304  *      u2      length;           // byte length of whole values
 305  *      s4      value[length/4];  // raw GMT offset in milliseconds
 306  *   }
 307  * </pre>
 308  * </blockquote>
 309  *
 310  * <p><strong>2.3 <code>raw_offset_index_table</code> structure</strong><p>
 311  * <br>
 312  * <blockquote>
 313  * <pre>
 314  *    raw_offset_index_table {
 315  *      u1      tag;              // 0x42 : constant
 316  *      u2      length;           // byte length of whole values
 317  *      u1      value[length];
 318  *    }
 319  * </pre>
 320  * </blockquote>
 321  *
 322  * <p><strong>2.4 <code>alias_table</code> structure</strong><p>
 323  * <br>
 324  * <blockquote>
 325  * <pre>
 326  *   alias_table {
 327  *      u1      tag;              // 0x43 : constant
 328  *      u2      length;           // byte length of whole values
 329  *      u2      nentries;         // number of id-pairs
 330  *      id_pair value[nentries];
 331  *   }
 332  *
 333  *   id_pair {
 334  *      zone_id aliasname;
 335  *      zone_id ID;
 336  *   }
 337  * </pre>
 338  * </blockquote>
 339  *
 340  * <p><strong>2.5 <code>versionName</code> structure</strong><p>
 341  * <br>
 342  * <blockquote>
 343  * <pre>
 344  *   versionName {
 345  *      u1      tag;              // 0x44 : constant
 346  *      u2      length;           // byte length of whole values
 347  *      u1      value[length];
 348  *   }
 349  * </pre>
 350  * </blockquote>
 351  *
 352  * <p><strong>2.6 <code>excludeList</code> structure</strong><p>
 353  * The list of zone IDs whose zones will change their GMT offsets
 354  * (a.k.a. raw offsets) some time in the future. Those IDs must be
 355  * added to the list of zone IDs for getAvailableIDs(). Also they must
 356  * be examined for getAvailableIDs(int) to determine the
 357  * <em>current</em> GMT offsets.
 358  * <br>
 359  * <blockquote>
 360  * <pre>
 361  *   excluded_list {
 362  *      u1      tag;              // 0x45 : constant
 363  *      u2      length;           // byte length of whole values
 364  *      u2      nentries;         // number of zone_ids
 365  *      zone_id value[nentries];  // excluded zone IDs
 366  *   }
 367  * </pre>
 368  * </blockquote>
 369  *
 370  * @since 1.4
 371  */
 372 
 373 public class ZoneInfoFile {
 374 
 375     /**
 376      * The magic number for the ZoneInfo data file format.
 377      */
 378     public static final byte[]  JAVAZI_LABEL = {
 379         (byte)'j', (byte)'a', (byte)'v', (byte)'a', (byte)'z', (byte)'i', (byte)'\0'
 380     };
 381     private static final int    JAVAZI_LABEL_LENGTH = JAVAZI_LABEL.length;
 382 
 383     /**
 384      * The ZoneInfo data file format version number. Must increase
 385      * one when any incompatible change has been made.
 386      */
 387     public static final byte    JAVAZI_VERSION = 0x01;
 388 
 389     /**
 390      * Raw offset data item tag.
 391      */
 392     public static final byte    TAG_RawOffset = 1;
 393 
 394     /**
 395      * Known last Daylight Saving Time save value data item tag.
 396      */
 397     public static final byte    TAG_LastDSTSaving = 2;
 398 
 399     /**
 400      * Checksum data item tag.
 401      */
 402     public static final byte    TAG_CRC32 = 3;
 403 
 404     /**
 405      * Transition data item tag.
 406      */
 407     public static final byte    TAG_Transition = 4;
 408 
 409     /**
 410      * Offset table data item tag.
 411      */
 412     public static final byte    TAG_Offset = 5;
 413 
 414     /**
 415      * SimpleTimeZone parameters data item tag.
 416      */
 417     public static final byte    TAG_SimpleTimeZone = 6;
 418 
 419     /**
 420      * Raw GMT offset will change in the future.
 421      */
 422     public static final byte    TAG_GMTOffsetWillChange = 7;
 423 
 424 
 425     /**
 426      * The ZoneInfoMappings file name.
 427      */
 428     public static final String  JAVAZM_FILE_NAME = "ZoneInfoMappings";
 429 
 430     /**
 431      * The magic number for the ZoneInfoMappings file format.
 432      */
 433     public static final byte[]  JAVAZM_LABEL = {
 434         (byte)'j', (byte)'a', (byte)'v', (byte)'a', (byte)'z', (byte)'m', (byte)'\0'
 435     };
 436     private static final int    JAVAZM_LABEL_LENGTH = JAVAZM_LABEL.length;
 437 
 438     /**
 439      * The ZoneInfoMappings file format version number. Must increase
 440      * one when any incompatible change has been made.
 441      */
 442     public static final byte    JAVAZM_VERSION = 0x01;
 443 
 444     /**
 445      * Time zone IDs data item tag.
 446      */
 447     public static final byte    TAG_ZoneIDs = 64;
 448 
 449     /**
 450      * Raw GMT offsets table data item tag.
 451      */
 452     public static final byte    TAG_RawOffsets = 65;
 453 
 454     /**
 455      * Indices to the raw GMT offset table data item tag.
 456      */
 457     public static final byte    TAG_RawOffsetIndices = 66;
 458 
 459     /**
 460      * Time zone aliases table data item tag.
 461      */
 462     public static final byte    TAG_ZoneAliases = 67;
 463 
 464     /**
 465      * Olson's public zone information version tag.
 466      */
 467     public static final byte    TAG_TZDataVersion = 68;
 468 
 469     /**
 470      * Excluded zones item tag. (Added in Mustang)
 471      */
 472     public static final byte    TAG_ExcludedZones = 69;
 473 
 474     private static Map<String, ZoneInfo> zoneInfoObjects = null;
 475 
 476     private static final String ziDir = AccessController.doPrivileged(
 477         new PrivilegedAction<String>() {
 478             public String run() {
 479                 String zi = System.getProperty("java.home") +
 480                     File.separator + "lib" + File.separator + "zi";
 481                 try {
 482                     zi = FileSystems.getDefault().getPath(zi).toRealPath(true).toString();
 483                 } catch(Exception e) {
 484                 }
 485                 return zi;
 486             }
 487         });
 488 
 489     /**
 490      * Converts the given time zone ID to a platform dependent path
 491      * name. For example, "America/Los_Angeles" is converted to
 492      * "America\Los_Angeles" on Win32.
 493      * @return a modified ID replacing '/' with {@link
 494      * java.io.File#separatorChar File.separatorChar} if needed.
 495      */
 496     public static String getFileName(String ID) {
 497         if (File.separatorChar == '/') {
 498             return ID;
 499         }
 500         return ID.replace('/', File.separatorChar);
 501     }
 502 
 503     /**
 504      * Gets a ZoneInfo with the given GMT offset. The object
 505      * has its ID in the format of GMT{+|-}hh:mm.
 506      *
 507      * @param originalId the given custom id (before normalized such as "GMT+9")
 508      * @param gmtOffset GMT offset <em>in milliseconds</em>
 509      * @return a ZoneInfo constructed with the given GMT offset
 510      */
 511     public static ZoneInfo getCustomTimeZone(String originalId, int gmtOffset) {
 512         String id = toCustomID(gmtOffset);
 513 
 514         ZoneInfo zi = getFromCache(id);
 515         if (zi == null) {
 516             zi = new ZoneInfo(id, gmtOffset);
 517             zi = addToCache(id, zi);
 518             if (!id.equals(originalId)) {
 519                 zi = addToCache(originalId, zi);
 520             }
 521         }
 522         return (ZoneInfo) zi.clone();
 523     }
 524 
 525     public static String toCustomID(int gmtOffset) {
 526         char sign;
 527         int offset = gmtOffset / 60000;
 528 
 529         if (offset >= 0) {
 530             sign = '+';
 531         } else {
 532             sign = '-';
 533             offset = -offset;
 534         }
 535         int hh = offset / 60;
 536         int mm = offset % 60;
 537 
 538         char[] buf = new char[] { 'G', 'M', 'T', sign, '0', '0', ':', '0', '0' };
 539         if (hh >= 10) {
 540             buf[4] += hh / 10;
 541         }
 542         buf[5] += hh % 10;
 543         if (mm != 0) {
 544             buf[7] += mm / 10;
 545             buf[8] += mm % 10;
 546         }
 547         return new String(buf);
 548     }
 549 
 550     /**
 551      * @return a ZoneInfo instance created for the specified id, or
 552      * null if there is no time zone data file found for the specified
 553      * id.
 554      */
 555     public static ZoneInfo getZoneInfo(String id) {
 556         ZoneInfo zi = getFromCache(id);
 557         if (zi == null) {
 558             zi = createZoneInfo(id);
 559             if (zi == null) {
 560                 return null;
 561             }
 562             zi = addToCache(id, zi);
 563         }
 564         return (ZoneInfo) zi.clone();
 565     }
 566 
 567     synchronized static ZoneInfo getFromCache(String id) {
 568         if (zoneInfoObjects == null) {
 569             return null;
 570         }
 571         return zoneInfoObjects.get(id);
 572     }
 573 
 574     synchronized static ZoneInfo addToCache(String id, ZoneInfo zi) {
 575         if (zoneInfoObjects == null) {
 576             zoneInfoObjects = new HashMap<String, ZoneInfo>();
 577         } else {
 578             ZoneInfo zone = zoneInfoObjects.get(id);
 579             if (zone != null) {
 580                 return zone;
 581             }
 582         }
 583         zoneInfoObjects.put(id, zi);
 584         return zi;
 585     }
 586 
 587     private static ZoneInfo createZoneInfo(String id) {
 588         byte[] buf = readZoneInfoFile(getFileName(id));
 589         if (buf == null) {
 590             return null;
 591         }
 592 
 593         int index = 0;
 594         int filesize = buf.length;
 595         int rawOffset = 0;
 596         int dstSavings = 0;
 597         int checksum = 0;
 598         boolean willGMTOffsetChange = false;
 599         long[] transitions = null;
 600         int[] offsets = null;
 601         int[] simpleTimeZoneParams = null;
 602 
 603         try {
 604             for (index = 0; index < JAVAZI_LABEL.length; index++) {
 605                 if (buf[index] != JAVAZI_LABEL[index]) {
 606                     System.err.println("ZoneInfo: wrong magic number: " + id);
 607                     return null;
 608                 }
 609             }
 610             if (buf[index++] > JAVAZI_VERSION) {
 611                 System.err.println("ZoneInfo: incompatible version ("
 612                                    + buf[index - 1] + "): " + id);
 613                 return null;
 614             }
 615 
 616             while (index < filesize) {
 617                 byte tag = buf[index++];
 618                 int  len = ((buf[index++] & 0xFF) << 8) + (buf[index++] & 0xFF);
 619 
 620                 if (filesize < index+len) {
 621                     break;
 622                 }
 623 
 624                 switch (tag) {
 625                 case TAG_CRC32:
 626                     {
 627                         int val = buf[index++] & 0xff;
 628                         val = (val << 8) + (buf[index++] & 0xff);
 629                         val = (val << 8) + (buf[index++] & 0xff);
 630                         val = (val << 8) + (buf[index++] & 0xff);
 631                         checksum = val;
 632                     }
 633                     break;
 634 
 635                 case TAG_LastDSTSaving:
 636                     {
 637                         short val = (short)(buf[index++] & 0xff);
 638                         val = (short)((val << 8) + (buf[index++] & 0xff));
 639                         dstSavings = val * 1000;
 640                     }
 641                     break;
 642 
 643                 case TAG_RawOffset:
 644                     {
 645                         int val = buf[index++] & 0xff;
 646                         val = (val << 8) + (buf[index++] & 0xff);
 647                         val = (val << 8) + (buf[index++] & 0xff);
 648                         val = (val << 8) + (buf[index++] & 0xff);
 649                         rawOffset = val;
 650                     }
 651                     break;
 652 
 653                 case TAG_Transition:
 654                     {
 655                         int n = len / 8;
 656                         transitions = new long[n];
 657                         for (int i = 0; i < n; i ++) {
 658                             long val = buf[index++] & 0xff;
 659                             val = (val << 8) + (buf[index++] & 0xff);
 660                             val = (val << 8) + (buf[index++] & 0xff);
 661                             val = (val << 8) + (buf[index++] & 0xff);
 662                             val = (val << 8) + (buf[index++] & 0xff);
 663                             val = (val << 8) + (buf[index++] & 0xff);
 664                             val = (val << 8) + (buf[index++] & 0xff);
 665                             val = (val << 8) + (buf[index++] & 0xff);
 666                             transitions[i] = val;
 667                         }
 668                     }
 669                     break;
 670 
 671                 case TAG_Offset:
 672                     {
 673                         int n = len / 4;
 674                         offsets = new int[n];
 675                         for (int i = 0; i < n; i ++) {
 676                             int val = buf[index++] & 0xff;
 677                             val = (val << 8) + (buf[index++] & 0xff);
 678                             val = (val << 8) + (buf[index++] & 0xff);
 679                             val = (val << 8) + (buf[index++] & 0xff);
 680                             offsets[i] = val;
 681                         }
 682                     }
 683                     break;
 684 
 685                 case TAG_SimpleTimeZone:
 686                     {
 687                         if (len != 32 && len != 40) {
 688                             System.err.println("ZoneInfo: wrong SimpleTimeZone parameter size");
 689                             return null;
 690                         }
 691                         int n = len / 4;
 692                         simpleTimeZoneParams = new int[n];
 693                         for (int i = 0; i < n; i++) {
 694                             int val = buf[index++] & 0xff;
 695                             val = (val << 8) + (buf[index++] & 0xff);
 696                             val = (val << 8) + (buf[index++] & 0xff);
 697                             val = (val << 8) + (buf[index++] & 0xff);
 698                             simpleTimeZoneParams[i] = val;
 699                         }
 700                     }
 701                     break;
 702 
 703                 case TAG_GMTOffsetWillChange:
 704                     {
 705                         if (len != 1) {
 706                             System.err.println("ZoneInfo: wrong byte length for TAG_GMTOffsetWillChange");
 707                         }
 708                         willGMTOffsetChange = buf[index++] == 1;
 709                     }
 710                     break;
 711 
 712                 default:
 713                     System.err.println("ZoneInfo: unknown tag < " + tag + ">. ignored.");
 714                     index += len;
 715                     break;
 716                 }
 717             }
 718         } catch (Exception e) {
 719             System.err.println("ZoneInfo: corrupted zoneinfo file: " + id);
 720             return null;
 721         }
 722 
 723         if (index != filesize) {
 724             System.err.println("ZoneInfo: wrong file size: " + id);
 725             return null;
 726         }
 727 
 728         return new ZoneInfo(id, rawOffset, dstSavings, checksum,
 729                             transitions, offsets, simpleTimeZoneParams,
 730                             willGMTOffsetChange);
 731     }
 732 
 733     private volatile static SoftReference<List<String>> zoneIDs = null;
 734 
 735     static List<String> getZoneIDs() {
 736         List<String> ids = null;
 737 
 738         SoftReference<List<String>> cache = zoneIDs;
 739         if (cache != null) {
 740             ids = cache.get();
 741             if (ids != null) {
 742                 return ids;
 743             }
 744         }
 745 
 746         byte[] buf = null;
 747         buf = getZoneInfoMappings();
 748         int index = JAVAZM_LABEL_LENGTH + 1;
 749         int filesize = buf.length;
 750 
 751         try {
 752         loop:
 753             while (index < filesize) {
 754                 byte tag = buf[index++];
 755                 int     len = ((buf[index++] & 0xFF) << 8) + (buf[index++] & 0xFF);
 756 
 757                 switch (tag) {
 758                 case TAG_ZoneIDs:
 759                     {
 760                         int n = (buf[index++] << 8) + (buf[index++] & 0xFF);
 761                         ids = new ArrayList<String>(n);
 762 
 763                         for (int i = 0; i < n; i++) {
 764                             byte m = buf[index++];
 765                             ids.add(new String(buf, index, m, "UTF-8"));
 766                             index += m;
 767                         }
 768                     }
 769                     break loop;
 770 
 771                 default:
 772                     index += len;
 773                     break;
 774                 }
 775             }
 776         } catch (Exception e) {
 777             System.err.println("ZoneInfo: corrupted " + JAVAZM_FILE_NAME);
 778         }
 779 
 780         zoneIDs = new SoftReference<List<String>>(ids);
 781         return ids;
 782     }
 783 
 784     /**
 785      * @return an alias table in HashMap where a key is an alias ID
 786      * (e.g., "PST") and its value is a real time zone ID (e.g.,
 787      * "America/Los_Angeles").
 788      */
 789     static Map<String, String> getZoneAliases() {
 790         byte[] buf = getZoneInfoMappings();
 791         int index = JAVAZM_LABEL_LENGTH + 1;
 792         int filesize = buf.length;
 793         Map<String, String> aliases = null;
 794 
 795         try {
 796         loop:
 797             while (index < filesize) {
 798                 byte tag = buf[index++];
 799                 int     len = ((buf[index++] & 0xFF) << 8) + (buf[index++] & 0xFF);
 800 
 801                 switch (tag) {
 802                 case TAG_ZoneAliases:
 803                     {
 804                         int n = (buf[index++] << 8) + (buf[index++] & 0xFF);
 805                         aliases = new HashMap<String, String>(n);
 806                         for (int i = 0; i < n; i++) {
 807                             byte m = buf[index++];
 808                             String name = new String(buf, index, m, "UTF-8");
 809                             index += m;
 810                             m = buf[index++];
 811                             String realName = new String(buf, index, m, "UTF-8");
 812                             index += m;
 813                             aliases.put(name, realName);
 814                         }
 815                     }
 816                     break loop;
 817 
 818                 default:
 819                     index += len;
 820                     break;
 821                 }
 822             }
 823         } catch (Exception e) {
 824             System.err.println("ZoneInfo: corrupted " + JAVAZM_FILE_NAME);
 825             return null;
 826         }
 827         return aliases;
 828     }
 829 
 830     private volatile static SoftReference<List<String>> excludedIDs = null;
 831     private volatile static boolean hasNoExcludeList = false;
 832 
 833     /**
 834      * @return a List of zone IDs for zones that will change their GMT
 835      * offsets in some future time.
 836      *
 837      * @since 1.6
 838      */
 839     static List<String> getExcludedZones() {
 840         if (hasNoExcludeList) {
 841             return null;
 842         }
 843 
 844         List<String> excludeList = null;
 845 
 846         SoftReference<List<String>> cache = excludedIDs;
 847         if (cache != null) {
 848             excludeList = cache.get();
 849             if (excludeList != null) {
 850                 return excludeList;
 851             }
 852         }
 853 
 854         byte[] buf = getZoneInfoMappings();
 855         int index = JAVAZM_LABEL_LENGTH + 1;
 856         int filesize = buf.length;
 857 
 858         try {
 859           loop:
 860             while (index < filesize) {
 861                 byte tag = buf[index++];
 862                 int     len = ((buf[index++] & 0xFF) << 8) + (buf[index++] & 0xFF);
 863 
 864                 switch (tag) {
 865                 case TAG_ExcludedZones:
 866                     {
 867                         int n = (buf[index++] << 8) + (buf[index++] & 0xFF);
 868                         excludeList = new ArrayList<String>();
 869                         for (int i = 0; i < n; i++) {
 870                             byte m = buf[index++];
 871                             String name = new String(buf, index, m, "UTF-8");
 872                             index += m;
 873                             excludeList.add(name);
 874                         }
 875                     }
 876                     break loop;
 877 
 878                 default:
 879                     index += len;
 880                     break;
 881                 }
 882             }
 883         } catch (Exception e) {
 884             System.err.println("ZoneInfo: corrupted " + JAVAZM_FILE_NAME);
 885             return null;
 886         }
 887 
 888         if (excludeList != null) {
 889             excludedIDs = new SoftReference<List<String>>(excludeList);
 890         } else {
 891             hasNoExcludeList = true;
 892         }
 893         return excludeList;
 894     }
 895 
 896     private volatile static SoftReference<byte[]> rawOffsetIndices = null;
 897 
 898     static byte[] getRawOffsetIndices() {
 899         byte[] indices = null;
 900 
 901         SoftReference<byte[]> cache = rawOffsetIndices;
 902         if (cache != null) {
 903             indices = cache.get();
 904             if (indices != null) {
 905                 return indices;
 906             }
 907         }
 908 
 909         byte[] buf = getZoneInfoMappings();
 910         int index = JAVAZM_LABEL_LENGTH + 1;
 911         int filesize = buf.length;
 912 
 913         try {
 914         loop:
 915             while (index < filesize) {
 916                 byte tag = buf[index++];
 917                 int     len = ((buf[index++] & 0xFF) << 8) + (buf[index++] & 0xFF);
 918 
 919                 switch (tag) {
 920                 case TAG_RawOffsetIndices:
 921                     {
 922                         indices = new byte[len];
 923                         for (int i = 0; i < len; i++) {
 924                             indices[i] = buf[index++];
 925                         }
 926                     }
 927                     break loop;
 928 
 929                 default:
 930                     index += len;
 931                     break;
 932                 }
 933             }
 934         } catch (ArrayIndexOutOfBoundsException e) {
 935             System.err.println("ZoneInfo: corrupted " + JAVAZM_FILE_NAME);
 936         }
 937 
 938         rawOffsetIndices = new SoftReference<byte[]>(indices);
 939         return indices;
 940     }
 941 
 942     private volatile static SoftReference<int[]> rawOffsets = null;
 943 
 944     static int[] getRawOffsets() {
 945         int[] offsets = null;
 946 
 947         SoftReference<int[]> cache = rawOffsets;
 948         if (cache != null) {
 949             offsets = cache.get();
 950             if (offsets != null) {
 951                 return offsets;
 952             }
 953         }
 954 
 955         byte[] buf = getZoneInfoMappings();
 956         int index = JAVAZM_LABEL_LENGTH + 1;
 957         int filesize = buf.length;
 958 
 959         try {
 960         loop:
 961             while (index < filesize) {
 962                 byte tag = buf[index++];
 963                 int     len = ((buf[index++] & 0xFF) << 8) + (buf[index++] & 0xFF);
 964 
 965                 switch (tag) {
 966                 case TAG_RawOffsets:
 967                     {
 968                         int n = len/4;
 969                         offsets = new int[n];
 970                         for (int i = 0; i < n; i++) {
 971                             int val = buf[index++] & 0xff;
 972                             val = (val << 8) + (buf[index++] & 0xff);
 973                             val = (val << 8) + (buf[index++] & 0xff);
 974                             val = (val << 8) + (buf[index++] & 0xff);
 975                             offsets[i] = val;
 976                         }
 977                     }
 978                     break loop;
 979 
 980                 default:
 981                     index += len;
 982                     break;
 983                 }
 984             }
 985         } catch (ArrayIndexOutOfBoundsException e) {
 986             System.err.println("ZoneInfo: corrupted " + JAVAZM_FILE_NAME);
 987         }
 988 
 989         rawOffsets = new SoftReference<int[]>(offsets);
 990         return offsets;
 991     }
 992 
 993     private volatile static SoftReference<byte[]> zoneInfoMappings = null;
 994 
 995     private static byte[] getZoneInfoMappings() {
 996         byte[] data;
 997 
 998         SoftReference<byte[]> cache = zoneInfoMappings;
 999         if (cache != null) {
1000             data = cache.get();
1001             if (data != null) {
1002                 return data;
1003             }
1004         }
1005 
1006         data = readZoneInfoFile(JAVAZM_FILE_NAME);
1007 
1008         if (data == null) {
1009             return null;
1010         }
1011 
1012         int index;
1013         for (index = 0; index < JAVAZM_LABEL.length; index++) {
1014             if (data[index] != JAVAZM_LABEL[index]) {
1015                 System.err.println("ZoneInfo: wrong magic number: " + JAVAZM_FILE_NAME);
1016                 return null;
1017             }
1018         }
1019         if (data[index++] > JAVAZM_VERSION) {
1020             System.err.println("ZoneInfo: incompatible version ("
1021                                + data[index - 1] + "): " + JAVAZM_FILE_NAME);
1022             return null;
1023         }
1024 
1025         zoneInfoMappings = new SoftReference<byte[]>(data);
1026         return data;
1027     }
1028 
1029     /**
1030      * Reads the specified file under &lt;java.home&gt;/lib/zi into a buffer.
1031      * @return the buffer, or null if any I/O error occurred.
1032      */
1033     private static byte[] readZoneInfoFile(final String fileName) {
1034         byte[] buffer = null;
1035 
1036         try {
1037             buffer = (byte[]) AccessController.doPrivileged(new PrivilegedExceptionAction() {
1038                 public Object run() throws IOException {
1039                     File file = new File(ziDir, fileName);
1040                     if (!file.exists() || !file.isFile()) {
1041                         return null;
1042                     }
1043                     file = file.getCanonicalFile();
1044                     String path = file.getCanonicalPath();
1045                     byte[] buf = null;
1046                     if (path != null && path.startsWith(ziDir)) {
1047                         int filesize = (int)file.length();
1048                         if (filesize > 0) {
1049                             FileInputStream fis = new FileInputStream(file);
1050                             buf = new byte[filesize];
1051                             try {
1052                                 if (fis.read(buf) != filesize) {
1053                                     throw new IOException("read error on " + fileName);
1054                                 }
1055                             } finally {
1056                                 fis.close();
1057                             }
1058                         }
1059                     }
1060                     return buf;
1061                 }
1062             });
1063         } catch (PrivilegedActionException e) {
1064             Exception ex = e.getException();
1065             if (!(ex instanceof FileNotFoundException) || JAVAZM_FILE_NAME.equals(fileName)) {
1066                 System.err.println("ZoneInfo: " + ex.getMessage());
1067             }
1068         }
1069         return buffer;
1070     }
1071 }