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