1 /*
   2  * Copyright (c) 2000, 2013, 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 import  java.io.IOException;
  27 import  java.io.File;
  28 import  java.io.FileOutputStream;
  29 import  java.io.DataOutputStream;
  30 import  java.io.RandomAccessFile;
  31 import  java.util.List;
  32 import  java.util.Map;
  33 import  java.util.Set;
  34 
  35 /**
  36  * <code>Gen</code> is one of back-end classes of javazic, and generates
  37  * ZoneInfoMappings and zone-specific file for each zone.
  38  */
  39 class Gen extends BackEnd {
  40 
  41     /**
  42      * Generates datafile in binary TLV format for each time zone.
  43      * Regarding contents of output files, see {@link ZoneInfoFile}.
  44      *
  45      * @param Timezone
  46      * @return 0 if no errors, or 1 if error occurred.
  47      */
  48     int processZoneinfo(Timezone tz) {
  49         try {
  50             int size;
  51             String outputDir = Main.getOutputDir();
  52             String zonefile = ZoneInfoFile.getFileName(tz.getName());
  53 
  54             /* If outputDir doesn't end with file-separator, adds it. */
  55             if (!outputDir.endsWith(File.separator)) {
  56                 outputDir += File.separatorChar;
  57             }
  58 
  59             /* If zonefile includes file-separator, it's treated as part of
  60              * pathname. And make directory if necessary.
  61              */
  62             int index = zonefile.lastIndexOf(File.separatorChar);
  63             if (index != -1) {
  64                 outputDir += zonefile.substring(0, index+1);
  65             }
  66             File outD = new File(outputDir);
  67             outD.mkdirs();
  68 
  69             FileOutputStream fos =
  70                 new FileOutputStream(outputDir + zonefile.substring(index+1));
  71             DataOutputStream dos = new DataOutputStream(fos);
  72 
  73             /* Output Label */
  74             dos.write(ZoneInfoFile.JAVAZI_LABEL, 0,
  75                       ZoneInfoFile.JAVAZI_LABEL.length);
  76 
  77             /* Output Version of ZoneInfoFile */
  78             dos.writeByte(ZoneInfoFile.JAVAZI_VERSION);
  79 
  80             List<Long> transitions = tz.getTransitions();
  81             if (transitions != null) {
  82                 List<Integer> dstOffsets = tz.getDstOffsets();
  83                 List<Integer> offsets = tz.getOffsets();
  84 
  85                 if ((dstOffsets == null && offsets != null) ||
  86                     (dstOffsets != null && offsets == null)) {
  87                     Main.panic("Data not exist. (dstOffsets or offsets)");
  88                     return 1;
  89                 }
  90 
  91                 /* Output Transition records */
  92                 dos.writeByte(ZoneInfoFile.TAG_Transition);
  93                 size = transitions.size();
  94                 dos.writeShort((size * 8) & 0xFFFF);
  95                 int dstoffset;
  96                 for (int i = 0; i < size; i++) {
  97                     /* if DST offset is 0, this means DST isn't used.
  98                      * (NOT: offset's index is 0.)
  99                      */
 100                     if ((dstoffset = dstOffsets.get(i).intValue()) == -1) {
 101                         dstoffset = 0;
 102                     }
 103 
 104                     dos.writeLong((transitions.get(i).longValue() << 12)
 105                                   | (dstoffset << 4)
 106                                   | offsets.get(i).intValue());
 107 
 108                 }
 109 
 110                 /* Output data for GMTOffset */
 111                 List<Integer> gmtoffset = tz.getGmtOffsets();
 112                 dos.writeByte(ZoneInfoFile.TAG_Offset);
 113                 size = gmtoffset.size();
 114                 dos.writeShort((size * 4) & 0xFFFF);
 115                 for (int i = 0; i < size; i++) {
 116                     dos.writeInt(gmtoffset.get(i));
 117                 }
 118             }
 119 
 120             /* Output data for SimpleTimeZone */
 121             List<RuleRec> stz = tz.getLastRules();
 122             if (stz != null) {
 123                 RuleRec[] rr = new RuleRec[2];
 124                 boolean wall = true;
 125 
 126                 rr[0] = stz.get(0);
 127                 rr[1] = stz.get(1);
 128 
 129                 dos.writeByte(ZoneInfoFile.TAG_SimpleTimeZone);
 130                 wall = rr[0].getTime().isWall() && rr[1].getTime().isWall();
 131                 if (wall) {
 132                     dos.writeShort(32);
 133                 } else {
 134                     dos.writeShort(40);
 135                 }
 136 
 137                 for (int i = 0; i < 2; i++) {
 138                     dos.writeInt(rr[i].getMonthNum() - 1); // 0-based month number
 139                     dos.writeInt(rr[i].getDay().getDayForSimpleTimeZone());
 140                     dos.writeInt(rr[i].getDay().getDayOfWeekForSimpleTimeZoneInt());
 141                     dos.writeInt((int)rr[i].getTime().getTime());
 142                     if (!wall) {
 143                         dos.writeInt((rr[i].getTime().getType() & 0xFF) - 1);
 144                     }
 145                 }
 146             }
 147 
 148             /* Output RawOffset */
 149             dos.writeByte(ZoneInfoFile.TAG_RawOffset);
 150             dos.writeShort(4);
 151             dos.writeInt(tz.getRawOffset());
 152 
 153             /* Output willGMTOffsetChange flag */
 154             if (tz.willGMTOffsetChange()) {
 155                 dos.writeByte(ZoneInfoFile.TAG_GMTOffsetWillChange);
 156                 dos.writeShort(1);
 157                 dos.writeByte(1);
 158             }
 159 
 160             /* Output LastDSTSaving */
 161             dos.writeByte(ZoneInfoFile.TAG_LastDSTSaving);
 162             dos.writeShort(2);
 163             dos.writeShort(tz.getLastDSTSaving()/1000);
 164 
 165             /* Output checksum */
 166             dos.writeByte(ZoneInfoFile.TAG_CRC32);
 167             dos.writeShort(4);
 168             dos.writeInt(tz.getCRC32());
 169 
 170             fos.close();
 171             dos.close();
 172         } catch(IOException e) {
 173             Main.panic("IO error: "+e.getMessage());
 174             return 1;
 175         }
 176 
 177         return 0;
 178     }
 179 
 180     /**
 181      * Generates ZoneInfoMappings in binary TLV format for each zone.
 182      * Regarding contents of output files, see {@link ZoneInfoFile}.
 183      *
 184      * @param Mappings
 185      * @return 0 if no errors, or 1 if error occurred.
 186      */
 187     int generateSrc(Mappings map) {
 188         try {
 189             int index;
 190             int block_size;
 191             int roi_size;
 192             long fp;
 193             String outputDir = Main.getOutputDir();
 194 
 195             /* If outputDir doesn't end with file-separator, adds it. */
 196             if (!outputDir.endsWith(File.separator)) {
 197                 outputDir += File.separatorChar;
 198             }
 199 
 200             File outD = new File(outputDir);
 201             outD.mkdirs();
 202 
 203             /* Open ZoneInfoMapping file to write. */
 204             RandomAccessFile raf =
 205                 new RandomAccessFile(outputDir + ZoneInfoFile.JAVAZM_FILE_NAME, "rw");
 206 
 207             /* Whether rawOffsetIndex list exists or not. */
 208             List<Integer> roi = map.getRawOffsetsIndex();
 209             if (roi == null) {
 210                 Main.panic("Data not exist. (rawOffsetsIndex)");
 211                 return 1;
 212             }
 213             roi_size = roi.size();
 214 
 215             /* Whether rawOffsetIndexTable list exists or not. */
 216             List<Set<String>> roit = map.getRawOffsetsIndexTable();
 217             if (roit == null || roit.size() != roi_size) {
 218                 Main.panic("Data not exist. (rawOffsetsIndexTable) Otherwise, Invalid size");
 219                 return 1;
 220             }
 221 
 222             /* Output Label */
 223             raf.write(ZoneInfoFile.JAVAZM_LABEL, 0,
 224                       ZoneInfoFile.JAVAZM_LABEL.length);
 225 
 226             /* Output Version */
 227             raf.writeByte(ZoneInfoFile.JAVAZM_VERSION);
 228 
 229             index = ZoneInfoFile.JAVAZM_LABEL.length + 2;
 230 
 231             /* Output Version of Olson's tzdata */
 232             byte[] b = Main.getVersionName().getBytes("UTF-8");
 233             raf.writeByte(ZoneInfoFile.TAG_TZDataVersion);
 234             raf.writeShort((b.length+1) & 0xFFFF);
 235             raf.write(b);
 236             raf.writeByte(0x00);
 237             index += b.length + 4;
 238 
 239             /* Output ID list. */
 240             raf.writeByte(ZoneInfoFile.TAG_ZoneIDs);
 241             block_size = 2;
 242             raf.writeShort(block_size & 0xFFFF);
 243             short nID = 0;
 244             raf.writeShort(nID & 0xFFFF);
 245             for (int i = 0; i < roi_size; i++) {
 246                 for (String key : roit.get(i)) {
 247                     byte size = (byte)key.getBytes("UTF-8").length;
 248                     raf.writeByte(size & 0xFF);
 249                     raf.write(key.getBytes("UTF-8"), 0, size);
 250                     block_size += 1 + size;
 251                     nID++;
 252                 }
 253             }
 254             fp = raf.getFilePointer();
 255             raf.seek(index);
 256             raf.writeShort((block_size) & 0xFFFF);
 257             raf.writeShort(nID & 0xFFFF);
 258             raf.seek(fp);
 259 
 260             /* Output sorted rawOffset list. */
 261             raf.writeByte(ZoneInfoFile.TAG_RawOffsets);
 262             index += 3 + block_size;
 263             block_size = roi_size * 4;
 264             raf.writeShort(block_size & 0xFFFF);
 265             for (int i = 0; i < roi_size; i++) {
 266                 raf.writeInt(Integer.parseInt(roi.get(i).toString()));
 267             }
 268 
 269             /* Output sorted rawOffsetIndex list. */
 270             raf.writeByte(ZoneInfoFile.TAG_RawOffsetIndices);
 271             index += 3 + block_size;
 272             block_size = 0;
 273             raf.writeShort(block_size & 0xFFFF);
 274             int num;
 275             for (int i = 0; i < roi_size; i++) {
 276                 num = roit.get(i).size();
 277                 block_size += num;
 278                 for (int j = 0; j < num; j++) {
 279                     raf.writeByte(i);
 280                 }
 281             }
 282             fp = raf.getFilePointer();
 283             raf.seek(index);
 284             raf.writeShort((block_size) & 0xFFFF);
 285             raf.seek(fp);
 286 
 287             /* Whether alias list exists or not. */
 288             Map<String,String> a = map.getAliases();
 289             if (a == null) {
 290                 Main.panic("Data not exist. (aliases)");
 291                 return 0;
 292             }
 293 
 294             /* Output ID list. */
 295             raf.writeByte(ZoneInfoFile.TAG_ZoneAliases);
 296             index += 3 + block_size;
 297             block_size = 2;
 298             raf.writeShort(block_size & 0xFFFF);
 299             raf.writeShort(a.size() & 0xFFFF);
 300             for (String key : a.keySet()) {
 301                 String alias = a.get(key);
 302                 byte key_size = (byte)key.length();
 303                 byte alias_size = (byte)alias.length();
 304                 raf.writeByte(key_size & 0xFF);
 305                 raf.write(key.getBytes("UTF-8"), 0, key_size);
 306                 raf.writeByte(alias_size & 0xFF);
 307                 raf.write(alias.getBytes("UTF-8"), 0, alias_size);
 308                 block_size += 2 + key_size + alias_size;
 309             }
 310             fp = raf.getFilePointer();
 311             raf.seek(index);
 312             raf.writeShort((block_size) & 0xFFFF);
 313             raf.seek(fp);
 314 
 315             /* Output the exclude list if it exists. */
 316             List<String> excludedZones = map.getExcludeList();
 317             if (excludedZones != null) {
 318                 raf.writeByte(ZoneInfoFile.TAG_ExcludedZones);
 319                 index += 3 + block_size;
 320                 block_size = 2;
 321                 raf.writeShort(block_size & 0xFFFF);  // place holder
 322                 raf.writeShort(excludedZones.size()); // the number of excluded zones
 323                 for (String name : excludedZones) {
 324                     byte size = (byte) name.length();
 325                     raf.writeByte(size);                 // byte length
 326                     raf.write(name.getBytes("UTF-8"), 0, size); // zone name
 327                     block_size += 1 + size;
 328                 }
 329                 fp = raf.getFilePointer();
 330                 raf.seek(index);
 331                 raf.writeShort(block_size & 0xFFFF);
 332                 raf.seek(fp);
 333             }
 334 
 335             /* Close ZoneInfoMapping file. */
 336             raf.close();
 337         } catch(IOException e) {
 338             Main.panic("IO error: "+e.getMessage());
 339             return 1;
 340         }
 341 
 342         return 0;
 343     }
 344 }