001: /*
002: * Copyright 2000-2006 Sun Microsystems, Inc. All Rights Reserved.
003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004: *
005: * This code is free software; you can redistribute it and/or modify it
006: * under the terms of the GNU General Public License version 2 only, as
007: * published by the Free Software Foundation. Sun designates this
008: * particular file as subject to the "Classpath" exception as provided
009: * by Sun in the LICENSE file that accompanied this code.
010: *
011: * This code is distributed in the hope that it will be useful, but WITHOUT
012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014: * version 2 for more details (a copy is included in the LICENSE file that
015: * accompanied this code).
016: *
017: * You should have received a copy of the GNU General Public License version
018: * 2 along with this work; if not, write to the Free Software Foundation,
019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020: *
021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022: * CA 95054 USA or visit www.sun.com if you need additional information or
023: * have any questions.
024: */
025:
026: package sun.tools.javazic;
027:
028: import java.io.BufferedReader;
029: import java.io.FileReader;
030: import java.io.FileNotFoundException;
031: import java.io.IOException;
032: import java.util.ArrayList;
033: import java.util.HashMap;
034: import java.util.Iterator;
035: import java.util.List;
036: import java.util.Map;
037: import java.util.StringTokenizer;
038:
039: /**
040: * Zoneinfo provides javazic compiler front-end functionality.
041: * @since 1.4
042: */
043: class Zoneinfo {
044:
045: private static final int minYear = 1900;
046: private static final int maxYear = 2037;
047: private static final long minTime = Time.getLocalTime(minYear,
048: Month.JANUARY, 1, 0);
049: private static int startYear = minYear;
050: private static int endYear = maxYear;
051:
052: /**
053: * True if javazic should generate a list of SimpleTimeZone
054: * instances for the SimpleTimeZone-based time zone support.
055: */
056: static boolean isYearForTimeZoneDataSpecified = false;
057:
058: /**
059: * Zone name to Zone mappings
060: */
061: private Map<String, Zone> zones;
062:
063: /**
064: * Rule name to Rule mappings
065: */
066: private Map<String, Rule> rules;
067:
068: /**
069: * Alias name to real name mappings
070: */
071: private Map<String, String> aliases;
072:
073: /**
074: * Constracts a Zoneinfo.
075: */
076: Zoneinfo() {
077: zones = new HashMap<String, Zone>();
078: rules = new HashMap<String, Rule>();
079: aliases = new HashMap<String, String>();
080: }
081:
082: /**
083: * Adds the given zone to the list of Zones.
084: * @param zone Zone to be added to the list.
085: */
086: void add(Zone zone) {
087: String name = zone.getName();
088: zones.put(name, zone);
089: }
090:
091: /**
092: * Adds the given rule to the list of Rules.
093: * @param rule Rule to be added to the list.
094: */
095: void add(Rule rule) {
096: String name = rule.getName();
097: rules.put(name, rule);
098: }
099:
100: /**
101: * Puts the specifid name pair to the alias table.
102: * @param name1 an alias time zone name
103: * @param name2 the real time zone of the alias name
104: */
105: void putAlias(String name1, String name2) {
106: aliases.put(name1, name2);
107: }
108:
109: /**
110: * Sets the given year for SimpleTimeZone list output.
111: * This method is called when the -S option is specified.
112: * @param year the year for which SimpleTimeZone list should be generated
113: */
114: static void setYear(int year) {
115: setStartYear(year);
116: setEndYear(year);
117: isYearForTimeZoneDataSpecified = true;
118: }
119:
120: /**
121: * Sets the start year.
122: * @param year the start year value
123: * @throws IllegalArgumentException if the specified year value is
124: * smaller than the minimum year or greater than the end year.
125: */
126: static void setStartYear(int year) {
127: if (year < minYear || year > endYear) {
128: throw new IllegalArgumentException(
129: "invalid start year specified: " + year);
130: }
131: startYear = year;
132: }
133:
134: /**
135: * @return the start year value
136: */
137: static int getStartYear() {
138: return startYear;
139: }
140:
141: /**
142: * Sets the end year.
143: * @param year the end year value
144: * @throws IllegalArgumentException if the specified year value is
145: * smaller than the start year or greater than the maximum year.
146: */
147: static void setEndYear(int year) {
148: if (year < startYear || year > maxYear) {
149: throw new IllegalArgumentException();
150: }
151: endYear = year;
152: }
153:
154: /**
155: * @return the end year value
156: */
157: static int getEndYear() {
158: return endYear;
159: }
160:
161: /**
162: * @return the minimum year value
163: */
164: static int getMinYear() {
165: return minYear;
166: }
167:
168: /**
169: * @return the maximum year value
170: */
171: static int getMaxYear() {
172: return maxYear;
173: }
174:
175: /**
176: * @return the alias table
177: */
178: Map<String, String> getAliases() {
179: return aliases;
180: }
181:
182: /**
183: * @return the Zone list
184: */
185: Map<String, Zone> getZones() {
186: return zones;
187: }
188:
189: /**
190: * @return a Zone specified by name.
191: * @param name a zone name
192: */
193: Zone getZone(String name) {
194: return zones.get(name);
195: }
196:
197: /**
198: * @return a Rule specified by name.
199: * @param name a rule name
200: */
201: Rule getRule(String name) {
202: return rules.get(name);
203: }
204:
205: private static String line;
206:
207: private static int lineNum;
208:
209: /**
210: * Parses the specified time zone data file and creates a Zoneinfo
211: * that has all Rules, Zones and Links (aliases) information.
212: * @param fname the time zone data file name
213: * @return a Zoneinfo object
214: */
215: static Zoneinfo parse(String fname) {
216: BufferedReader in = null;
217: try {
218: FileReader fr = new FileReader(fname);
219: in = new BufferedReader(fr);
220: } catch (FileNotFoundException e) {
221: panic("can't open file: " + fname);
222: }
223: Zoneinfo zi = new Zoneinfo();
224: boolean continued = false;
225: Zone zone = null;
226: String l;
227:
228: try {
229: while ((line = in.readLine()) != null) {
230: lineNum++;
231: // skip blank and comment lines
232: if (line.length() == 0 || line.charAt(0) == '#') {
233: continue;
234: }
235:
236: // trim trailing comments
237: int rindex = line.lastIndexOf('#');
238: if (rindex != -1) {
239: // take the data part of the line
240: l = line.substring(0, rindex);
241: } else {
242: l = line;
243: }
244:
245: StringTokenizer tokens = new StringTokenizer(l);
246: if (!tokens.hasMoreTokens()) {
247: continue;
248: }
249: String token = tokens.nextToken();
250:
251: if (continued || "Zone".equals(token)) {
252: if (zone == null) {
253: if (!tokens.hasMoreTokens()) {
254: panic("syntax error: zone no more token");
255: }
256: token = tokens.nextToken();
257: // if the zone name is in "GMT+hh" or "GMT-hh"
258: // format, ignore it due to spec conflict.
259: if (token.startsWith("GMT+")
260: || token.startsWith("GMT-")) {
261: continue;
262: }
263: zone = new Zone(token);
264: } else {
265: // no way to push the current token back...
266: tokens = new StringTokenizer(l);
267: }
268:
269: ZoneRec zrec = ZoneRec.parse(tokens);
270: zrec.setLine(line);
271: zone.add(zrec);
272: if ((continued = zrec.hasUntil()) == false) {
273: if (Zone.isTargetZone(zone.getName())) {
274: // zone.resolve(zi);
275: zi.add(zone);
276: }
277: zone = null;
278: }
279: } else if ("Rule".equals(token)) {
280: if (!tokens.hasMoreTokens()) {
281: panic("syntax error: rule no more token");
282: }
283: token = tokens.nextToken();
284: Rule rule = zi.getRule(token);
285: if (rule == null) {
286: rule = new Rule(token);
287: zi.add(rule);
288: }
289: RuleRec rrec = RuleRec.parse(tokens);
290: rrec.setLine(line);
291: rule.add(rrec);
292: } else if ("Link".equals(token)) {
293: // Link <newname> <oldname>
294: try {
295: String name1 = tokens.nextToken();
296: String name2 = tokens.nextToken();
297:
298: // if the zone name is in "GMT+hh" or "GMT-hh"
299: // format, ignore it due to spec conflict with
300: // custom time zones. Also, ignore "ROC" for
301: // PC-ness.
302: if (name2.startsWith("GMT+")
303: || name2.startsWith("GMT-")
304: || "ROC".equals(name2)) {
305: continue;
306: }
307: zi.putAlias(name2, name1);
308: } catch (Exception e) {
309: panic("syntax error: no more token for Link");
310: }
311: }
312: }
313: in.close();
314: } catch (IOException ex) {
315: panic("IO error: " + ex.getMessage());
316: }
317:
318: return zi;
319: }
320:
321: /**
322: * Interprets a zone and constructs a Timezone object that
323: * contains enough information on GMT offsets and DST schedules to
324: * generate a zone info database.
325: *
326: * @param zoneName the zone name for which a Timezone object is
327: * constructed.
328: *
329: * @return a Timezone object that contains all GMT offsets and DST
330: * rules information.
331: */
332: Timezone phase2(String zoneName) {
333: Timezone tz = new Timezone(zoneName);
334: Zone zone = getZone(zoneName);
335: zone.resolve(this );
336:
337: // TODO: merge phase2's for the regular and SimpleTimeZone ones.
338: if (isYearForTimeZoneDataSpecified) {
339: ZoneRec zrec = zone.get(zone.size() - 1);
340: tz.setLastZoneRec(zrec);
341: tz.setRawOffset(zrec.getGmtOffset());
342: if (zrec.hasRuleReference()) {
343: /*
344: * This part assumes that the specified year is covered by
345: * the rules referred to by the last zone record.
346: */
347: List<RuleRec> rrecs = zrec.getRuleRef().getRules(
348: startYear);
349:
350: if (rrecs.size() == 2) {
351: // make sure that one is a start rule and the other is
352: // an end rule.
353: RuleRec r0 = rrecs.get(0);
354: RuleRec r1 = rrecs.get(1);
355: if (r0.getSave() == 0 && r1.getSave() > 0) {
356: rrecs.set(0, r1);
357: rrecs.set(1, r0);
358: } else if (!(r0.getSave() > 0 && r1.getSave() == 0)) {
359: rrecs = null;
360: Main.error(zoneName + ": rules for "
361: + startYear + " not found.");
362: }
363: } else {
364: rrecs = null;
365: }
366: if (rrecs != null) {
367: tz.setLastRules(rrecs);
368: }
369: }
370: return tz;
371: }
372:
373: int gmtOffset;
374: int year = minYear;
375: int fromYear = year;
376: long fromTime = Time.getLocalTime(startYear, Month.JANUARY, 1,
377: 0);
378:
379: // take the index 0 for the GMT offset of the last zone record
380: ZoneRec zrec = zone.get(zone.size() - 1);
381: tz.getOffsetIndex(zrec.getGmtOffset());
382:
383: int currentSave = 0;
384: boolean usedZone;
385: for (int zindex = 0; zindex < zone.size(); zindex++) {
386: zrec = zone.get(zindex);
387: usedZone = false;
388: gmtOffset = zrec.getGmtOffset();
389: int stdOffset = zrec.getDirectSave();
390:
391: // If this is the last zone record, take the last rule info.
392: if (!zrec.hasUntil()) {
393: tz.setRawOffset(gmtOffset, fromTime);
394: if (zrec.hasRuleReference()) {
395: tz.setLastRules(zrec.getRuleRef().getLastRules());
396: } else if (stdOffset != 0) {
397: // in case the last rule is all year round DST-only
398: // (Asia/Amman once announced this rule.)
399: tz.setLastDSTSaving(stdOffset);
400: }
401: }
402: if (!zrec.hasRuleReference()) {
403: if (!zrec.hasUntil()
404: || zrec.getUntilTime(stdOffset) >= fromTime) {
405: tz.addTransition(fromTime, tz
406: .getOffsetIndex(gmtOffset + stdOffset), tz
407: .getDstOffsetIndex(stdOffset));
408: usedZone = true;
409: }
410: currentSave = stdOffset;
411: // optimization in case the last rule is fixed.
412: if (!zrec.hasUntil()) {
413: if (tz.getNTransitions() > 0) {
414: if (stdOffset == 0) {
415: tz.setDSTType(tz.X_DST);
416: } else {
417: tz.setDSTType(tz.LAST_DST);
418: }
419: long time = Time.getLocalTime(maxYear,
420: Month.JANUARY, 1, 0);
421: time -= zrec.getGmtOffset();
422: tz.addTransition(time, tz
423: .getOffsetIndex(gmtOffset + stdOffset),
424: tz.getDstOffsetIndex(stdOffset));
425: tz.addUsedRec(zrec);
426: } else {
427: tz.setDSTType(tz.NO_DST);
428: }
429: break;
430: }
431: } else {
432: Rule rule = zrec.getRuleRef();
433: boolean fromTimeUsed = false;
434: currentSave = 0;
435: year_loop: for (year = getMinYear(); year <= endYear; year++) {
436: if (zrec.hasUntil() && year > zrec.getUntilYear()) {
437: break;
438: }
439: List<RuleRec> rules = rule.getRules(year);
440: if (rules.size() > 0) {
441: for (int i = 0; i < rules.size(); i++) {
442: RuleRec rrec = rules.get(i);
443: long transition = rrec.getTransitionTime(
444: year, gmtOffset, currentSave);
445: if (zrec.hasUntil()) {
446: if (transition >= zrec
447: .getUntilTime(currentSave)) {
448: break year_loop;
449: }
450: }
451:
452: if (fromTimeUsed == false) {
453: if (fromTime <= transition) {
454: fromTimeUsed = true;
455:
456: if (fromTime != minTime) {
457: int prevsave;
458:
459: ZoneRec prevzrec = zone
460: .get(zindex - 1);
461:
462: // See if until time in the previous
463: // ZoneRec is the same thing as the
464: // local time in the next rule.
465: // (examples are Asia/Ashkhabad in 1991,
466: // Europe/Riga in 1989)
467:
468: if (i > 0) {
469: prevsave = rules.get(i - 1)
470: .getSave();
471: } else {
472: List<RuleRec> prevrules = rule
473: .getRules(year - 1);
474:
475: if (prevrules.size() > 0) {
476: prevsave = prevrules
477: .get(
478: prevrules
479: .size() - 1)
480: .getSave();
481: } else {
482: prevsave = 0;
483: }
484: }
485:
486: if (rrec.isSameTransition(
487: prevzrec, prevsave,
488: gmtOffset)) {
489: currentSave = rrec
490: .getSave();
491: tz
492: .addTransition(
493: fromTime,
494: tz
495: .getOffsetIndex(gmtOffset
496: + currentSave),
497: tz
498: .getDstOffsetIndex(currentSave));
499: tz.addUsedRec(rrec);
500: usedZone = true;
501: continue;
502: }
503: if (!prevzrec
504: .hasRuleReference()
505: || rule != prevzrec
506: .getRuleRef()
507: || (rule == prevzrec
508: .getRuleRef() && gmtOffset != prevzrec
509: .getGmtOffset())) {
510: int save = (fromTime == transition) ? rrec
511: .getSave()
512: : currentSave;
513: tz
514: .addTransition(
515: fromTime,
516: tz
517: .getOffsetIndex(gmtOffset
518: + save),
519: tz
520: .getDstOffsetIndex(save));
521: tz.addUsedRec(rrec);
522: usedZone = true;
523: }
524: } else {
525: int save = rrec.getSave();
526: tz
527: .addTransition(
528: fromTime,
529: tz
530: .getOffsetIndex(gmtOffset
531: + save),
532: tz
533: .getDstOffsetIndex(save));
534: tz.addUsedRec(rrec);
535: usedZone = true;
536: }
537: } else if (year == fromYear
538: && i == rules.size() - 1) {
539: int save = rrec.getSave();
540: tz.addTransition(fromTime, tz
541: .getOffsetIndex(gmtOffset
542: + save), tz
543: .getDstOffsetIndex(save));
544: }
545: }
546:
547: currentSave = rrec.getSave();
548: if (fromTime < transition) {
549: tz
550: .addTransition(
551: transition,
552: tz
553: .getOffsetIndex(gmtOffset
554: + currentSave),
555: tz
556: .getDstOffsetIndex(currentSave));
557: tz.addUsedRec(rrec);
558: usedZone = true;
559: }
560: }
561: } else {
562: if (year == fromYear) {
563: tz.addTransition(fromTime, tz
564: .getOffsetIndex(gmtOffset
565: + currentSave), tz
566: .getDstOffsetIndex(currentSave));
567: fromTimeUsed = true;
568: }
569: if (year == endYear && !zrec.hasUntil()) {
570: if (tz.getNTransitions() > 0) {
571: // Assume that this Zone stopped DST
572: tz.setDSTType(tz.X_DST);
573: long time = Time.getLocalTime(maxYear,
574: Month.JANUARY, 1, 0);
575: time -= zrec.getGmtOffset();
576: tz.addTransition(time, tz
577: .getOffsetIndex(gmtOffset), tz
578: .getDstOffsetIndex(0));
579: usedZone = true;
580: } else {
581: tz.setDSTType(tz.NO_DST);
582: }
583: }
584: }
585: }
586: }
587: if (usedZone) {
588: tz.addUsedRec(zrec);
589: }
590: if (zrec.hasUntil()
591: && zrec.getUntilTime(currentSave) > fromTime) {
592: fromTime = zrec.getUntilTime(currentSave);
593: fromYear = zrec.getUntilYear();
594: year = zrec.getUntilYear();
595: }
596: }
597:
598: if (tz.getDSTType() == tz.UNDEF_DST) {
599: tz.setDSTType(tz.DST);
600: }
601: tz.optimize();
602: tz.checksum();
603: return tz;
604: }
605:
606: private static void panic(String msg) {
607: Main.panic(msg);
608: }
609: }
|