1 /* 2 * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 /* 27 * Copyright (c) 2009-2012, Stephen Colebourne & Michael Nascimento Santos 28 * 29 * All rights reserved. 30 * 31 * Redistribution and use in source and binary forms, with or without 32 * modification, are permitted provided that the following conditions are met: 33 * 34 * * Redistributions of source code must retain the above copyright notice, 35 * this list of conditions and the following disclaimer. 36 * 37 * * Redistributions in binary form must reproduce the above copyright notice, 38 * this list of conditions and the following disclaimer in the documentation 39 * and/or other materials provided with the distribution. 40 * 41 * * Neither the name of JSR-310 nor the names of its contributors 42 * may be used to endorse or promote products derived from this software 43 * without specific prior written permission. 44 * 45 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 46 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 47 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 48 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 49 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 50 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 51 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 52 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 53 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 54 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 55 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 56 */ 57 package build.tools.tzdb; 58 59 import java.io.ByteArrayOutputStream; 60 import java.io.DataOutputStream; 61 import java.nio.charset.StandardCharsets; 62 import java.nio.file.Files; 63 import java.nio.file.Path; 64 import java.nio.file.Paths; 65 import java.text.ParsePosition; 66 import java.util.ArrayList; 67 import java.util.Arrays; 68 import java.util.HashMap; 69 import java.util.HashSet; 70 import java.util.List; 71 import java.util.Map; 72 import java.util.NoSuchElementException; 73 import java.util.Scanner; 74 import java.util.SortedMap; 75 import java.util.TreeMap; 76 import java.util.regex.Matcher; 77 import java.util.regex.MatchResult; 78 import java.util.regex.Pattern; 79 80 /** 81 * A compiler that reads a set of TZDB time-zone files and builds a single 82 * combined TZDB data file. 83 * 84 * @since 1.8 85 */ 86 public final class TzdbZoneRulesCompiler { 87 88 public static void main(String[] args) { 89 new TzdbZoneRulesCompiler().compile(args); 90 } 91 92 private void compile(String[] args) { 93 if (args.length < 2) { 94 outputHelp(); 95 return; 96 } 97 Path srcFile = null; 98 Path dstFile = null; 99 String version = null; 100 // parse args/options 101 int i; 102 for (i = 0; i < args.length; i++) { 103 String arg = args[i]; 104 if (!arg.startsWith("-")) { 105 break; 106 } 107 if ("-srcfile".equals(arg)) { 108 if (srcFile == null && ++i < args.length) { 109 srcFile = Paths.get(args[i]); 110 continue; 111 } 112 } else if ("-dstfile".equals(arg)) { 113 if (dstFile == null && ++i < args.length) { 114 dstFile = Paths.get(args[i]); 115 continue; 116 } 117 } else if ("-verbose".equals(arg)) { 118 if (!verbose) { 119 verbose = true; 120 continue; 121 } 122 } else if (!"-help".equals(arg)) { 123 System.out.println("Unrecognised option: " + arg); 124 } 125 outputHelp(); 126 return; 127 } 128 // check source directory 129 if (srcFile == null) { 130 System.err.println("Source file must be specified using -srcdir"); 131 System.exit(1); 132 } 133 if (!Files.isRegularFile(srcFile)) { 134 System.err.println("Source does not exist or is not a file: " + srcFile); 135 System.exit(1); 136 } 137 // parse source file names 138 if (i == args.length) { 139 i = 0; 140 args = new String[] {"africa", "antarctica", "asia", "australasia", "europe", 141 "northamerica","southamerica", "backward", "etcetera" }; 142 System.out.println("Source filenames not specified, using default set ( "); 143 for (String name : args) { 144 System.out.printf(name + " "); 145 } 146 System.out.println(")"); 147 } 148 149 // source files list in the .tar.gz source file 150 List<String> srcFiles = new ArrayList<>(); 151 for (; i < args.length; i++) { 152 srcFiles.add(args[i]); 153 //System.err.println("Source directory does not contain source file: " + args[i]); 154 } 155 156 // check destination file 157 if (dstFile == null) { 158 dstFile = srcFile.resolveSibling("tzdb.dat"); 159 } else { 160 Path parent = dstFile.getParent(); 161 if (parent != null && !Files.exists(parent)) { 162 System.err.println("Destination directory does not exist: " + parent); 163 System.exit(1); 164 } 165 } 166 167 try { 168 // get tzdb source version 169 Matcher m = Pattern.compile("tzdata(?<ver>[0-9]{4}[A-z])") 170 .matcher(srcFile.getFileName().toString()); 171 if (m.find()) { 172 version = m.group("ver"); 173 } else { 174 System.exit(1); 175 System.err.println("Source file name does not contain correct version info: tzdata[0-9]{4}[A-z]"); 176 } 177 178 // load source files 179 printVerbose("Compiling TZDB version " + version); 180 TzdbZoneRulesProvider provider = 181 new TzdbZoneRulesProvider(srcFile.toFile(), srcFiles); 182 183 // build zone rules 184 printVerbose("Building rules"); 185 186 // Build the rules, zones and links into real zones. 187 SortedMap<String, ZoneRules> builtZones = new TreeMap<>(); 188 189 // build zones 190 for (String zoneId : provider.getZoneIds()) { 191 printVerbose("Building zone " + zoneId); 192 builtZones.put(zoneId, provider.getZoneRules(zoneId)); 193 } 194 195 // build aliases 196 Map<String, String> links = provider.getAliasMap(); 197 for (String aliasId : links.keySet()) { 198 String realId = links.get(aliasId); 199 printVerbose("Linking alias " + aliasId + " to " + realId); 200 ZoneRules realRules = builtZones.get(realId); 201 if (realRules == null) { 202 realId = links.get(realId); // try again (handle alias liked to alias) 203 printVerbose("Relinking alias " + aliasId + " to " + realId); 204 realRules = builtZones.get(realId); 205 if (realRules == null) { 206 throw new IllegalArgumentException("Alias '" + aliasId + "' links to invalid zone '" + realId); 207 } 208 links.put(aliasId, realId); 209 } 210 builtZones.put(aliasId, realRules); 211 } 212 213 // output to file 214 printVerbose("Outputting tzdb file: " + dstFile); 215 outputFile(dstFile, version, builtZones, links); 216 } catch (Exception ex) { 217 System.out.println("Failed: " + ex.toString()); 218 ex.printStackTrace(); 219 System.exit(1); 220 } 221 System.exit(0); 222 } 223 224 /** 225 * Output usage text for the command line. 226 */ 227 private static void outputHelp() { 228 System.out.println("Usage: TzdbZoneRulesCompiler <options> <tzdb source filenames>"); 229 System.out.println("where options include:"); 230 System.out.println(" -srcfile <file> The tzdb source file tzdata<Ver>.tar.gz (required)"); 231 System.out.println(" -dstfile <file> Where to output generated file (default srcdir/tzdb.dat)"); 232 System.out.println(" -help Print this usage message"); 233 System.out.println(" -verbose Output verbose information during compilation"); 234 System.out.println(" The source file must contain the required tzdb files, such as asia or europe"); 235 } 236 237 /** 238 * Outputs the file. 239 */ 240 private void outputFile(Path dstFile, String version, 241 SortedMap<String, ZoneRules> builtZones, 242 Map<String, String> links) { 243 try (DataOutputStream out = new DataOutputStream(Files.newOutputStream(dstFile))) { 244 // file version 245 out.writeByte(1); 246 // group 247 out.writeUTF("TZDB"); 248 // versions 249 out.writeShort(1); 250 out.writeUTF(version); 251 // regions 252 String[] regionArray = builtZones.keySet().toArray(new String[builtZones.size()]); 253 out.writeShort(regionArray.length); 254 for (String regionId : regionArray) { 255 out.writeUTF(regionId); 256 } 257 // rules -- hashset -> remove the dup 258 List<ZoneRules> rulesList = new ArrayList<>(new HashSet<>(builtZones.values())); 259 out.writeShort(rulesList.size()); 260 ByteArrayOutputStream baos = new ByteArrayOutputStream(1024); 261 for (ZoneRules rules : rulesList) { 262 baos.reset(); 263 DataOutputStream dataos = new DataOutputStream(baos); 264 rules.writeExternal(dataos); 265 dataos.close(); 266 byte[] bytes = baos.toByteArray(); 267 out.writeShort(bytes.length); 268 out.write(bytes); 269 } 270 // link version-region-rules 271 out.writeShort(builtZones.size()); 272 for (Map.Entry<String, ZoneRules> entry : builtZones.entrySet()) { 273 int regionIndex = Arrays.binarySearch(regionArray, entry.getKey()); 274 int rulesIndex = rulesList.indexOf(entry.getValue()); 275 out.writeShort(regionIndex); 276 out.writeShort(rulesIndex); 277 } 278 // alias-region 279 out.writeShort(links.size()); 280 for (Map.Entry<String, String> entry : links.entrySet()) { 281 int aliasIndex = Arrays.binarySearch(regionArray, entry.getKey()); 282 int regionIndex = Arrays.binarySearch(regionArray, entry.getValue()); 283 out.writeShort(aliasIndex); 284 out.writeShort(regionIndex); 285 } 286 out.flush(); 287 } catch (Exception ex) { 288 System.out.println("Failed: " + ex.toString()); 289 ex.printStackTrace(); 290 System.exit(1); 291 } 292 } 293 294 /** Whether to output verbose messages. */ 295 private boolean verbose; 296 297 /** 298 * private contructor 299 */ 300 private TzdbZoneRulesCompiler() {} 301 302 /** 303 * Prints a verbose message. 304 * 305 * @param message the message, not null 306 */ 307 private void printVerbose(String message) { 308 if (verbose) { 309 System.out.println(message); 310 } 311 } 312 }