• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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  */
16 
17 import java.io.*;
18 import 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 
41 public 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'.
copyFile(File inFile, OutputStream 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 
ZoneCompactor(String setupFile, String dataDirectory, String zoneTabFile, String outputDirectory, String version)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 
toAscii(byte[] dst, String src)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 
main(String[] args)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 }
208