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 srcDir = 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 ("-srcdir".equals(arg)) { 108 if (srcDir == null && ++i < args.length) { 109 srcDir = 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 (srcDir == null) { 130 System.err.println("Source directory must be specified using -srcdir"); 131 System.exit(1); 132 } 133 if (!Files.isDirectory(srcDir)) { 134 System.err.println("Source does not exist or is not a directory: " + srcDir); 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 // source files in this directory 149 List<Path> srcFiles = new ArrayList<>(); 150 for (; i < args.length; i++) { 151 Path file = srcDir.resolve(args[i]); 152 if (Files.exists(file)) { 153 srcFiles.add(file); 154 } else { 155 System.err.println("Source directory does not contain source file: " + args[i]); 156 System.exit(1); 157 } 158 } 159 // check destination file 160 if (dstFile == null) { 161 dstFile = srcDir.resolve("tzdb.dat"); 162 } else { 163 Path parent = dstFile.getParent(); 164 if (parent != null && !Files.exists(parent)) { 165 System.err.println("Destination directory does not exist: " + parent); 166 System.exit(1); 167 } 168 } 169 try { 170 // get tzdb source version 171 Matcher m = Pattern.compile("tzdata(?<ver>[0-9]{4}[A-z])") 172 .matcher(new String(Files.readAllBytes(srcDir.resolve("VERSION")), 173 "ISO-8859-1")); 174 if (m.find()) { 175 version = m.group("ver"); 176 } else { 177 System.exit(1); 178 System.err.println("Source directory does not contain file: VERSION"); 179 } 180 181 // load source files 182 printVerbose("Compiling TZDB version " + version); 183 TzdbZoneRulesProvider provider = new TzdbZoneRulesProvider(srcFiles); 184 185 // build zone rules 186 printVerbose("Building rules"); 187 188 // Build the rules, zones and links into real zones. 189 SortedMap<String, ZoneRules> builtZones = new TreeMap<>(); 190 191 // build zones 192 for (String zoneId : provider.getZoneIds()) { 193 printVerbose("Building zone " + zoneId); 194 builtZones.put(zoneId, provider.getZoneRules(zoneId)); 195 } 196 197 // build aliases 198 Map<String, String> links = provider.getAliasMap(); 199 for (String aliasId : links.keySet()) { 200 String realId = links.get(aliasId); 201 printVerbose("Linking alias " + aliasId + " to " + realId); 202 ZoneRules realRules = builtZones.get(realId); 203 if (realRules == null) { 204 realId = links.get(realId); // try again (handle alias liked to alias) 205 printVerbose("Relinking alias " + aliasId + " to " + realId); 206 realRules = builtZones.get(realId); 207 if (realRules == null) { 208 throw new IllegalArgumentException("Alias '" + aliasId + "' links to invalid zone '" + realId); 209 } 210 links.put(aliasId, realId); 211 } 212 builtZones.put(aliasId, realRules); 213 } 214 215 // output to file 216 printVerbose("Outputting tzdb file: " + dstFile); 217 outputFile(dstFile, version, builtZones, links); 218 } catch (Exception ex) { 219 System.out.println("Failed: " + ex.toString()); 220 ex.printStackTrace(); 221 System.exit(1); 222 } 223 System.exit(0); 224 } 225 226 /** 227 * Output usage text for the command line. 228 */ 229 private static void outputHelp() { 230 System.out.println("Usage: TzdbZoneRulesCompiler <options> <tzdb source filenames>"); 231 System.out.println("where options include:"); 232 System.out.println(" -srcdir <directory> Where to find tzdb source directory (required)"); 233 System.out.println(" -dstfile <file> Where to output generated file (default srcdir/tzdb.dat)"); 234 System.out.println(" -help Print this usage message"); 235 System.out.println(" -verbose Output verbose information during compilation"); 236 System.out.println(" The source directory must contain the unpacked tzdb files, such as asia or europe"); 237 } 238 239 /** 240 * Outputs the file. 241 */ 242 private void outputFile(Path dstFile, String version, 243 SortedMap<String, ZoneRules> builtZones, 244 Map<String, String> links) { 245 try (DataOutputStream out = new DataOutputStream(Files.newOutputStream(dstFile))) { 246 // file version 247 out.writeByte(1); 248 // group 249 out.writeUTF("TZDB"); 250 // versions 251 out.writeShort(1); 252 out.writeUTF(version); 253 // regions 254 String[] regionArray = builtZones.keySet().toArray(new String[builtZones.size()]); 255 out.writeShort(regionArray.length); 256 for (String regionId : regionArray) { 257 out.writeUTF(regionId); 258 } 259 // rules -- hashset -> remove the dup 260 List<ZoneRules> rulesList = new ArrayList<>(new HashSet<>(builtZones.values())); 261 out.writeShort(rulesList.size()); 262 ByteArrayOutputStream baos = new ByteArrayOutputStream(1024); 263 for (ZoneRules rules : rulesList) { 264 baos.reset(); 265 DataOutputStream dataos = new DataOutputStream(baos); 266 rules.writeExternal(dataos); 267 dataos.close(); 268 byte[] bytes = baos.toByteArray(); 269 out.writeShort(bytes.length); 270 out.write(bytes); 271 } 272 // link version-region-rules 273 out.writeShort(builtZones.size()); 274 for (Map.Entry<String, ZoneRules> entry : builtZones.entrySet()) { 275 int regionIndex = Arrays.binarySearch(regionArray, entry.getKey()); 276 int rulesIndex = rulesList.indexOf(entry.getValue()); 277 out.writeShort(regionIndex); 278 out.writeShort(rulesIndex); 279 } 280 // alias-region 281 out.writeShort(links.size()); 282 for (Map.Entry<String, String> entry : links.entrySet()) { 283 int aliasIndex = Arrays.binarySearch(regionArray, entry.getKey()); 284 int regionIndex = Arrays.binarySearch(regionArray, entry.getValue()); 285 out.writeShort(aliasIndex); 286 out.writeShort(regionIndex); 287 } 288 out.flush(); 289 } catch (Exception ex) { 290 System.out.println("Failed: " + ex.toString()); 291 ex.printStackTrace(); 292 System.exit(1); 293 } 294 } 295 296 /** Whether to output verbose messages. */ 297 private boolean verbose; 298 299 /** 300 * private contructor 301 */ 302 private TzdbZoneRulesCompiler() {} 303 304 /** 305 * Prints a verbose message. 306 * 307 * @param message the message, not null 308 */ 309 private void printVerbose(String message) { 310 if (verbose) { 311 System.out.println(message); 312 } 313 } 314 }