1d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes
2d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughesimport java.io.*;
3d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughesimport java.util.*;
4d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes
5d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes// usage: java ZoneCompiler <setup file> <top-level directory>
6d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes//
7d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes// Compile a set of tzfile-formatted files into a single file plus
8d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes// an index file.
9d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes//
10d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes// The compilation is controlled by a setup file, which is provided as a
11d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes// command-line argument.  The setup file has the form:
12d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes//
13d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes// Link <toName> <fromName>
14d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes// ...
15d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes// <zone filename>
16d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes// ...
17d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes//
18d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes// Note that the links must be declared prior to the zone names.  A
19d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes// zone name is a filename relative to the source directory such as
20d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes// 'GMT', 'Africa/Dakar', or 'America/Argentina/Jujuy'.
21d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes//
22d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes// Use the 'zic' command-line tool to convert from flat files
23d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes// (e.g., 'africa', 'northamerica') into a suitable source directory
24d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes// hierarchy for this tool (e.g., 'data/Africa/Abidjan').
25d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes//
26d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes// Example:
27d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes//     zic -d data tz2007h
28d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes//     javac ZoneCompactor.java
29d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes//     java ZoneCompactor setup data
30d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes//     <produces zoneinfo.dat and zoneinfo.idx>
31d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes
32d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughespublic class ZoneCompactor {
33d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes
34d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes    // Zone name synonyms
35d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes    Map<String,String> links = new HashMap<String,String>();
36d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes
37d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes    // File starting bytes by zone name
38d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes    Map<String,Integer> starts = new HashMap<String,Integer>();
39d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes
40d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes    // File lengths by zone name
41d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes    Map<String,Integer> lengths = new HashMap<String,Integer>();
42d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes
43d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes    // Raw GMT offsets by zone name
44d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes    Map<String,Integer> offsets = new HashMap<String,Integer>();
45d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes    int start = 0;
46d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes
47d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes    // Maximum number of characters in a zone name, including '\0' terminator
48d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes    private static final int MAXNAME = 40;
49d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes
50d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes    // Concatenate the contents of 'inFile' onto 'out'
51d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes    // and return the contents as a byte array.
52d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes    private static byte[] copyFile(File inFile, OutputStream out)
53d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes        throws Exception {
54d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes        byte[] ret = new byte[0];
55d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes
56d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes        InputStream in = new FileInputStream(inFile);
57d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes        byte[] buf = new byte[8192];
58d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes        while (true) {
59d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes            int nbytes = in.read(buf);
60d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes            if (nbytes == -1) {
61d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes                break;
62d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes            }
63d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes            out.write(buf, 0, nbytes);
64d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes
65d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes            byte[] nret = new byte[ret.length + nbytes];
66d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes            System.arraycopy(ret, 0, nret, 0, ret.length);
67d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes            System.arraycopy(buf, 0, nret, ret.length, nbytes);
68d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes            ret = nret;
69d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes        }
70d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes        out.flush();
71d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes        return ret;
72d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes    }
73d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes
74d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes    // Write a 32-bit integer in network byte order
75d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes    private void writeInt(OutputStream os, int x) throws IOException {
76d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes        os.write((x >> 24) & 0xff);
77d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes        os.write((x >> 16) & 0xff);
78d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes        os.write((x >>  8) & 0xff);
79d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes        os.write( x        & 0xff);
80d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes    }
81d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes
82d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes    public ZoneCompactor(String setupFilename, String dirName)
83d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes        throws Exception {
84d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes        File zoneInfoFile = new File("zoneinfo.dat");
85d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes        zoneInfoFile.delete();
86d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes        OutputStream zoneInfo = new FileOutputStream(zoneInfoFile);
87d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes
88d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes        BufferedReader rdr = new BufferedReader(new FileReader(setupFilename));
89d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes
90d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes        String s;
91d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes        while ((s = rdr.readLine()) != null) {
92d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes            s = s.trim();
93d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes            if (s.startsWith("Link")) {
94d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes                StringTokenizer st = new StringTokenizer(s);
95d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes                st.nextToken();
96d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes                String to = st.nextToken();
97d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes                String from = st.nextToken();
98d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes                links.put(from, to);
99d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes            } else {
100d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes                String link = links.get(s);
101d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes                if (link == null) {
102d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes                    File f = new File(dirName, s);
103d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes                    long length = f.length();
104d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes                    starts.put(s, new Integer(start));
105d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes                    lengths.put(s, new Integer((int)length));
106d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes
107d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes                    start += length;
108d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes                    byte[] data = copyFile(f, zoneInfo);
109d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes
110d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes                    TimeZone tz = ZoneInfo.make(s, data);
111d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes                    int gmtOffset = tz.getRawOffset();
112d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes                    offsets.put(s, new Integer(gmtOffset));
113d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes                }
114d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes            }
115d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes        }
116d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes        zoneInfo.close();
117d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes
118d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes        // Fill in fields for links
119d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes        Iterator<String> iter = links.keySet().iterator();
120d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes        while (iter.hasNext()) {
121d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes            String from = iter.next();
122d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes            String to = links.get(from);
123d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes
124d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes            starts.put(from, starts.get(to));
125d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes            lengths.put(from, lengths.get(to));
126d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes            offsets.put(from, offsets.get(to));
127d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes        }
128d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes
129d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes        File idxFile = new File("zoneinfo.idx");
130d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes        idxFile.delete();
131d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes        FileOutputStream idx = new FileOutputStream(idxFile);
132d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes
133d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes        ArrayList<String> l = new ArrayList<String>();
134d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes        l.addAll(starts.keySet());
135d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes        Collections.sort(l);
136d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes        Iterator<String> ziter = l.iterator();
137d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes        while (ziter.hasNext()) {
138d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes            String zname = ziter.next();
139d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes            if (zname.length() >= MAXNAME) {
140d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes                System.err.println("Error - zone filename exceeds " +
141d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes                                   (MAXNAME - 1) + " characters!");
142d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes            }
143d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes
144d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes            byte[] znameBuf = new byte[MAXNAME];
145d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes            for (int i = 0; i < zname.length(); i++) {
146d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes                znameBuf[i] = (byte)zname.charAt(i);
147d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes            }
148d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes            idx.write(znameBuf);
149d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes            writeInt(idx, starts.get(zname).intValue());
150d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes            writeInt(idx, lengths.get(zname).intValue());
151d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes            writeInt(idx, offsets.get(zname).intValue());
152d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes        }
153d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes        idx.close();
154d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes
155d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes        // System.out.println("maxLength = " + maxLength);
156d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes    }
157d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes
158d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes    public static void main(String[] args) throws Exception {
159d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes        if (args.length != 2) {
160d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes            System.err.println("usage: java ZoneCompactor <setup> <data dir>");
161d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes            System.exit(0);
162d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes        }
163d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes        new ZoneCompactor(args[0], args[1]);
164d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes    }
165d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes
166d40e63ee47e4a7f072a9d9a20e09c26f0090b02cElliott Hughes}
167