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