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