• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 
2 import java.io.*;
3 import java.nio.ByteOrder;
4 import java.util.*;
5 import libcore.io.BufferIterator;
6 import libcore.util.ZoneInfo;
7 
8 // usage: java ZoneCompiler <setup file> <data directory> <output directory> <tzdata version>
9 //
10 // Compile a set of tzfile-formatted files into a single file containing an index.
11 //
12 // The compilation is controlled by a setup file, which is provided as a
13 // command-line argument.  The setup file has the form:
14 //
15 // Link <toName> <fromName>
16 // ...
17 // <zone filename>
18 // ...
19 //
20 // Note that the links must be declared prior to the zone names.
21 // A zone name is a filename relative to the source directory such as
22 // 'GMT', 'Africa/Dakar', or 'America/Argentina/Jujuy'.
23 //
24 // Use the 'zic' command-line tool to convert from flat files
25 // (such as 'africa' or 'northamerica') to a directory
26 // hierarchy suitable for this tool (containing files such as 'data/Africa/Abidjan').
27 //
28 
29 public class ZoneCompactor {
30   public static class ByteArrayBufferIteratorBE extends BufferIterator {
31     private final byte[] bytes;
32     private int offset = 0;
33 
ByteArrayBufferIteratorBE(byte[] bytes)34     public ByteArrayBufferIteratorBE(byte[] bytes) {
35       this.bytes = bytes;
36       this.offset = 0;
37     }
38 
seek(int offset)39     public void seek(int offset) {
40       this.offset = offset;
41     }
42 
skip(int byteCount)43     public void skip(int byteCount) {
44       this.offset += byteCount;
45     }
46 
readByteArray(byte[] dst, int dstOffset, int byteCount)47     public void readByteArray(byte[] dst, int dstOffset, int byteCount) {
48       System.arraycopy(bytes, offset, dst, dstOffset, byteCount);
49       offset += byteCount;
50     }
51 
readByte()52     public byte readByte() {
53       return bytes[offset++];
54     }
55 
readInt()56     public int readInt() {
57       return ((readByte() & 0xff) << 24) | ((readByte() & 0xff) << 16) | ((readByte() & 0xff) << 8) | (readByte() & 0xff);
58     }
59 
readIntArray(int[] dst, int dstOffset, int intCount)60     public void readIntArray(int[] dst, int dstOffset, int intCount) {
61       for (int i = 0; i < intCount; ++i) {
62         dst[dstOffset++] = readInt();
63       }
64     }
65 
readShort()66     public short readShort() {
67       throw new UnsupportedOperationException();
68     }
69   }
70 
71   // Maximum number of characters in a zone name, including '\0' terminator
72   private static final int MAXNAME = 40;
73 
74   // Zone name synonyms
75   private Map<String,String> links = new HashMap<String,String>();
76 
77   // File starting bytes by zone name
78   private Map<String,Integer> starts = new HashMap<String,Integer>();
79 
80   // File lengths by zone name
81   private Map<String,Integer> lengths = new HashMap<String,Integer>();
82 
83   // Raw GMT offsets by zone name
84   private Map<String,Integer> offsets = new HashMap<String,Integer>();
85   private int start = 0;
86 
87   // Concatenate the contents of 'inFile' onto 'out'
88   // and return the contents as a byte array.
copyFile(File inFile, OutputStream out)89   private static byte[] copyFile(File inFile, OutputStream out) throws Exception {
90     byte[] ret = new byte[0];
91 
92     InputStream in = new FileInputStream(inFile);
93     byte[] buf = new byte[8192];
94     while (true) {
95       int nbytes = in.read(buf);
96       if (nbytes == -1) {
97         break;
98       }
99       out.write(buf, 0, nbytes);
100 
101       byte[] nret = new byte[ret.length + nbytes];
102       System.arraycopy(ret, 0, nret, 0, ret.length);
103       System.arraycopy(buf, 0, nret, ret.length, nbytes);
104       ret = nret;
105     }
106     out.flush();
107     return ret;
108   }
109 
ZoneCompactor(String setupFile, String dataDirectory, String zoneTabFile, String outputDirectory, String version)110   public ZoneCompactor(String setupFile, String dataDirectory, String zoneTabFile, String outputDirectory, String version) throws Exception {
111     // Read the setup file, and concatenate all the data.
112     ByteArrayOutputStream allData = new ByteArrayOutputStream();
113     BufferedReader reader = new BufferedReader(new FileReader(setupFile));
114     String s;
115     while ((s = reader.readLine()) != null) {
116       s = s.trim();
117       if (s.startsWith("Link")) {
118         StringTokenizer st = new StringTokenizer(s);
119         st.nextToken();
120         String to = st.nextToken();
121         String from = st.nextToken();
122         links.put(from, to);
123       } else {
124         String link = links.get(s);
125         if (link == null) {
126           File sourceFile = new File(dataDirectory, s);
127           long length = sourceFile.length();
128           starts.put(s, start);
129           lengths.put(s, (int) length);
130 
131           start += length;
132           byte[] data = copyFile(sourceFile, allData);
133 
134           BufferIterator it = new ByteArrayBufferIteratorBE(data);
135           TimeZone tz = ZoneInfo.makeTimeZone(s, it);
136           int gmtOffset = tz.getRawOffset();
137           offsets.put(s, gmtOffset);
138         }
139       }
140     }
141     reader.close();
142 
143     // Fill in fields for links.
144     Iterator<String> it = links.keySet().iterator();
145     while (it.hasNext()) {
146       String from = it.next();
147       String to = links.get(from);
148 
149       starts.put(from, starts.get(to));
150       lengths.put(from, lengths.get(to));
151       offsets.put(from, offsets.get(to));
152     }
153 
154     // Create/truncate the destination file.
155     RandomAccessFile f = new RandomAccessFile(new File(outputDirectory, "tzdata"), "rw");
156     f.setLength(0);
157 
158     // Write the header.
159 
160     // byte[12] tzdata_version -- 'tzdata2012f\0'
161     // int index_offset -- so we can slip in extra header fields in a backwards-compatible way
162     // int data_offset
163     // int zonetab_offset
164 
165     // tzdata_version
166     f.write(toAscii(new byte[12], version));
167 
168     // Write dummy values for the three offsets, and remember where we need to seek back to later
169     // when we have the real values.
170     int index_offset_offset = (int) f.getFilePointer();
171     f.writeInt(0);
172     int data_offset_offset = (int) f.getFilePointer();
173     f.writeInt(0);
174     int zonetab_offset_offset = (int) f.getFilePointer();
175     f.writeInt(0);
176 
177     int index_offset = (int) f.getFilePointer();
178 
179     // Write the index.
180     ArrayList<String> sortedOlsonIds = new ArrayList<String>();
181     sortedOlsonIds.addAll(starts.keySet());
182     Collections.sort(sortedOlsonIds);
183     it = sortedOlsonIds.iterator();
184     while (it.hasNext()) {
185       String zoneName = it.next();
186       if (zoneName.length() >= MAXNAME) {
187         throw new RuntimeException("zone filename too long: " + zoneName.length());
188       }
189 
190       f.write(toAscii(new byte[MAXNAME], zoneName));
191       f.writeInt(starts.get(zoneName));
192       f.writeInt(lengths.get(zoneName));
193       f.writeInt(offsets.get(zoneName));
194     }
195 
196     int data_offset = (int) f.getFilePointer();
197 
198     // Write the data.
199     f.write(allData.toByteArray());
200 
201     int zonetab_offset = (int) f.getFilePointer();
202 
203     // Copy the zone.tab.
204     reader = new BufferedReader(new FileReader(zoneTabFile));
205     while ((s = reader.readLine()) != null) {
206       if (!s.startsWith("#")) {
207         f.writeBytes(s);
208         f.write('\n');
209       }
210     }
211     reader.close();
212 
213     // Go back and fix up the offsets in the header.
214     f.seek(index_offset_offset);
215     f.writeInt(index_offset);
216     f.seek(data_offset_offset);
217     f.writeInt(data_offset);
218     f.seek(zonetab_offset_offset);
219     f.writeInt(zonetab_offset);
220 
221     f.close();
222   }
223 
toAscii(byte[] dst, String src)224   private static byte[] toAscii(byte[] dst, String src) {
225     for (int i = 0; i < src.length(); ++i) {
226       if (src.charAt(i) > '~') {
227         throw new RuntimeException("non-ASCII string: " + src);
228       }
229       dst[i] = (byte) src.charAt(i);
230     }
231     return dst;
232   }
233 
main(String[] args)234   public static void main(String[] args) throws Exception {
235     if (args.length != 5) {
236       System.err.println("usage: java ZoneCompactor <setup file> <data directory> <zone.tab file> <output directory> <tzdata version>");
237       System.exit(0);
238     }
239     new ZoneCompactor(args[0], args[1], args[2], args[3], args[4]);
240   }
241 }
242