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