Home | History | Annotate | Download | only in zoneinfo
      1 
      2 import java.io.*;
      3 import java.nio.ByteOrder;
      4 import java.util.*;
      5 import libcore.io.BufferIterator;
      6 import libcore.util.ZoneInfo;
      7 
      8 // usage: java ZoneCompiler <setup file> <data directory> <output directory> <tzdata version>
      9 //
     10 // Compile a set of tzfile-formatted files into a single file containing an index.
     11 //
     12 // The compilation is controlled by a setup file, which is provided as a
     13 // command-line argument.  The setup file has the form:
     14 //
     15 // Link <toName> <fromName>
     16 // ...
     17 // <zone filename>
     18 // ...
     19 //
     20 // Note that the links must be declared prior to the zone names.
     21 // A zone name is a filename relative to the source directory such as
     22 // 'GMT', 'Africa/Dakar', or 'America/Argentina/Jujuy'.
     23 //
     24 // Use the 'zic' command-line tool to convert from flat files
     25 // (such as 'africa' or 'northamerica') to a directory
     26 // hierarchy suitable for this tool (containing files such as 'data/Africa/Abidjan').
     27 //
     28 
     29 public class ZoneCompactor {
     30   public static class ByteArrayBufferIteratorBE extends BufferIterator {
     31     private final byte[] bytes;
     32     private int offset = 0;
     33 
     34     public ByteArrayBufferIteratorBE(byte[] bytes) {
     35       this.bytes = bytes;
     36       this.offset = 0;
     37     }
     38 
     39     public void seek(int offset) {
     40       this.offset = offset;
     41     }
     42 
     43     public void skip(int byteCount) {
     44       this.offset += byteCount;
     45     }
     46 
     47     public void readByteArray(byte[] dst, int dstOffset, int byteCount) {
     48       System.arraycopy(bytes, offset, dst, dstOffset, byteCount);
     49       offset += byteCount;
     50     }
     51 
     52     public byte readByte() {
     53       return bytes[offset++];
     54     }
     55 
     56     public int readInt() {
     57       return ((readByte() & 0xff) << 24) | ((readByte() & 0xff) << 16) | ((readByte() & 0xff) << 8) | (readByte() & 0xff);
     58     }
     59 
     60     public void readIntArray(int[] dst, int dstOffset, int intCount) {
     61       for (int i = 0; i < intCount; ++i) {
     62         dst[dstOffset++] = readInt();
     63       }
     64     }
     65 
     66     public short readShort() {
     67       throw new UnsupportedOperationException();
     68     }
     69   }
     70 
     71   // Maximum number of characters in a zone name, including '\0' terminator
     72   private static final int MAXNAME = 40;
     73 
     74   // Zone name synonyms
     75   private Map<String,String> links = new HashMap<String,String>();
     76 
     77   // File starting bytes by zone name
     78   private Map<String,Integer> starts = new HashMap<String,Integer>();
     79 
     80   // File lengths by zone name
     81   private Map<String,Integer> lengths = new HashMap<String,Integer>();
     82 
     83   // Raw GMT offsets by zone name
     84   private Map<String,Integer> offsets = new HashMap<String,Integer>();
     85   private int start = 0;
     86 
     87   // Concatenate the contents of 'inFile' onto 'out'
     88   // and return the contents as a byte array.
     89   private static byte[] copyFile(File inFile, OutputStream out) throws Exception {
     90     byte[] ret = new byte[0];
     91 
     92     InputStream in = new FileInputStream(inFile);
     93     byte[] buf = new byte[8192];
     94     while (true) {
     95       int nbytes = in.read(buf);
     96       if (nbytes == -1) {
     97         break;
     98       }
     99       out.write(buf, 0, nbytes);
    100 
    101       byte[] nret = new byte[ret.length + nbytes];
    102       System.arraycopy(ret, 0, nret, 0, ret.length);
    103       System.arraycopy(buf, 0, nret, ret.length, nbytes);
    104       ret = nret;
    105     }
    106     out.flush();
    107     return ret;
    108   }
    109 
    110   public ZoneCompactor(String setupFile, String dataDirectory, String zoneTabFile, String outputDirectory, String version) throws Exception {
    111     // Read the setup file, and concatenate all the data.
    112     ByteArrayOutputStream allData = new ByteArrayOutputStream();
    113     BufferedReader reader = new BufferedReader(new FileReader(setupFile));
    114     String s;
    115     while ((s = reader.readLine()) != null) {
    116       s = s.trim();
    117       if (s.startsWith("Link")) {
    118         StringTokenizer st = new StringTokenizer(s);
    119         st.nextToken();
    120         String to = st.nextToken();
    121         String from = st.nextToken();
    122         links.put(from, to);
    123       } else {
    124         String link = links.get(s);
    125         if (link == null) {
    126           File sourceFile = new File(dataDirectory, s);
    127           long length = sourceFile.length();
    128           starts.put(s, start);
    129           lengths.put(s, (int) length);
    130 
    131           start += length;
    132           byte[] data = copyFile(sourceFile, allData);
    133 
    134           BufferIterator it = new ByteArrayBufferIteratorBE(data);
    135           TimeZone tz = ZoneInfo.makeTimeZone(s, it);
    136           int gmtOffset = tz.getRawOffset();
    137           offsets.put(s, gmtOffset);
    138         }
    139       }
    140     }
    141     reader.close();
    142 
    143     // Fill in fields for links.
    144     Iterator<String> it = links.keySet().iterator();
    145     while (it.hasNext()) {
    146       String from = it.next();
    147       String to = links.get(from);
    148 
    149       starts.put(from, starts.get(to));
    150       lengths.put(from, lengths.get(to));
    151       offsets.put(from, offsets.get(to));
    152     }
    153 
    154     // Create/truncate the destination file.
    155     RandomAccessFile f = new RandomAccessFile(new File(outputDirectory, "tzdata"), "rw");
    156     f.setLength(0);
    157 
    158     // Write the header.
    159 
    160     // byte[12] tzdata_version -- 'tzdata2012f\0'
    161     // int index_offset -- so we can slip in extra header fields in a backwards-compatible way
    162     // int data_offset
    163     // int zonetab_offset
    164 
    165     // tzdata_version
    166     f.write(toAscii(new byte[12], version));
    167 
    168     // Write dummy values for the three offsets, and remember where we need to seek back to later
    169     // when we have the real values.
    170     int index_offset_offset = (int) f.getFilePointer();
    171     f.writeInt(0);
    172     int data_offset_offset = (int) f.getFilePointer();
    173     f.writeInt(0);
    174     int zonetab_offset_offset = (int) f.getFilePointer();
    175     f.writeInt(0);
    176 
    177     int index_offset = (int) f.getFilePointer();
    178 
    179     // Write the index.
    180     ArrayList<String> sortedOlsonIds = new ArrayList<String>();
    181     sortedOlsonIds.addAll(starts.keySet());
    182     Collections.sort(sortedOlsonIds);
    183     it = sortedOlsonIds.iterator();
    184     while (it.hasNext()) {
    185       String zoneName = it.next();
    186       if (zoneName.length() >= MAXNAME) {
    187         throw new RuntimeException("zone filename too long: " + zoneName.length());
    188       }
    189 
    190       f.write(toAscii(new byte[MAXNAME], zoneName));
    191       f.writeInt(starts.get(zoneName));
    192       f.writeInt(lengths.get(zoneName));
    193       f.writeInt(offsets.get(zoneName));
    194     }
    195 
    196     int data_offset = (int) f.getFilePointer();
    197 
    198     // Write the data.
    199     f.write(allData.toByteArray());
    200 
    201     int zonetab_offset = (int) f.getFilePointer();
    202 
    203     // Copy the zone.tab.
    204     reader = new BufferedReader(new FileReader(zoneTabFile));
    205     while ((s = reader.readLine()) != null) {
    206       if (!s.startsWith("#")) {
    207         f.writeBytes(s);
    208         f.write('\n');
    209       }
    210     }
    211     reader.close();
    212 
    213     // Go back and fix up the offsets in the header.
    214     f.seek(index_offset_offset);
    215     f.writeInt(index_offset);
    216     f.seek(data_offset_offset);
    217     f.writeInt(data_offset);
    218     f.seek(zonetab_offset_offset);
    219     f.writeInt(zonetab_offset);
    220 
    221     f.close();
    222   }
    223 
    224   private static byte[] toAscii(byte[] dst, String src) {
    225     for (int i = 0; i < src.length(); ++i) {
    226       if (src.charAt(i) > '~') {
    227         throw new RuntimeException("non-ASCII string: " + src);
    228       }
    229       dst[i] = (byte) src.charAt(i);
    230     }
    231     return dst;
    232   }
    233 
    234   public static void main(String[] args) throws Exception {
    235     if (args.length != 5) {
    236       System.err.println("usage: java ZoneCompactor <setup file> <data directory> <zone.tab file> <output directory> <tzdata version>");
    237       System.exit(0);
    238     }
    239     new ZoneCompactor(args[0], args[1], args[2], args[3], args[4]);
    240   }
    241 }
    242