1 /*
   2  * Copyright (c) 2000, 2011, 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 package build.tools.javazic;
  27 
  28 import java.io.BufferedReader;
  29 import java.io.FileReader;
  30 import java.io.FileNotFoundException;
  31 import java.io.IOException;
  32 import java.util.HashMap;
  33 import java.util.List;
  34 import java.util.Map;
  35 import java.util.StringTokenizer;
  36 
  37 /**
  38  * Zoneinfo provides javazic compiler front-end functionality.
  39  * @since 1.4
  40  */
  41 class Zoneinfo {
  42 
  43     private static final int minYear = 1900;
  44     private static final int maxYear = 2037;
  45     private static final long minTime = Time.getLocalTime(minYear, Month.JANUARY, 1, 0);
  46     private static int startYear = minYear;
  47     private static int endYear = maxYear;
  48 
  49     /**
  50      * True if javazic should generate a list of SimpleTimeZone
  51      * instances for the SimpleTimeZone-based time zone support.
  52      */
  53     static boolean isYearForTimeZoneDataSpecified = false;
  54 
  55     /**
  56      * Zone name to Zone mappings
  57      */
  58     private Map<String,Zone> zones;
  59 
  60     /**
  61      * Rule name to Rule mappings
  62      */
  63     private Map<String,Rule> rules;
  64 
  65     /**
  66      * Alias name to real name mappings
  67      */
  68     private Map<String,String> aliases;
  69 
  70     /**
  71      * Constracts a Zoneinfo.
  72      */
  73     Zoneinfo() {
  74         zones = new HashMap<String,Zone>();
  75         rules = new HashMap<String,Rule>();
  76         aliases = new HashMap<String,String>();
  77     }
  78 
  79     /**
  80      * Adds the given zone to the list of Zones.
  81      * @param zone Zone to be added to the list.
  82      */
  83     void add(Zone zone) {
  84         String name = zone.getName();
  85         zones.put(name, zone);
  86     }
  87 
  88     /**
  89      * Adds the given rule to the list of Rules.
  90      * @param rule Rule to be added to the list.
  91      */
  92     void add(Rule rule) {
  93         String name = rule.getName();
  94         rules.put(name, rule);
  95     }
  96 
  97     /**
  98      * Puts the specifid name pair to the alias table.
  99      * @param name1 an alias time zone name
 100      * @param name2 the real time zone of the alias name
 101      */
 102     void putAlias(String name1, String name2) {
 103         aliases.put(name1, name2);
 104     }
 105 
 106     /**
 107      * Sets the given year for SimpleTimeZone list output.
 108      * This method is called when the -S option is specified.
 109      * @param year the year for which SimpleTimeZone list should be generated
 110      */
 111     static void setYear(int year) {
 112         setStartYear(year);
 113         setEndYear(year);
 114         isYearForTimeZoneDataSpecified = true;
 115     }
 116 
 117     /**
 118      * Sets the start year.
 119      * @param year the start year value
 120      * @throws IllegalArgumentException if the specified year value is
 121      * smaller than the minimum year or greater than the end year.
 122      */
 123     static void setStartYear(int year) {
 124         if (year < minYear || year > endYear) {
 125             throw new IllegalArgumentException("invalid start year specified: " + year);
 126         }
 127         startYear = year;
 128     }
 129 
 130     /**
 131      * @return the start year value
 132      */
 133     static int getStartYear() {
 134         return startYear;
 135     }
 136 
 137     /**
 138      * Sets the end year.
 139      * @param year the end year value
 140      * @throws IllegalArgumentException if the specified year value is
 141      * smaller than the start year or greater than the maximum year.
 142      */
 143     static void setEndYear(int year) {
 144         if (year < startYear || year > maxYear) {
 145             throw new IllegalArgumentException();
 146         }
 147         endYear = year;
 148     }
 149 
 150     /**
 151      * @return the end year value
 152      */
 153     static int getEndYear() {
 154         return endYear;
 155     }
 156 
 157     /**
 158      * @return the minimum year value
 159      */
 160     static int getMinYear() {
 161         return minYear;
 162     }
 163 
 164     /**
 165      * @return the maximum year value
 166      */
 167     static int getMaxYear() {
 168         return maxYear;
 169     }
 170 
 171     /**
 172      * @return the alias table
 173      */
 174     Map<String,String> getAliases() {
 175         return aliases;
 176     }
 177 
 178     /**
 179      * @return the Zone list
 180      */
 181     Map<String,Zone> getZones() {
 182         return zones;
 183     }
 184 
 185     /**
 186      * @return a Zone specified by name.
 187      * @param name a zone name
 188      */
 189     Zone getZone(String name) {
 190         return zones.get(name);
 191     }
 192 
 193     /**
 194      * @return a Rule specified by name.
 195      * @param name a rule name
 196      */
 197     Rule getRule(String name) {
 198         return rules.get(name);
 199     }
 200 
 201     private static String line;
 202 
 203     private static int lineNum;
 204 
 205     /**
 206      * Parses the specified time zone data file and creates a Zoneinfo
 207      * that has all Rules, Zones and Links (aliases) information.
 208      * @param fname the time zone data file name
 209      * @return a Zoneinfo object
 210      */
 211     static Zoneinfo parse(String fname) {
 212         BufferedReader in = null;
 213         try {
 214             FileReader fr = new FileReader(fname);
 215             in = new BufferedReader(fr);
 216         } catch (FileNotFoundException e) {
 217             panic("can't open file: "+fname);
 218         }
 219         Zoneinfo zi = new Zoneinfo();
 220         boolean continued = false;
 221         Zone zone = null;
 222         String l;
 223         lineNum = 0;
 224 
 225         try {
 226             while ((line = in.readLine()) != null) {
 227                 lineNum++;
 228                 // skip blank and comment lines
 229                 if (line.length() == 0 || line.charAt(0) == '#') {
 230                     continue;
 231                 }
 232 
 233                 // trim trailing comments
 234                 int rindex = line.lastIndexOf('#');
 235                 if (rindex != -1) {
 236                     // take the data part of the line
 237                     l = line.substring(0, rindex);
 238                 } else {
 239                     l = line;
 240                 }
 241 
 242                 StringTokenizer tokens = new StringTokenizer(l);
 243                 if (!tokens.hasMoreTokens()) {
 244                     continue;
 245                 }
 246                 String token = tokens.nextToken();
 247 
 248                 if (continued || "Zone".equals(token)) {
 249                     if (zone == null) {
 250                         if (!tokens.hasMoreTokens()) {
 251                             panic("syntax error: zone no more token");
 252                         }
 253                         token = tokens.nextToken();
 254                         // if the zone name is in "GMT+hh" or "GMT-hh"
 255                         // format, ignore it due to spec conflict.
 256                         if (token.startsWith("GMT+") || token.startsWith("GMT-")) {
 257                             continue;
 258                         }
 259                         zone = new Zone(token);
 260                     } else {
 261                         // no way to push the current token back...
 262                         tokens = new StringTokenizer(l);
 263                     }
 264 
 265                     ZoneRec zrec = ZoneRec.parse(tokens);
 266                     zrec.setLine(line);
 267                     zone.add(zrec);
 268                     if ((continued = zrec.hasUntil()) == false) {
 269                         if (Zone.isTargetZone(zone.getName())) {
 270                             // zone.resolve(zi);
 271                             zi.add(zone);
 272                         }
 273                         zone = null;
 274                     }
 275                 } else if ("Rule".equals(token)) {
 276                     if (!tokens.hasMoreTokens()) {
 277                         panic("syntax error: rule no more token");
 278                     }
 279                     token = tokens.nextToken();
 280                     Rule rule = zi.getRule(token);
 281                     if (rule == null) {
 282                         rule = new Rule(token);
 283                         zi.add(rule);
 284                     }
 285                     RuleRec rrec = RuleRec.parse(tokens);
 286                     rrec.setLine(line);
 287                     rule.add(rrec);
 288                 } else if ("Link".equals(token)) {
 289                     // Link <newname> <oldname>
 290                     try {
 291                         String name1 = tokens.nextToken();
 292                         String name2 = tokens.nextToken();
 293 
 294                         // if the zone name is in "GMT+hh" or "GMT-hh"
 295                         // format, ignore it due to spec conflict with
 296                         // custom time zones. Also, ignore "ROC" for
 297                         // PC-ness.
 298                         if (name2.startsWith("GMT+") || name2.startsWith("GMT-")
 299                             || "ROC".equals(name2)) {
 300                             continue;
 301                         }
 302                         zi.putAlias(name2, name1);
 303                     } catch (Exception e) {
 304                         panic("syntax error: no more token for Link");
 305                     }
 306                 }
 307             }
 308             in.close();
 309         } catch (IOException ex) {
 310             panic("IO error: " + ex.getMessage());
 311         }
 312 
 313         return zi;
 314     }
 315 
 316     /**
 317      * Interprets a zone and constructs a Timezone object that
 318      * contains enough information on GMT offsets and DST schedules to
 319      * generate a zone info database.
 320      *
 321      * @param zoneName the zone name for which a Timezone object is
 322      * constructed.
 323      *
 324      * @return a Timezone object that contains all GMT offsets and DST
 325      * rules information.
 326      */
 327     Timezone phase2(String zoneName) {
 328         Timezone tz = new Timezone(zoneName);
 329         Zone zone = getZone(zoneName);
 330         zone.resolve(this);
 331 
 332         // TODO: merge phase2's for the regular and SimpleTimeZone ones.
 333         if (isYearForTimeZoneDataSpecified) {
 334             ZoneRec zrec = zone.get(zone.size()-1);
 335             tz.setLastZoneRec(zrec);
 336             tz.setRawOffset(zrec.getGmtOffset());
 337             if (zrec.hasRuleReference()) {
 338                 /*
 339                  * This part assumes that the specified year is covered by
 340                  * the rules referred to by the last zone record.
 341                  */
 342                 List<RuleRec> rrecs = zrec.getRuleRef().getRules(startYear);
 343 
 344                 if (rrecs.size() == 2) {
 345                     // make sure that one is a start rule and the other is
 346                     // an end rule.
 347                     RuleRec r0 = rrecs.get(0);
 348                     RuleRec r1 = rrecs.get(1);
 349                     if (r0.getSave() == 0 && r1.getSave() > 0) {
 350                         rrecs.set(0, r1);
 351                         rrecs.set(1, r0);
 352                     } else if (!(r0.getSave() > 0 && r1.getSave() == 0)) {
 353                         rrecs = null;
 354                         Main.error(zoneName + ": rules for " +  startYear + " not found.");
 355                     }
 356                 } else {
 357                     rrecs = null;
 358                 }
 359                 if (rrecs != null) {
 360                     tz.setLastRules(rrecs);
 361                 }
 362             }
 363             return tz;
 364         }
 365 
 366         int gmtOffset;
 367         int year = minYear;
 368         int fromYear = year;
 369         long fromTime = Time.getLocalTime(startYear,
 370                                           Month.JANUARY,
 371                                           1, 0);
 372 
 373         // take the index 0 for the GMT offset of the last zone record
 374         ZoneRec zrec = zone.get(zone.size()-1);
 375         tz.getOffsetIndex(zrec.getGmtOffset());
 376 
 377         int currentSave = 0;
 378         boolean usedZone;
 379         for (int zindex = 0; zindex < zone.size(); zindex++) {
 380             zrec = zone.get(zindex);
 381             usedZone = false;
 382             gmtOffset = zrec.getGmtOffset();
 383             int stdOffset = zrec.getDirectSave();
 384 
 385             // If this is the last zone record, take the last rule info.
 386             if (!zrec.hasUntil()) {
 387                 tz.setRawOffset(gmtOffset, fromTime);
 388                 if (zrec.hasRuleReference()) {
 389                     tz.setLastRules(zrec.getRuleRef().getLastRules());
 390                 } else if (stdOffset != 0) {
 391                     // in case the last rule is all year round DST-only
 392                     // (Asia/Amman once announced this rule.)
 393                     tz.setLastDSTSaving(stdOffset);
 394                 }
 395             }
 396             if (!zrec.hasRuleReference()) {
 397                 if (!zrec.hasUntil() || zrec.getUntilTime(stdOffset) >= fromTime) {
 398                     tz.addTransition(fromTime,
 399                                      tz.getOffsetIndex(gmtOffset+stdOffset),
 400                                      tz.getDstOffsetIndex(stdOffset));
 401                     usedZone = true;
 402                 }
 403                 currentSave = stdOffset;
 404                 // optimization in case the last rule is fixed.
 405                 if (!zrec.hasUntil()) {
 406                     if (tz.getNTransitions() > 0) {
 407                         if (stdOffset == 0) {
 408                             tz.setDSTType(Timezone.X_DST);
 409                         } else {
 410                             tz.setDSTType(Timezone.LAST_DST);
 411                         }
 412                         long time = Time.getLocalTime(maxYear,
 413                                                       Month.JANUARY, 1, 0);
 414                         time -= zrec.getGmtOffset();
 415                         tz.addTransition(time,
 416                                          tz.getOffsetIndex(gmtOffset+stdOffset),
 417                                          tz.getDstOffsetIndex(stdOffset));
 418                         tz.addUsedRec(zrec);
 419                     } else {
 420                         tz.setDSTType(Timezone.NO_DST);
 421                     }
 422                     break;
 423                 }
 424             } else {
 425                 Rule rule = zrec.getRuleRef();
 426                 boolean fromTimeUsed = false;
 427                 currentSave = 0;
 428             year_loop:
 429                 for (year = getMinYear(); year <= endYear; year++) {
 430                     if (zrec.hasUntil() && year > zrec.getUntilYear()) {
 431                         break;
 432                     }
 433                     List<RuleRec> rules = rule.getRules(year);
 434                     if (rules.size() > 0) {
 435                         for (int i = 0; i < rules.size(); i++) {
 436                             RuleRec rrec = rules.get(i);
 437                             long transition = rrec.getTransitionTime(year,
 438                                                                      gmtOffset,
 439                                                                      currentSave);
 440                             if (zrec.hasUntil()) {
 441                                 if (transition >= zrec.getUntilTime(currentSave)) {
 442                                     break year_loop;
 443                                 }
 444                             }
 445 
 446                             if (fromTimeUsed == false) {
 447                                 if (fromTime <= transition) {
 448                                     fromTimeUsed = true;
 449 
 450                                     if (fromTime != minTime) {
 451                                         int prevsave;
 452 
 453                                         ZoneRec prevzrec = zone.get(zindex - 1);
 454 
 455                                         // See if until time in the previous
 456                                         // ZoneRec is the same thing as the
 457                                         // local time in the next rule.
 458                                         // (examples are Asia/Ashkhabad in 1991,
 459                                         // Europe/Riga in 1989)
 460 
 461                                         if (i > 0) {
 462                                             prevsave = rules.get(i-1).getSave();
 463                                         } else {
 464                                             List<RuleRec> prevrules = rule.getRules(year-1);
 465 
 466                                             if (prevrules.size() > 0) {
 467                                                 prevsave = prevrules.get(prevrules.size()-1).getSave();
 468                                             } else {
 469                                                 prevsave = 0;
 470                                             }
 471                                         }
 472 
 473                                         if (rrec.isSameTransition(prevzrec, prevsave, gmtOffset)) {
 474                                             currentSave = rrec.getSave();
 475                                             tz.addTransition(fromTime,
 476                                                          tz.getOffsetIndex(gmtOffset+currentSave),
 477                                                          tz.getDstOffsetIndex(currentSave));
 478                                             tz.addUsedRec(rrec);
 479                                             usedZone = true;
 480                                             continue;
 481                                         }
 482                                         if (!prevzrec.hasRuleReference()
 483                                             || rule != prevzrec.getRuleRef()
 484                                             || (rule == prevzrec.getRuleRef()
 485                                                 && gmtOffset != prevzrec.getGmtOffset())) {
 486                                             int save = (fromTime == transition) ? rrec.getSave() : currentSave;
 487                                             tz.addTransition(fromTime,
 488                                                          tz.getOffsetIndex(gmtOffset+save),
 489                                                          tz.getDstOffsetIndex(save));
 490                                             tz.addUsedRec(rrec);
 491                                             usedZone = true;
 492                                         }
 493                                     } else {
 494                                         int save = rrec.getSave();
 495                                         tz.addTransition(fromTime,
 496                                                          tz.getOffsetIndex(gmtOffset+save),
 497                                                          tz.getDstOffsetIndex(save));
 498                                         tz.addUsedRec(rrec);
 499                                         usedZone = true;
 500                                     }
 501                                 } else if (year == fromYear && i == rules.size()-1) {
 502                                     int save = rrec.getSave();
 503                                     tz.addTransition(fromTime,
 504                                                      tz.getOffsetIndex(gmtOffset+save),
 505                                                      tz.getDstOffsetIndex(save));
 506                                 }
 507                             }
 508 
 509                             currentSave = rrec.getSave();
 510                             if (fromTime < transition) {
 511                                 tz.addTransition(transition,
 512                                                  tz.getOffsetIndex(gmtOffset+currentSave),
 513                                                  tz.getDstOffsetIndex(currentSave));
 514                                 tz.addUsedRec(rrec);
 515                                 usedZone = true;
 516                             }
 517                         }
 518                     } else {
 519                         if (year == fromYear) {
 520                             tz.addTransition(fromTime,
 521                                              tz.getOffsetIndex(gmtOffset+currentSave),
 522                                              tz.getDstOffsetIndex(currentSave));
 523                             fromTimeUsed = true;
 524                         }
 525                         if (year == endYear && !zrec.hasUntil()) {
 526                             if (tz.getNTransitions() > 0) {
 527                                 // Assume that this Zone stopped DST
 528                                 tz.setDSTType(Timezone.X_DST);
 529                                 long time = Time.getLocalTime(maxYear, Month.JANUARY,
 530                                                               1, 0);
 531                                 time -= zrec.getGmtOffset();
 532                                 tz.addTransition(time,
 533                                                  tz.getOffsetIndex(gmtOffset),
 534                                                  tz.getDstOffsetIndex(0));
 535                                 usedZone = true;
 536                             } else {
 537                                 tz.setDSTType(Timezone.NO_DST);
 538                             }
 539                         }
 540                     }
 541                 }
 542             }
 543             if (usedZone) {
 544                 tz.addUsedRec(zrec);
 545             }
 546             if (zrec.hasUntil() && zrec.getUntilTime(currentSave) > fromTime) {
 547                 fromTime = zrec.getUntilTime(currentSave);
 548                 fromYear = zrec.getUntilYear();
 549                 year = zrec.getUntilYear();
 550             }
 551         }
 552 
 553         if (tz.getDSTType() == Timezone.UNDEF_DST) {
 554             tz.setDSTType(Timezone.DST);
 555         }
 556         tz.optimize();
 557         tz.checksum();
 558         return tz;
 559     }
 560 
 561     private static void panic(String msg) {
 562         Main.panic(msg);
 563     }
 564 }