blob: 2d598fec041c6888689698dd0e0db5ed7e36ea02 [file] [log] [blame]
Elliott Hughesd40e63e2011-02-17 16:20:07 -08001
2import java.io.*;
3import java.util.*;
4
Elliott Hughes328a4842012-10-19 13:03:52 -07005// usage: java ZoneCompiler <setup file> <data directory> <output directory> <tzdata version>
Elliott Hughesd40e63e2011-02-17 16:20:07 -08006//
Elliott Hughes5b1497a2012-10-19 14:47:37 -07007// Compile a set of tzfile-formatted files into a single file containing an index.
Elliott Hughesd40e63e2011-02-17 16:20:07 -08008//
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//
Elliott Hughes5b1497a2012-10-19 14:47:37 -070017// 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
Elliott Hughesd40e63e2011-02-17 16:20:07 -080019// 'GMT', 'Africa/Dakar', or 'America/Argentina/Jujuy'.
20//
21// Use the 'zic' command-line tool to convert from flat files
Elliott Hughes5b1497a2012-10-19 14:47:37 -070022// (such as 'africa' or 'northamerica') to a directory
23// hierarchy suitable for this tool (containing files such as 'data/Africa/Abidjan').
Elliott Hughesd40e63e2011-02-17 16:20:07 -080024//
Elliott Hughesd40e63e2011-02-17 16:20:07 -080025
26public class ZoneCompactor {
Elliott Hughes90cb5ff2014-08-06 15:23:11 -070027 // Maximum number of characters in a zone name, including '\0' terminator.
Elliott Hughes5b1497a2012-10-19 14:47:37 -070028 private static final int MAXNAME = 40;
29
Elliott Hughes90cb5ff2014-08-06 15:23:11 -070030 // Zone name synonyms.
Elliott Hughes5b1497a2012-10-19 14:47:37 -070031 private Map<String,String> links = new HashMap<String,String>();
32
Elliott Hughes90cb5ff2014-08-06 15:23:11 -070033 // File offsets by zone name.
34 private Map<String,Integer> offsets = new HashMap<String,Integer>();
Elliott Hughes5b1497a2012-10-19 14:47:37 -070035
Elliott Hughes90cb5ff2014-08-06 15:23:11 -070036 // File lengths by zone name.
Elliott Hughes5b1497a2012-10-19 14:47:37 -070037 private Map<String,Integer> lengths = new HashMap<String,Integer>();
38
Elliott Hughes90cb5ff2014-08-06 15:23:11 -070039 // Concatenate the contents of 'inFile' onto 'out'.
40 private static void copyFile(File inFile, OutputStream out) throws Exception {
Elliott Hughes5b1497a2012-10-19 14:47:37 -070041 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();
Elliott Hughes5b1497a2012-10-19 14:47:37 -070058 }
59
Elliott Hughes23935352012-10-22 14:47:58 -070060 public ZoneCompactor(String setupFile, String dataDirectory, String zoneTabFile, String outputDirectory, String version) throws Exception {
Elliott Hughes90cb5ff2014-08-06 15:23:11 -070061 // Read the setup file and concatenate all the data.
Elliott Hughes5b1497a2012-10-19 14:47:37 -070062 ByteArrayOutputStream allData = new ByteArrayOutputStream();
63 BufferedReader reader = new BufferedReader(new FileReader(setupFile));
64 String s;
Elliott Hughes90cb5ff2014-08-06 15:23:11 -070065 int offset = 0;
Elliott Hughes5b1497a2012-10-19 14:47:37 -070066 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();
Elliott Hughes90cb5ff2014-08-06 15:23:11 -070079 offsets.put(s, offset);
Elliott Hughes5b1497a2012-10-19 14:47:37 -070080 lengths.put(s, (int) length);
81
Elliott Hughes90cb5ff2014-08-06 15:23:11 -070082 offset += length;
83 copyFile(sourceFile, allData);
Elliott Hughes5b1497a2012-10-19 14:47:37 -070084 }
85 }
86 }
Elliott Hughes23935352012-10-22 14:47:58 -070087 reader.close();
Elliott Hughes5b1497a2012-10-19 14:47:37 -070088
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
Elliott Hughes5b1497a2012-10-19 14:47:37 -070095 offsets.put(from, offsets.get(to));
Elliott Hughes90cb5ff2014-08-06 15:23:11 -070096 lengths.put(from, lengths.get(to));
Elliott Hughes5b1497a2012-10-19 14:47:37 -070097 }
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
Elliott Hughes23935352012-10-22 14:47:58 -0700105 // byte[12] tzdata_version -- 'tzdata2012f\0'
106 // int index_offset -- so we can slip in extra header fields in a backwards-compatible way
Elliott Hughes5b1497a2012-10-19 14:47:37 -0700107 // int data_offset
108 // int zonetab_offset
109
110 // tzdata_version
111 f.write(toAscii(new byte[12], version));
112
Elliott Hughes5b1497a2012-10-19 14:47:37 -0700113 // 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>();
Elliott Hughes90cb5ff2014-08-06 15:23:11 -0700126 sortedOlsonIds.addAll(offsets.keySet());
Elliott Hughes5b1497a2012-10-19 14:47:37 -0700127 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
Elliott Hughes371dcc12014-11-11 14:10:51 -0800135 // 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
Elliott Hughes5b1497a2012-10-19 14:47:37 -0700141 f.write(toAscii(new byte[MAXNAME], zoneName));
Elliott Hughes371dcc12014-11-11 14:10:51 -0800142 f.writeInt(offsets.get(actualZoneName));
143 f.writeInt(lengths.get(actualZoneName));
Elliott Hughes90cb5ff2014-08-06 15:23:11 -0700144 f.writeInt(0); // Used to be raw GMT offset. No longer used.
Elliott Hughes5b1497a2012-10-19 14:47:37 -0700145 }
146
147 int data_offset = (int) f.getFilePointer();
148
149 // Write the data.
150 f.write(allData.toByteArray());
151
Elliott Hughesaf7f2f22013-03-14 17:10:24 -0700152 int zonetab_offset = (int) f.getFilePointer();
153
Elliott Hughes23935352012-10-22 14:47:58 -0700154 // 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
Elliott Hughes5b1497a2012-10-19 14:47:37 -0700164 // 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 {
Elliott Hughes23935352012-10-22 14:47:58 -0700186 if (args.length != 5) {
187 System.err.println("usage: java ZoneCompactor <setup file> <data directory> <zone.tab file> <output directory> <tzdata version>");
Elliott Hughes5b1497a2012-10-19 14:47:37 -0700188 System.exit(0);
189 }
Elliott Hughes23935352012-10-22 14:47:58 -0700190 new ZoneCompactor(args[0], args[1], args[2], args[3], args[4]);
Elliott Hughes5b1497a2012-10-19 14:47:37 -0700191 }
Elliott Hughesd40e63e2011-02-17 16:20:07 -0800192}