]> git.aero2k.de Git - tmp/jakarta-migration.git/blob
1160a5aa2af1a2e09cdaefcead5ecaf01d8c4a4e
[tmp/jakarta-migration.git] /
1 package org.collectionspace.services.structureddate.antlr;
2
3 import java.util.Stack;
4
5 import org.antlr.v4.runtime.ANTLRInputStream;
6 import org.antlr.v4.runtime.BailErrorStrategy;
7 import org.antlr.v4.runtime.CommonTokenStream;
8 import org.antlr.v4.runtime.FailedPredicateException;
9 import org.antlr.v4.runtime.InputMismatchException;
10 import org.antlr.v4.runtime.NoViableAltException;
11 import org.antlr.v4.runtime.Parser;
12 import org.antlr.v4.runtime.RecognitionException;
13 import org.antlr.v4.runtime.Token;
14 import org.antlr.v4.runtime.TokenStream;
15 import org.antlr.v4.runtime.misc.ParseCancellationException;
16 import org.antlr.v4.runtime.tree.TerminalNode;
17 import org.collectionspace.services.structureddate.Date;
18 import org.collectionspace.services.structureddate.DateUtils;
19 import org.collectionspace.services.structureddate.DeferredCenturyEndDate;
20 import org.collectionspace.services.structureddate.DeferredCenturyStartDate;
21 import org.collectionspace.services.structureddate.DeferredDate;
22 import org.collectionspace.services.structureddate.DeferredDecadeEndDate;
23 import org.collectionspace.services.structureddate.DeferredDecadeStartDate;
24 import org.collectionspace.services.structureddate.DeferredHalfCenturyEndDate;
25 import org.collectionspace.services.structureddate.DeferredHalfCenturyStartDate;
26 import org.collectionspace.services.structureddate.DeferredMillenniumEndDate;
27 import org.collectionspace.services.structureddate.DeferredMillenniumStartDate;
28 import org.collectionspace.services.structureddate.DeferredPartialCenturyEndDate;
29 import org.collectionspace.services.structureddate.DeferredPartialCenturyStartDate;
30 import org.collectionspace.services.structureddate.DeferredPartialDecadeEndDate;
31 import org.collectionspace.services.structureddate.DeferredPartialDecadeStartDate;
32 import org.collectionspace.services.structureddate.DeferredQuarterCenturyEndDate;
33 import org.collectionspace.services.structureddate.DeferredQuarterCenturyStartDate;
34 import org.collectionspace.services.structureddate.Era;
35 import org.collectionspace.services.structureddate.Part;
36 import org.collectionspace.services.structureddate.StructuredDateInternal;
37 import org.collectionspace.services.structureddate.StructuredDateEvaluator;
38 import org.collectionspace.services.structureddate.StructuredDateFormatException;
39 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.AllOrPartOfContext;
40 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.BeforeOrAfterDateContext;
41 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.CenturyContext;
42 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.CertainDateContext;
43 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.DateContext;
44 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.DayFirstDateContext;
45 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.DayOrYearFirstDateContext;
46 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.DecadeContext;
47 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.DisplayDateContext;
48 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.EraContext;
49 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.HalfCenturyContext;
50 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.HalfYearContext;
51 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.HyphenatedRangeContext;
52 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.InvMonthYearContext;
53 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.InvSeasonYearContext;
54 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.InvStrDateContext;
55 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.InvStrDateEraLastDateContext;
56 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.MillenniumContext;
57 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.MonthContext;
58 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.MonthInYearRangeContext;
59 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.MonthYearContext;
60 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NthCenturyRangeContext;
61 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NthContext;
62 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NthHalfContext;
63 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NthQuarterContext;
64 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NthQuarterInYearRangeContext;
65 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NthQuarterYearContext;
66 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NumCenturyContext;
67 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NumContext;
68 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NumDateContext;
69 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NumDayInMonthRangeContext;
70 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NumDayOfMonthContext;
71 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NumDecadeContext;
72 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NumMonthContext;
73 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NumYearContext;
74 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.PartOfContext;
75 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.PartialCenturyContext;
76 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.PartialDecadeContext;
77 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.PartialYearContext;
78 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.QuarterCenturyContext;
79 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.QuarterInYearRangeContext;
80 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.QuarterYearContext;
81 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.SeasonYearContext;
82 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.StrCenturyContext;
83 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.StrDateContext;
84 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.StrDayInMonthRangeContext;
85 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.StrMonthContext;
86 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.StrSeasonContext;
87 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.StrSeasonInYearRangeContext;
88 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.UncertainDateContext;
89 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.YearContext;
90 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.YearSpanningWinterContext;
91
92 /**
93  * A StructuredDateEvaluator that uses an ANTLR parser to parse the display date,
94  * and an ANTLR listener to generate a structured date from the resulting parse
95  * tree.
96  */
97 public class ANTLRStructuredDateEvaluator extends StructuredDateBaseListener implements StructuredDateEvaluator {
98         /**
99          * The result of the evaluation.
100          */
101         protected StructuredDateInternal result;
102
103         /**
104          * The operation stack. The parse listener methods that are implemented here
105          * pop input parameters off the stack, and push results back on to the stack.
106          */
107         protected Stack<Object> stack;
108
109         public ANTLRStructuredDateEvaluator() {
110
111         }
112
113         @Override
114         public StructuredDateInternal evaluate(String displayDate) throws StructuredDateFormatException {
115                 stack = new Stack<Object>();
116
117                 result = new StructuredDateInternal();
118                 result.setDisplayDate(displayDate);
119
120                 // Instantiate a parser from the lowercased display date, so that parsing will be
121                 // case insensitive.
122                 ANTLRInputStream inputStream = new ANTLRInputStream(displayDate.toLowerCase());
123                 StructuredDateLexer lexer = new StructuredDateLexer(inputStream);
124                 CommonTokenStream tokenStream = new CommonTokenStream(lexer);
125                 StructuredDateParser parser = new StructuredDateParser(tokenStream);
126
127                 // Don't try to recover from parse errors, just bail.
128                 parser.setErrorHandler(new BailErrorStrategy());
129
130                 // Don't print error messages to the console.
131                 parser.removeErrorListeners();
132
133                 // Generate our own custom error messages.
134                 parser.addParseListener(this);
135
136                 try {
137                         // Attempt to fulfill the oneDisplayDate rule of the grammar.
138                         parser.oneDisplayDate();
139                 }
140                 catch(ParseCancellationException e) {
141                         // ParseCancellationException is thrown by the BailErrorStrategy when there is a
142                         // parse error, with the underlying RecognitionException as the cause.
143                         RecognitionException re = (RecognitionException) e.getCause();
144
145                         throw new StructuredDateFormatException(getErrorMessage(re), re);
146                 }
147
148                 // The parsing was successful. Return the result.
149                 return result;
150         }
151
152         @Override
153         public void exitDisplayDate(DisplayDateContext ctx) {
154                 if (ctx.exception != null) return;
155
156                 Date latestDate = (Date) stack.pop();
157                 Date earliestDate = (Date) stack.pop();
158
159                 // If the earliest date and the latest date are the same, it's just a "single" date.
160                 // There's no need to have the latest, so set it to null.
161
162                 if (earliestDate.equals(latestDate)) {
163                         latestDate = null;
164                 }
165
166                 result.setEarliestSingleDate(earliestDate);
167                 result.setLatestDate(latestDate);
168         }
169
170         @Override
171         public void exitBeforeOrAfterDate(BeforeOrAfterDateContext ctx) {
172                 if (ctx.exception != null) return;
173
174                 Date latestDate = (Date) stack.pop();
175                 Date earliestDate = (Date) stack.pop();
176
177                 // Set null eras to the default.
178
179                 if (earliestDate.getEra() == null) {
180                         earliestDate.setEra(Date.DEFAULT_ERA);
181                 }
182
183                 if (latestDate.getEra() == null) {
184                         latestDate.setEra(Date.DEFAULT_ERA);
185                 }
186
187                 // Finalize any deferred calculations.
188
189                 if (latestDate instanceof DeferredDate) {
190                         ((DeferredDate) latestDate).resolveDate();
191                 }
192
193                 if (earliestDate instanceof DeferredDate) {
194                         ((DeferredDate) earliestDate).resolveDate();
195                 }
196
197                 // Calculate the earliest date or end date.
198
199                 if (ctx.BEFORE() != null) {
200                         latestDate = earliestDate;
201                         earliestDate = DateUtils.getEarliestBeforeDate(earliestDate, latestDate);
202                 }
203                 else if (ctx.AFTER() != null) {
204                         earliestDate = latestDate;
205                         latestDate = DateUtils.getLatestAfterDate(earliestDate, latestDate);
206                 }
207
208                 stack.push(earliestDate);
209                 stack.push(latestDate);
210         }
211
212         @Override
213         public void exitUncertainDate(UncertainDateContext ctx) {
214                 if (ctx.exception != null) return;
215
216                 Date latestDate = (Date) stack.pop();
217                 Date earliestDate = (Date) stack.pop();
218
219                 int earliestInterval = DateUtils.getCircaIntervalYears(earliestDate.getYear(), earliestDate.getEra());
220                 int latestInterval = DateUtils.getCircaIntervalYears(latestDate.getYear(), latestDate.getEra());
221
222                 // Express the circa interval as a qualifier.
223
224                 // stack.push(earliestDate.withQualifier(QualifierType.MINUS, earliestInterval, QualifierUnit.YEARS));
225                 // stack.push(latestDate.withQualifier(QualifierType.PLUS, latestInterval, QualifierUnit.YEARS));
226
227                 // OR:
228
229                 // Express the circa interval as an offset calculated into the year.
230
231                 DateUtils.subtractYears(earliestDate, earliestInterval);
232                 DateUtils.addYears(latestDate, latestInterval);
233
234                 stack.push(earliestDate);
235                 stack.push(latestDate);
236         }
237
238         @Override
239         public void exitCertainDate(CertainDateContext ctx) {
240                 if (ctx.exception != null) return;
241
242                 Date latestDate = (Date) stack.pop();
243                 Date earliestDate = (Date) stack.pop();
244
245                 // Set null eras to the default.
246
247                 if (earliestDate.getEra() == null) {
248                         earliestDate.setEra(Date.DEFAULT_ERA);
249                 }
250
251                 if (latestDate.getEra() == null) {
252                         latestDate.setEra(Date.DEFAULT_ERA);
253                 }
254
255                 // Finalize any deferred calculations.
256
257                 if (latestDate instanceof DeferredDate) {
258                         ((DeferredDate) latestDate).resolveDate();
259                 }
260
261                 if (earliestDate instanceof DeferredDate) {
262                         ((DeferredDate) earliestDate).resolveDate();
263                 }
264
265                 stack.push(earliestDate);
266                 stack.push(latestDate);
267         }
268
269         @Override
270         public void exitHyphenatedRange(HyphenatedRangeContext ctx) {
271                 if (ctx.exception != null) return;
272
273                 Date latestEndDate = (Date) stack.pop();
274                 stack.pop(); // latestStartDate
275                 stack.pop(); // earliestEndDate
276                 Date earliestStartDate = (Date) stack.pop();
277
278                 // If no era was explicitly specified for the first date,
279                 // make it inherit the era of the second date.
280
281                 if (earliestStartDate.getEra() == null && latestEndDate.getEra() != null) {
282                         earliestStartDate.setEra(latestEndDate.getEra());
283                 }
284
285                 // Finalize any deferred calculations.
286
287                 if (earliestStartDate instanceof DeferredDate) {
288                         ((DeferredDate) earliestStartDate).resolveDate();
289                 }
290
291                 if (latestEndDate instanceof DeferredDate) {
292                         ((DeferredDate) latestEndDate).resolveDate();
293                 }
294
295                 stack.push(earliestStartDate);
296                 stack.push(latestEndDate);
297         }
298
299         @Override
300         public void exitNthCenturyRange(NthCenturyRangeContext ctx) {
301                 if (ctx.exception != null) return;
302
303                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
304                 Integer endN = (Integer) stack.pop();
305                 Part endPart = (Part) stack.pop();
306                 Integer startN = (Integer) stack.pop();
307                 Part startPart = (Part) stack.pop();
308
309                 if (era == null) {
310                         era = Date.DEFAULT_ERA;
311                 }
312
313                 int startYear = DateUtils.nthCenturyToYear(startN);
314                 int endYear = DateUtils.nthCenturyToYear(endN);
315
316                 stack.push(startPart == null ? DateUtils.getCenturyStartDate(startYear, era) : DateUtils.getPartialCenturyStartDate(startYear, startPart, era));
317                 stack.push(startPart == null ? DateUtils.getCenturyEndDate(startYear, era) : DateUtils.getPartialCenturyEndDate(startYear, startPart, era));
318                 stack.push(endPart == null ? DateUtils.getCenturyStartDate(endYear, era) : DateUtils.getPartialCenturyStartDate(endYear, endPart, era));
319                 stack.push(endPart == null ? DateUtils.getCenturyEndDate(endYear, era) : DateUtils.getPartialCenturyEndDate(endYear, endPart, era));
320         }
321
322         @Override
323         public void exitMonthInYearRange(MonthInYearRangeContext ctx) {
324                 if (ctx.exception != null) return;
325
326                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
327                 Integer year = (Integer) stack.pop();
328                 Integer numMonthEnd = (Integer) stack.pop();
329                 Integer numMonthStart = (Integer) stack.pop();
330
331                 stack.push(new Date(year, numMonthStart, 1, era));
332                 stack.push(new Date(year, numMonthStart, DateUtils.getDaysInMonth(numMonthStart, year, era), era));
333                 stack.push(new Date(year, numMonthEnd, 1, era));
334                 stack.push(new Date(year, numMonthEnd, DateUtils.getDaysInMonth(numMonthEnd, year, era), era));
335         }
336
337         @Override
338         public void exitQuarterInYearRange(QuarterInYearRangeContext ctx) {
339                 if (ctx.exception != null) return;
340
341                 Era era = (Era) stack.pop();
342                 Integer year = (Integer) stack.pop();
343                 Integer lastQuarter = (Integer) stack.pop();
344                 Integer firstQuarter = (Integer) stack.pop();
345
346                 stack.push(DateUtils.getQuarterYearStartDate(firstQuarter, year).withEra(era));
347                 stack.push(DateUtils.getQuarterYearEndDate(firstQuarter, year, era).withEra(era));
348                 stack.push(DateUtils.getQuarterYearStartDate(lastQuarter, year).withEra(era));
349                 stack.push(DateUtils.getQuarterYearEndDate(lastQuarter, year, era).withEra(era));
350         }
351
352         @Override
353         public void exitStrDayInMonthRange(StrDayInMonthRangeContext ctx) {
354                 if (ctx.exception != null) return;
355
356                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
357                 Integer year = (Integer) stack.pop();
358                 Integer dayOfMonthEnd = (Integer) stack.pop();
359                 Integer dayOfMonthStart = (Integer) stack.pop();
360                 Integer numMonth = (Integer) stack.pop();
361
362                 stack.push(new Date(year, numMonth, dayOfMonthStart, era));
363                 stack.push(new Date(year, numMonth, dayOfMonthStart, era));
364                 stack.push(new Date(year, numMonth, dayOfMonthEnd, era));
365                 stack.push(new Date(year, numMonth, dayOfMonthEnd, era));
366         }
367
368         @Override
369         public void exitNumDayInMonthRange(NumDayInMonthRangeContext ctx) {
370                 if (ctx.exception != null) return;
371
372                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
373                 Integer year = (Integer) stack.pop();
374                 Integer dayOfMonthEnd = (Integer) stack.pop();
375                 Integer dayOfMonthStart = (Integer) stack.pop();
376                 Integer numMonth = (Integer) stack.pop();
377
378                 stack.push(new Date(year, numMonth, dayOfMonthStart, era));
379                 stack.push(new Date(year, numMonth, dayOfMonthStart, era));
380                 stack.push(new Date(year, numMonth, dayOfMonthEnd, era));
381                 stack.push(new Date(year, numMonth, dayOfMonthEnd, era));
382         }
383
384         @Override
385         public void exitDate(DateContext ctx) {
386                 if (ctx.exception != null) return;
387
388                 // Expect the canonical year-month-day-era ordering
389                 // to be on the stack.
390
391                 Era era = (Era) stack.pop();
392                 Integer dayOfMonth = (Integer) stack.pop();
393                 Integer numMonth = (Integer) stack.pop();
394                 Integer year = (Integer) stack.pop();
395
396                 // For the latest date we could either return null, or a copy of the earliest date,
397                 // since the UI doesn't care. Use a copy of the earliest date, since it makes
398                 // things easier here if we don't have to test for null up the tree.
399
400                 stack.push(new Date(year, numMonth, dayOfMonth, era));
401                 stack.push(new Date(year, numMonth, dayOfMonth, era));
402         }
403
404         @Override
405         public void exitNumDate(NumDateContext ctx) {
406                 if (ctx.exception != null) return;
407
408                 // This could either be year-month-day, or
409                 // month-day-year. Try to determine which,
410                 // and reorder the stack into the canonical
411                 // year-month-day-era ordering.
412
413                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
414                 Integer num3 = (Integer) stack.pop();
415                 Integer num2 = (Integer) stack.pop();
416                 Integer num1 = (Integer) stack.pop();
417
418                 // Default to a month-day-year interpretation.
419
420                 int numMonth = num1;
421                 int dayOfMonth = num2;
422                 int year = num3;
423
424                 if (DateUtils.isValidDate(num3, num1, num2, era)) {
425                         // Interpreting as month-day-year produces a valid date. Go with it.
426                 }
427                 else if (DateUtils.isValidDate(num1, num2, num3, era)) {
428                         // Interpreting as month-day-year doesn't produce a valid date, but
429                         // year-month-day does. Go with year-month-day.
430
431                         year = num1;
432                         numMonth = num2;
433                         dayOfMonth = num3;
434                 }
435
436                 stack.push(year);
437                 stack.push(numMonth);
438                 stack.push(dayOfMonth);
439                 stack.push(era);
440         }
441
442         @Override
443         public void exitStrDate(StrDateContext ctx) {
444                 if (ctx.exception != null) return;
445
446                 // Reorder the stack into a canonical ordering,
447                 // year-month-day-era.
448
449                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
450                 Integer year = (Integer) stack.pop();
451                 Integer dayOfMonth = (Integer) stack.pop();
452                 Integer numMonth = (Integer) stack.pop();
453
454                 stack.push(year);
455                 stack.push(numMonth);
456                 stack.push(dayOfMonth);
457                 stack.push(era);
458         }
459
460         @Override
461         public void exitInvStrDate(InvStrDateContext ctx) {
462                 if (ctx.exception != null) return;
463
464                 // Reorder the stack into a canonical ordering,
465                 // year-month-day-era.
466
467                 Integer dayOfMonth = (Integer) stack.pop();
468                 Integer numMonth = (Integer) stack.pop();
469                 Integer year = (Integer) stack.pop();
470                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
471
472                 stack.push(year);
473                 stack.push(numMonth);
474                 stack.push(dayOfMonth);
475                 stack.push(era);
476         }
477
478         @Override
479         public void exitDayFirstDate(DayFirstDateContext ctx) {
480                 if (ctx.exception != null) return ;
481                 
482                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
483                 Integer year = (Integer) stack.pop();
484                 Integer month = (Integer) stack.pop();
485                 Integer dayOfMonth = (Integer) stack.pop();
486
487                 stack.push(year);
488                 stack.push(month);
489                 stack.push(dayOfMonth);
490                 stack.push(era);
491         }
492
493         @Override
494         public void exitDayOrYearFirstDate(DayOrYearFirstDateContext ctx) {
495                 if (ctx.exception != null) return;
496
497                 Era era = (stack.size() == 3) ? null : (Era) stack.pop();
498                 Integer year = (Integer) stack.pop();
499                 Integer numMonth = (Integer) stack.pop();
500                 Integer dayOfMonth = (Integer) stack.pop();
501
502                 Integer num1 = year;
503                 Integer num2 = dayOfMonth;
504
505                 if (DateUtils.isValidDate(num1, numMonth, num2, era)) {
506                         // Do nothing, it is already In the right format
507                 } else if (DateUtils.isValidDate(num2, numMonth, num1, era)) {
508                         // Then the first number is a year. Fix:
509                         year = num2;
510                         dayOfMonth = num1;
511                 }
512
513                 stack.push(year);
514                 stack.push(numMonth);
515                 stack.push(dayOfMonth);
516                 stack.push(era);
517
518                 if (dayOfMonth > 31 || dayOfMonth <= 0) {
519                         throw new StructuredDateFormatException("unexpected day of month '" + Integer.toString(dayOfMonth) + "'");
520                 }
521                 if (year == 0) {
522                         throw new StructuredDateFormatException("unexpected year '" + Integer.toString(year) + "'");
523                 }
524         }
525
526         @Override
527         public void exitInvStrDateEraLastDate(InvStrDateEraLastDateContext ctx) {
528                 if (ctx.exception != null) return;
529
530                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
531                 Integer dayOfMonth = (Integer) stack.pop();
532                 Integer month = (Integer) stack.pop();
533                 Integer year = (Integer) stack.pop();
534
535                 stack.push(year);
536                 stack.push(month);
537                 stack.push(dayOfMonth);
538                 stack.push(era);
539         }
540
541         @Override
542         public void exitMonth(MonthContext ctx) {
543                 if (ctx.exception != null) return;
544
545                 Era era = (Era) stack.pop();
546                 Integer year = (Integer) stack.pop();
547                 Integer numMonth = (Integer) stack.pop();
548
549                 stack.push(new Date(year, numMonth, 1, era));
550                 stack.push(new Date(year, numMonth, DateUtils.getDaysInMonth(numMonth, year, era), era));
551         }
552
553         @Override
554         public void exitMonthYear(MonthYearContext ctx) {
555                 if (ctx.exception != null) return;
556
557                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
558
559                 stack.push(era);
560         }
561
562         @Override
563         public void exitInvMonthYear(InvMonthYearContext ctx) {
564                 if (ctx.exception != null) return;
565
566                 // Invert the arguments.
567
568                 Integer numMonth = (Integer) stack.pop();
569                 Integer year = (Integer) stack.pop();
570                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
571
572                 stack.push(numMonth);
573                 stack.push(year);
574                 stack.push(era);
575         }
576
577         @Override
578         public void exitYearSpanningWinter(YearSpanningWinterContext ctx) {
579                 if (ctx.exception != null) return;
580
581                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
582                 Integer endYear = (Integer) stack.pop();
583                 Integer startYear = (Integer) stack.pop();
584
585                 stack.push(new Date(startYear, 12, 1).withEra(era));
586                 stack.push(DateUtils.getQuarterYearEndDate(1, endYear, era).withEra(era));
587         }
588
589         @Override
590         public void exitPartialYear(PartialYearContext ctx) {
591                 if (ctx.exception != null) return;
592
593                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
594                 Integer year = (Integer) stack.pop();
595                 Part part = (Part) stack.pop();
596
597                 stack.push(DateUtils.getPartialYearStartDate(part, year).withEra(era));
598                 stack.push(DateUtils.getPartialYearEndDate(part, year, era).withEra(era));
599         }
600
601         @Override
602         public void exitQuarterYear(QuarterYearContext ctx) {
603                 if (ctx.exception != null) return;
604
605                 Era era = (Era) stack.pop();
606                 Integer year = (Integer) stack.pop();
607                 Integer quarter = (Integer) stack.pop();
608
609                 stack.push(DateUtils.getQuarterYearStartDate(quarter, year).withEra(era));
610                 stack.push(DateUtils.getQuarterYearEndDate(quarter, year, era).withEra(era));
611         }
612
613         @Override
614         public void exitHalfYear(HalfYearContext ctx) {
615                 if (ctx.exception != null) return;
616
617                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
618                 Integer year = (Integer) stack.pop();
619                 Integer half = (Integer) stack.pop();
620
621                 stack.push(DateUtils.getHalfYearStartDate(half, year).withEra(era));
622                 stack.push(DateUtils.getHalfYearEndDate(half, year, era).withEra(era));
623         }
624
625         @Override
626         public void exitInvSeasonYear(InvSeasonYearContext ctx) {
627                 if (ctx.exception != null) return;
628
629                 // Invert the arguments.
630
631                 Integer quarter = (Integer) stack.pop();
632                 Integer year = (Integer) stack.pop();
633                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
634
635                 stack.push(quarter);
636                 stack.push(year);
637                 stack.push(era);
638         }
639
640         @Override 
641         public void exitSeasonYear(SeasonYearContext ctx) {
642                 if (ctx.exception != null) return;
643
644                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
645                 stack.push(era);
646
647         }
648
649         @Override
650         public void exitYear(YearContext ctx) {
651                 if (ctx.exception != null) return;
652
653                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
654                 Integer year = (Integer) stack.pop();
655
656                 stack.push(new Date(year, 1, 1, era));
657                 stack.push(new Date(year, 12, 31, era));
658         }
659
660         @Override
661         public void exitPartialDecade(PartialDecadeContext ctx) {
662                 if (ctx.exception != null) return;
663
664                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
665                 Integer year = (Integer) stack.pop();
666                 Part part = (Part) stack.pop();
667
668                 if (era != null) {
669                         // If the era was explicitly specified, the start and end years
670                         // may be calculated now.
671
672                         stack.push(DateUtils.getPartialDecadeStartDate(year, part, era));
673                         stack.push(DateUtils.getPartialDecadeEndDate(year, part, era));
674                 }
675                 else {
676                         // If the era was not explicitly specified, the start and end years
677                         // can't be calculated yet. The calculation must be deferred until
678                         // later. For example, this partial decade may be the start of a hyphenated
679                         // range, where the era will be inherited from the era of the end of
680                         // the range; this era won't be known until farther up the parse tree,
681                         // when both sides of the range will have been parsed.
682
683                         stack.push(new DeferredPartialDecadeStartDate(year, part));
684                         stack.push(new DeferredPartialDecadeEndDate(year, part));
685                 }
686         }
687
688         @Override
689         public void exitDecade(DecadeContext ctx) {
690                 if (ctx.exception != null) return;
691
692                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
693                 Integer year = (Integer) stack.pop();
694
695                 // Calculate the start and end year of the decade, which depends on the era.
696
697                 if (era != null) {
698                         // If the era was explicitly specified, the start and end years
699                         // may be calculated now.
700
701                         stack.push(DateUtils.getDecadeStartDate(year, era));
702                         stack.push(DateUtils.getDecadeEndDate(year, era));
703                 }
704                 else {
705                         // If the era was not explicitly specified, the start and end years
706                         // can't be calculated yet. The calculation must be deferred until
707                         // later. For example, this decade may be the start of a hyphenated
708                         // range, where the era will be inherited from the era of the end of
709                         // the range; this era won't be known until farther up the parse tree,
710                         // when both sides of the range will have been parsed.
711
712                         stack.push(new DeferredDecadeStartDate(year));
713                         stack.push(new DeferredDecadeEndDate(year));
714                 }
715         }
716
717         @Override
718         public void exitPartialCentury(PartialCenturyContext ctx) {
719                 if (ctx.exception != null) return;
720
721                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
722                 Integer year = (Integer) stack.pop();
723                 Part part = (Part) stack.pop();
724
725                 if (era != null) {
726                         // If the era was explicitly specified, the start and end years
727                         // may be calculated now.
728
729                         stack.push(DateUtils.getPartialCenturyStartDate(year, part, era));
730                         stack.push(DateUtils.getPartialCenturyEndDate(year, part, era));
731                 }
732                 else {
733                         // If the era was not explicitly specified, the start and end years
734                         // can't be calculated yet. The calculation must be deferred until
735                         // later. For example, this partial century may be the start of a hyphenated
736                         // range, where the era will be inherited from the era of the end of
737                         // the range; this era won't be known until farther up the parse tree,
738                         // when both sides of the range will have been parsed.
739
740                         stack.push(new DeferredPartialCenturyStartDate(year, part));
741                         stack.push(new DeferredPartialCenturyEndDate(year, part));
742                 }
743         }
744
745         @Override
746         public void exitQuarterCentury(QuarterCenturyContext ctx) {
747                 if (ctx.exception != null) return;
748
749                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
750                 Integer year = (Integer) stack.pop();
751                 Integer quarter = (Integer) stack.pop();
752
753                 if (era != null) {
754                         // If the era was explicitly specified, the start and end years
755                         // may be calculated now.
756
757                         stack.push(DateUtils.getQuarterCenturyStartDate(year, quarter, era));
758                         stack.push(DateUtils.getQuarterCenturyEndDate(year, quarter, era));
759                 }
760                 else {
761                         // If the era was not explicitly specified, the start and end years
762                         // can't be calculated yet. The calculation must be deferred until
763                         // later. For example, this century may be the start of a hyphenated
764                         // range, where the era will be inherited from the era of the end of
765                         // the range; this era won't be known until farther up the parse tree,
766                         // when both sides of the range will have been parsed.
767
768                         stack.push(new DeferredQuarterCenturyStartDate(year, quarter));
769                         stack.push(new DeferredQuarterCenturyEndDate(year, quarter));
770                 }
771         }
772
773         @Override
774         public void exitHalfCentury(HalfCenturyContext ctx) {
775                 if (ctx.exception != null) return;
776
777                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
778                 Integer year = (Integer) stack.pop();
779                 Integer half = (Integer) stack.pop();
780
781                 if (era != null) {
782                         // If the era was explicitly specified, the start and end years
783                         // may be calculated now.
784
785                         stack.push(DateUtils.getHalfCenturyStartDate(year, half, era));
786                         stack.push(DateUtils.getHalfCenturyEndDate(year, half, era));
787                 }
788                 else {
789                         // If the era was not explicitly specified, the start and end years
790                         // can't be calculated yet. The calculation must be deferred until
791                         // later. For example, this half century may be the start of a hyphenated
792                         // range, where the era will be inherited from the era of the end of
793                         // the range; this era won't be known until farther up the parse tree,
794                         // when both sides of the range will have been parsed.
795
796                         stack.push(new DeferredHalfCenturyStartDate(year, half));
797                         stack.push(new DeferredHalfCenturyEndDate(year, half));
798                 }
799         }
800
801         @Override
802         public void exitCentury(CenturyContext ctx) {
803                 if (ctx.exception != null) return;
804
805                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
806                 Integer year = (Integer) stack.pop();
807
808                 if (era != null) {
809                         // If the era was explicitly specified, the start and end years
810                         // may be calculated now.
811
812                         stack.push(DateUtils.getCenturyStartDate(year, era));
813                         stack.push(DateUtils.getCenturyEndDate(year, era));
814                 }
815                 else {
816                         // If the era was not explicitly specified, the start and end years
817                         // can't be calculated yet. The calculation must be deferred until
818                         // later. For example, this quarter century may be the start of a hyphenated
819                         // range, where the era will be inherited from the era of the end of
820                         // the range; this era won't be known until farther up the parse tree,
821                         // when both sides of the range will have been parsed.
822
823                         stack.push(new DeferredCenturyStartDate(year));
824                         stack.push(new DeferredCenturyEndDate(year));
825                 }
826         }
827
828         @Override
829         public void exitMillennium(MillenniumContext ctx) {
830                 if (ctx.exception != null) return;
831
832                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
833                 Integer n = (Integer) stack.pop();
834
835                 if (era != null) {
836                         // If the era was explicitly specified, the start and end years
837                         // may be calculated now.
838
839                         stack.push(DateUtils.getMillenniumStartDate(n, era));
840                         stack.push(DateUtils.getMillenniumEndDate(n, era));
841                 }
842                 else {
843                         // If the era was not explicitly specified, the start and end years
844                         // can't be calculated yet. The calculation must be deferred until
845                         // later. For example, this millennium may be the start of a hyphenated
846                         // range, where the era will be inherited from the era of the end of
847                         // the range; this era won't be known until farther up the parse tree,
848                         // when both sides of the range will have been parsed.
849
850                         stack.push(new DeferredMillenniumStartDate(n));
851                         stack.push(new DeferredMillenniumEndDate(n));
852                 }
853         }
854
855         @Override
856         public void exitStrCentury(StrCenturyContext ctx) {
857                 if (ctx.exception != null) return;
858
859                 Integer n = (Integer) stack.pop();
860
861                 // Convert the nth number to a year number,
862                 // and push on the stack.
863
864                 Integer year = DateUtils.nthCenturyToYear(n);
865
866                 stack.push(year);
867         }
868
869         @Override
870         public void exitNumCentury(NumCenturyContext ctx) {
871                 if (ctx.exception != null) return;
872
873                 // Convert the string to a number,
874                 // and push on the stack.
875
876                 Integer year = new Integer(stripEndLetters(ctx.HUNDREDS().getText()));
877
878                 if (year == 0) {
879                         throw new StructuredDateFormatException("unexpected century '" + ctx.HUNDREDS().getText() + "'");
880                 }
881
882                 stack.push(year);
883         }
884
885         @Override
886         public void exitNumDecade(NumDecadeContext ctx) {
887                 if (ctx.exception != null) return;
888
889                 // Convert the string to a number,
890                 // and push on the stack.
891
892                 Integer year = new Integer(stripEndLetters(ctx.TENS().getText()));
893
894                 if (year == 0) {
895                         throw new StructuredDateFormatException("unexpected decade '" + ctx.TENS().getText() + "'");
896                 }
897
898                 stack.push(year);
899         }
900
901         @Override
902         public void exitNumYear(NumYearContext ctx) {
903                 if (ctx.exception != null) return;
904
905                 // Convert the string to a number,
906                 // and push on the stack.
907
908                 Integer year = new Integer(ctx.NUMBER().getText());
909
910                 if (year == 0) {
911                         throw new StructuredDateFormatException("unexpected year '" + ctx.NUMBER().getText() + "'");
912                 }
913
914                 stack.push(year);
915         }
916
917         @Override
918         public void exitNumMonth(NumMonthContext ctx) {
919                 if (ctx.exception != null) return;
920
921                 // Convert the string a number,
922                 // and push on the stack.
923
924                 Integer month = new Integer(ctx.NUMBER().getText());
925
926                 if (month < 1 || month > 12) {
927                         throw new StructuredDateFormatException("unexpected month '" + ctx.NUMBER().getText() + "'");
928                 }
929
930                 stack.push(month);
931         }
932
933         @Override
934         public void exitNthHalf(NthHalfContext ctx) {
935                 if (ctx.exception != null) return;
936
937                 // Convert LAST to a number (the last half
938                 // is the 2nd). If this rule matched the
939                 // alternative with nth instead of LAST,
940                 // the nth handler will already have pushed
941                 // a number on the stack.
942
943                 if (ctx.LAST() != null) {
944                         stack.push(new Integer(2));
945                 }
946
947                 // Check for a valid half.
948
949                 Integer n = (Integer) stack.peek();
950
951                 if (n < 1 || n > 2) {
952                         throw new StructuredDateFormatException("unexpected half '" + n + "'");
953                 }
954         }
955         
956
957         @Override
958         public void exitNthQuarterInYearRange(NthQuarterInYearRangeContext ctx) {
959                 if (ctx.exception != null) return;
960
961                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
962
963                 stack.push(era);
964         }
965
966         @Override
967         public void exitStrSeasonInYearRange(StrSeasonInYearRangeContext ctx) {
968                 if (ctx.exception != null) return;
969
970                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
971
972                 stack.push(era);
973
974         }
975
976         @Override
977         public void exitNthQuarterYear(NthQuarterYearContext ctx) {
978                 
979                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
980
981                 stack.push(era);
982         }
983
984         @Override
985         public void exitNthQuarter(NthQuarterContext ctx) {
986                 if (ctx.exception != null) return;
987
988                 // Convert LAST to a number (the last quarter
989                 // is the 4th). If this rule matched the
990                 // alternative with nth instead of LAST,
991                 // the nth handler will already have pushed
992                 // a number on the stack.
993
994                 if (ctx.LAST() != null) {
995                         stack.push(new Integer(4));
996                 }
997
998                 // Check for a valid quarter.
999
1000                 Integer n = (Integer) stack.peek();
1001
1002                 if (n < 1 || n > 4) {
1003                         throw new StructuredDateFormatException("unexpected quarter '" + n + "'");
1004                 }
1005         }
1006
1007         @Override
1008         public void exitNth(NthContext ctx) {
1009                 if (ctx.exception != null) return;
1010
1011                 // Convert the string to a number,
1012                 // and push on the stack.
1013
1014                 Integer n = null;
1015
1016                 if (ctx.NTHSTR() != null) {
1017                         n = new Integer(stripEndLetters(ctx.NTHSTR().getText()));
1018                 }
1019                 else if (ctx.FIRST() != null) {
1020                         n = 1;
1021                 }
1022                 else if (ctx.SECOND() != null) {
1023                         n = 2;
1024                 }
1025                 else if (ctx.THIRD() != null) {
1026                         n = 3;
1027                 }
1028                 else if (ctx.FOURTH() != null) {
1029                         n = 4;
1030                 }
1031
1032                 stack.push(n);
1033         }
1034
1035         @Override
1036         public void exitStrMonth(StrMonthContext ctx) {
1037                 if (ctx.exception != null) return;
1038
1039                 // Convert the month name to a number,
1040                 // and push on the stack.
1041
1042                 TerminalNode monthNode = ctx.MONTH();
1043
1044                 if (monthNode == null) {
1045                         monthNode = ctx.SHORTMONTH();
1046                 }
1047
1048                 String monthStr = monthNode.getText();
1049
1050                 stack.push(DateUtils.getMonthByName(monthStr));
1051         }
1052
1053         @Override
1054         public void exitStrSeason(StrSeasonContext ctx) {
1055                 if (ctx.exception != null) return;
1056
1057                 // Convert the season to a quarter number,
1058                 // and push on the stack.
1059
1060                 Integer quarter = null;
1061
1062                 if (ctx.WINTER() != null) {
1063                         quarter = 1;
1064                 }
1065                 else if (ctx.SPRING() != null) {
1066                         quarter = 2;
1067                 }
1068                 else if (ctx.SUMMER() != null) {
1069                         quarter = 3;
1070                 }
1071                 else if (ctx.FALL() != null) {
1072                         quarter = 4;
1073                 }
1074
1075                 stack.push(quarter);
1076         }
1077
1078         @Override
1079         public void exitAllOrPartOf(AllOrPartOfContext ctx) {
1080                 if (ctx.exception != null) return;
1081
1082                 // If a part was specified, it will have been
1083                 // pushed on the stack in exitPartOf(). If not,
1084                 // push null on the stack.
1085
1086                 if (ctx.partOf() == null) {
1087                         stack.push(null);
1088                 }
1089         }
1090
1091         @Override
1092         public void exitPartOf(PartOfContext ctx) {
1093                 if (ctx.exception != null) return;
1094
1095                 // Convert the token to a Part,
1096                 // and push on the stack.
1097
1098                 Part part = null;
1099
1100                 if (ctx.EARLY() != null) {
1101                         part = Part.EARLY;
1102                 }
1103                 else if (ctx.MIDDLE() != null) {
1104                         part = Part.MIDDLE;
1105                 }
1106                 else if (ctx.LATE() != null) {
1107                         part = Part.LATE;
1108                 }
1109
1110                 stack.push(part);
1111         }
1112
1113         @Override
1114         public void exitEra(EraContext ctx) {
1115                 if (ctx.exception != null) return;
1116
1117                 // Convert the token to an Era,
1118                 // and push on the stack.
1119
1120                 Era era = null;
1121
1122                 if (ctx.BC() != null) {
1123                         era = Era.BCE;
1124                 }
1125                 else if (ctx.AD() != null) {
1126                         era = Era.CE;
1127                 }
1128
1129                 stack.push(era);
1130         }
1131
1132         @Override
1133         public void exitNumDayOfMonth(NumDayOfMonthContext ctx) {
1134                 if (ctx.exception != null) return;
1135
1136                 // Convert the numeric string to an Integer,
1137                 // and push on the stack.
1138
1139                 Integer dayOfMonth = new Integer(ctx.NUMBER().getText());
1140
1141                 if (dayOfMonth == 0 || dayOfMonth > 31) {
1142                         throw new StructuredDateFormatException("unexpected day of month '" + ctx.NUMBER().getText() + "'");
1143                 }
1144
1145                 stack.push(dayOfMonth);
1146         }
1147
1148         @Override
1149         public void exitNum(NumContext ctx) {
1150                 if (ctx.exception != null) return;
1151
1152                 // Convert the numeric string to an Integer,
1153                 // and push on the stack.
1154
1155                 Integer num = new Integer(ctx.NUMBER().getText());
1156
1157                 stack.push(num);
1158         }
1159
1160         protected String getErrorMessage(RecognitionException re) {
1161                 String message = "";
1162
1163                 Parser recognizer = (Parser) re.getRecognizer();
1164                 TokenStream tokens = recognizer.getInputStream();
1165
1166                 if (re instanceof NoViableAltException) {
1167                         NoViableAltException e = (NoViableAltException) re;
1168                         Token startToken = e.getStartToken();
1169                         String input = (startToken.getType() == Token.EOF ) ? "end of text" : quote(tokens.getText(startToken, e.getOffendingToken()));
1170
1171                         message = "no viable date format found at " + input;
1172                 }
1173                 else if (re instanceof InputMismatchException) {
1174                         InputMismatchException e = (InputMismatchException) re;
1175                         message = "did not expect " + getTokenDisplayString(e.getOffendingToken()) + " while looking for " +
1176                                   e.getExpectedTokens().toString(recognizer.getTokenNames());
1177                 }
1178                 else if (re instanceof FailedPredicateException) {
1179                         FailedPredicateException e = (FailedPredicateException) re;
1180                         String ruleName = recognizer.getRuleNames()[recognizer.getContext().getRuleIndex()];
1181
1182                         message = "failed predicate " + ruleName + ": " + e.getMessage();
1183                 }
1184
1185                 return message;
1186         }
1187
1188         protected String quote(String text) {
1189                 return "'" + text + "'";
1190         }
1191
1192         protected String getTokenDisplayString(Token token) {
1193                 String string;
1194
1195                 if (token == null) {
1196                         string = "[no token]";
1197                 }
1198                 else {
1199                         String text = token.getText();
1200
1201                         if (text == null) {
1202                                 if (token.getType() == Token.EOF ) {
1203                                         string = "end of text";
1204                                 }
1205                                 else {
1206                                         string = "[" + token.getType() + "]";
1207                                 }
1208                         }
1209                         else {
1210                                 string = quote(text);
1211                         }
1212                 }
1213
1214                 return string;
1215         }
1216
1217         protected String stripEndLetters(String input) {
1218                 return input.replaceAll("[^\\d]+$", "");
1219         }
1220
1221         public static void main(String[] args) {
1222                 StructuredDateEvaluator evaluator = new ANTLRStructuredDateEvaluator();
1223
1224                 for (String displayDate : args) {
1225                         try {
1226                                 evaluator.evaluate(displayDate);
1227                         } catch (StructuredDateFormatException e) {
1228                                 e.printStackTrace();
1229                         }
1230                 }
1231         }
1232 }