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