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