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