blob: 5cc076a59554f21778d240c0cfeee68305bcd1d5 [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
Neil Fuller61b7d562020-05-21 14:43:47 +010020// usage: java ZoneCompactor <setup file> <data directory> <output directory> <tzdata version>
Neil Fuller86e72c52017-06-12 15:36:06 +010021//
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.
Neil Fuller4da0af12020-05-19 14:56:15 +010046 private Map<String,String> links = new HashMap<>();
Neil Fuller86e72c52017-06-12 15:36:06 +010047
48 // File offsets by zone name.
Neil Fuller4da0af12020-05-19 14:56:15 +010049 private Map<String,Integer> offsets = new HashMap<>();
Neil Fuller86e72c52017-06-12 15:36:06 +010050
51 // File lengths by zone name.
Neil Fuller4da0af12020-05-19 14:56:15 +010052 private Map<String,Integer> lengths = new HashMap<>();
Neil Fuller86e72c52017-06-12 15:36:06 +010053
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
Neil Fuller61b7d562020-05-21 14:43:47 +010075 public ZoneCompactor(String setupFile, String dataDirectory, String outputDirectory,
76 String version) throws Exception {
Neil Fuller86e72c52017-06-12 15:36:06 +010077 // Read the setup file and concatenate all the data.
78 ByteArrayOutputStream allData = new ByteArrayOutputStream();
79 BufferedReader reader = new BufferedReader(new FileReader(setupFile));
80 String s;
81 int offset = 0;
82 while ((s = reader.readLine()) != null) {
83 s = s.trim();
Neil Fuller4da0af12020-05-19 14:56:15 +010084 StringTokenizer st = new StringTokenizer(s);
85 String lineType = st.nextToken();
86 if (lineType.startsWith("Link")) {
Neil Fuller86e72c52017-06-12 15:36:06 +010087 String to = st.nextToken();
88 String from = st.nextToken();
89 links.put(from, to);
Neil Fuller4da0af12020-05-19 14:56:15 +010090 } else if (lineType.startsWith("Zone")) {
91 String zoneId = st.nextToken();
92 String link = links.get(zoneId);
Neil Fuller86e72c52017-06-12 15:36:06 +010093 if (link == null) {
Neil Fuller4da0af12020-05-19 14:56:15 +010094 File sourceFile = new File(dataDirectory, zoneId);
Neil Fuller86e72c52017-06-12 15:36:06 +010095 long length = sourceFile.length();
Neil Fuller4da0af12020-05-19 14:56:15 +010096 offsets.put(zoneId, offset);
97 lengths.put(zoneId, (int) length);
Neil Fuller86e72c52017-06-12 15:36:06 +010098
99 offset += length;
100 copyFile(sourceFile, allData);
101 }
102 }
103 }
104 reader.close();
105
106 // Fill in fields for links.
Neil Fuller4da0af12020-05-19 14:56:15 +0100107 for (String from : links.keySet()) {
Neil Fuller86e72c52017-06-12 15:36:06 +0100108 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
Neil Fuller4da0af12020-05-19 14:56:15 +0100123 // int final_offset
Neil Fuller86e72c52017-06-12 15:36:06 +0100124
125 // tzdata_version
126 f.write(toAscii(new byte[12], version));
127
Victor Changfdc53992020-07-27 15:45:23 +0100128 // Write placeholder values for the offsets, and remember where we need to seek back to later
Neil Fuller86e72c52017-06-12 15:36:06 +0100129 // 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);
Neil Fuller4da0af12020-05-19 14:56:15 +0100134 // The final offset serves as a placeholder for sections that might be added in future and
135 // ensures we know the size of the final "real" section. Relying on the last section ending at
136 // EOF would make it harder to append sections to the end of the file in a backward compatible
137 // way.
138 int final_offset_offset = (int) f.getFilePointer();
139 f.writeInt(0);
Neil Fuller86e72c52017-06-12 15:36:06 +0100140
141 int index_offset = (int) f.getFilePointer();
142
143 // Write the index.
144 ArrayList<String> sortedOlsonIds = new ArrayList<String>();
145 sortedOlsonIds.addAll(offsets.keySet());
146 Collections.sort(sortedOlsonIds);
Neil Fuller4da0af12020-05-19 14:56:15 +0100147 for (String zoneName : sortedOlsonIds) {
Neil Fuller86e72c52017-06-12 15:36:06 +0100148 if (zoneName.length() >= MAXNAME) {
149 throw new RuntimeException("zone filename too long: " + zoneName.length());
150 }
151
152 // Follow the chain of links to work out where the real data for this zone lives.
153 String actualZoneName = zoneName;
154 while (links.get(actualZoneName) != null) {
155 actualZoneName = links.get(actualZoneName);
156 }
157
158 f.write(toAscii(new byte[MAXNAME], zoneName));
159 f.writeInt(offsets.get(actualZoneName));
160 f.writeInt(lengths.get(actualZoneName));
161 f.writeInt(0); // Used to be raw GMT offset. No longer used.
162 }
163
164 int data_offset = (int) f.getFilePointer();
165
166 // Write the data.
167 f.write(allData.toByteArray());
168
Neil Fuller4da0af12020-05-19 14:56:15 +0100169 int final_offset = (int) f.getFilePointer();
170
Neil Fuller86e72c52017-06-12 15:36:06 +0100171 // Go back and fix up the offsets in the header.
172 f.seek(index_offset_offset);
173 f.writeInt(index_offset);
174 f.seek(data_offset_offset);
175 f.writeInt(data_offset);
Neil Fuller4da0af12020-05-19 14:56:15 +0100176 f.seek(final_offset_offset);
177 f.writeInt(final_offset);
Neil Fuller86e72c52017-06-12 15:36:06 +0100178
179 f.close();
180 }
181
182 private static byte[] toAscii(byte[] dst, String src) {
183 for (int i = 0; i < src.length(); ++i) {
184 if (src.charAt(i) > '~') {
185 throw new RuntimeException("non-ASCII string: " + src);
186 }
187 dst[i] = (byte) src.charAt(i);
188 }
189 return dst;
190 }
191
192 public static void main(String[] args) throws Exception {
Neil Fuller61b7d562020-05-21 14:43:47 +0100193 if (args.length != 4) {
194 System.err.println("usage: java ZoneCompactor <setup file> <data directory>"
195 + " <output directory> <tzdata version>");
196 System.exit(1);
Neil Fuller86e72c52017-06-12 15:36:06 +0100197 }
Neil Fuller61b7d562020-05-21 14:43:47 +0100198 new ZoneCompactor(args[0], args[1], args[2], args[3]);
Neil Fuller86e72c52017-06-12 15:36:06 +0100199 }
200}