]> git.aero2k.de Git - tmp/jakarta-migration.git/blob
5c9066ff97796275958c24c9fdaa963664c883c2
[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 = (stack.size() == 3) ? null : (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 = null;
498                 Integer num2 = (Integer) stack.pop();
499                 Integer numMonth = (Integer) stack.pop();
500                 Integer num1 = (Integer) stack.pop();
501
502                 Integer year = num1;
503                 Integer dayOfMonth = num2;
504
505                 if (DateUtils.isValidDate(num1, numMonth, num2, era)) {
506                         // The first number is a year. Already correct
507                 } else if (DateUtils.isValidDate(num2, numMonth, num1, era)) {
508                         // The second number is a year.
509                         year = num2;
510                         dayOfMonth = num1;
511                 }
512
513                 stack.push(year);
514                 stack.push(numMonth);
515                 stack.push(dayOfMonth);
516
517                 if (dayOfMonth > 31 || dayOfMonth <= 0) {
518                         throw new StructuredDateFormatException("unexpected day of month '" + Integer.toString(dayOfMonth) + "'");
519                 }
520                 if (year == 0) {
521                         throw new StructuredDateFormatException("unexpected year '" + Integer.toString(year) + "'");
522                 }
523         }
524
525         @Override
526         public void exitInvStrDateEraLastDate(InvStrDateEraLastDateContext ctx) {
527                 if (ctx.exception != null) return;
528
529                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
530                 Integer dayOfMonth = (Integer) stack.pop();
531                 Integer month = (Integer) stack.pop();
532                 Integer year = (Integer) stack.pop();
533
534                 stack.push(year);
535                 stack.push(month);
536                 stack.push(dayOfMonth);
537                 stack.push(era);
538         }
539
540         @Override
541         public void exitMonth(MonthContext ctx) {
542                 if (ctx.exception != null) return;
543
544                 Era era = (Era) stack.pop();
545                 Integer year = (Integer) stack.pop();
546                 Integer numMonth = (Integer) stack.pop();
547
548                 stack.push(new Date(year, numMonth, 1, era));
549                 stack.push(new Date(year, numMonth, DateUtils.getDaysInMonth(numMonth, year, era), era));
550         }
551
552         @Override
553         public void exitMonthYear(MonthYearContext ctx) {
554                 if (ctx.exception != null) return;
555
556                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
557
558                 stack.push(era);
559         }
560
561         @Override
562         public void exitInvMonthYear(InvMonthYearContext ctx) {
563                 if (ctx.exception != null) return;
564
565                 // Invert the arguments.
566
567                 Integer numMonth = (Integer) stack.pop();
568                 Integer year = (Integer) stack.pop();
569                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
570
571                 stack.push(numMonth);
572                 stack.push(year);
573                 stack.push(era);
574         }
575
576         @Override
577         public void exitYearSpanningWinter(YearSpanningWinterContext ctx) {
578                 if (ctx.exception != null) return;
579
580                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
581                 Integer endYear = (Integer) stack.pop();
582                 Integer startYear = (Integer) stack.pop();
583
584                 stack.push(new Date(startYear, 12, 1).withEra(era));
585                 stack.push(DateUtils.getQuarterYearEndDate(1, endYear, era).withEra(era));
586         }
587
588         @Override
589         public void exitPartialYear(PartialYearContext ctx) {
590                 if (ctx.exception != null) return;
591
592                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
593                 Integer year = (Integer) stack.pop();
594                 Part part = (Part) stack.pop();
595
596                 stack.push(DateUtils.getPartialYearStartDate(part, year).withEra(era));
597                 stack.push(DateUtils.getPartialYearEndDate(part, year, era).withEra(era));
598         }
599
600         @Override
601         public void exitQuarterYear(QuarterYearContext ctx) {
602                 if (ctx.exception != null) return;
603
604                 Era era = (Era) stack.pop();
605                 Integer year = (Integer) stack.pop();
606                 Integer quarter = (Integer) stack.pop();
607
608                 stack.push(DateUtils.getQuarterYearStartDate(quarter, year).withEra(era));
609                 stack.push(DateUtils.getQuarterYearEndDate(quarter, year, era).withEra(era));
610         }
611
612         @Override
613         public void exitHalfYear(HalfYearContext ctx) {
614                 if (ctx.exception != null) return;
615
616                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
617                 Integer year = (Integer) stack.pop();
618                 Integer half = (Integer) stack.pop();
619
620                 stack.push(DateUtils.getHalfYearStartDate(half, year).withEra(era));
621                 stack.push(DateUtils.getHalfYearEndDate(half, year, era).withEra(era));
622         }
623
624         @Override
625         public void exitInvSeasonYear(InvSeasonYearContext ctx) {
626                 if (ctx.exception != null) return;
627
628                 // Invert the arguments.
629
630                 Integer quarter = (Integer) stack.pop();
631                 Integer year = (Integer) stack.pop();
632                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
633
634                 stack.push(quarter);
635                 stack.push(year);
636                 stack.push(era);
637         }
638
639         @Override 
640         public void exitSeasonYear(SeasonYearContext ctx) {
641                 if (ctx.exception != null) return;
642
643                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
644                 stack.push(era);
645
646         }
647
648         @Override
649         public void exitYear(YearContext ctx) {
650                 if (ctx.exception != null) return;
651
652                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
653                 Integer year = (Integer) stack.pop();
654
655                 stack.push(new Date(year, 1, 1, era));
656                 stack.push(new Date(year, 12, 31, era));
657         }
658
659         @Override
660         public void exitPartialDecade(PartialDecadeContext ctx) {
661                 if (ctx.exception != null) return;
662
663                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
664                 Integer year = (Integer) stack.pop();
665                 Part part = (Part) stack.pop();
666
667                 if (era != null) {
668                         // If the era was explicitly specified, the start and end years
669                         // may be calculated now.
670
671                         stack.push(DateUtils.getPartialDecadeStartDate(year, part, era));
672                         stack.push(DateUtils.getPartialDecadeEndDate(year, part, era));
673                 }
674                 else {
675                         // If the era was not explicitly specified, the start and end years
676                         // can't be calculated yet. The calculation must be deferred until
677                         // later. For example, this partial decade may be the start of a hyphenated
678                         // range, where the era will be inherited from the era of the end of
679                         // the range; this era won't be known until farther up the parse tree,
680                         // when both sides of the range will have been parsed.
681
682                         stack.push(new DeferredPartialDecadeStartDate(year, part));
683                         stack.push(new DeferredPartialDecadeEndDate(year, part));
684                 }
685         }
686
687         @Override
688         public void exitDecade(DecadeContext ctx) {
689                 if (ctx.exception != null) return;
690
691                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
692                 Integer year = (Integer) stack.pop();
693
694                 // Calculate the start and end year of the decade, which depends on the era.
695
696                 if (era != null) {
697                         // If the era was explicitly specified, the start and end years
698                         // may be calculated now.
699
700                         stack.push(DateUtils.getDecadeStartDate(year, era));
701                         stack.push(DateUtils.getDecadeEndDate(year, era));
702                 }
703                 else {
704                         // If the era was not explicitly specified, the start and end years
705                         // can't be calculated yet. The calculation must be deferred until
706                         // later. For example, this decade may be the start of a hyphenated
707                         // range, where the era will be inherited from the era of the end of
708                         // the range; this era won't be known until farther up the parse tree,
709                         // when both sides of the range will have been parsed.
710
711                         stack.push(new DeferredDecadeStartDate(year));
712                         stack.push(new DeferredDecadeEndDate(year));
713                 }
714         }
715
716         @Override
717         public void exitPartialCentury(PartialCenturyContext ctx) {
718                 if (ctx.exception != null) return;
719
720                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
721                 Integer year = (Integer) stack.pop();
722                 Part part = (Part) stack.pop();
723
724                 if (era != null) {
725                         // If the era was explicitly specified, the start and end years
726                         // may be calculated now.
727
728                         stack.push(DateUtils.getPartialCenturyStartDate(year, part, era));
729                         stack.push(DateUtils.getPartialCenturyEndDate(year, part, era));
730                 }
731                 else {
732                         // If the era was not explicitly specified, the start and end years
733                         // can't be calculated yet. The calculation must be deferred until
734                         // later. For example, this partial century may be the start of a hyphenated
735                         // range, where the era will be inherited from the era of the end of
736                         // the range; this era won't be known until farther up the parse tree,
737                         // when both sides of the range will have been parsed.
738
739                         stack.push(new DeferredPartialCenturyStartDate(year, part));
740                         stack.push(new DeferredPartialCenturyEndDate(year, part));
741                 }
742         }
743
744         @Override
745         public void exitQuarterCentury(QuarterCenturyContext ctx) {
746                 if (ctx.exception != null) return;
747
748                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
749                 Integer year = (Integer) stack.pop();
750                 Integer quarter = (Integer) stack.pop();
751
752                 if (era != null) {
753                         // If the era was explicitly specified, the start and end years
754                         // may be calculated now.
755
756                         stack.push(DateUtils.getQuarterCenturyStartDate(year, quarter, era));
757                         stack.push(DateUtils.getQuarterCenturyEndDate(year, quarter, era));
758                 }
759                 else {
760                         // If the era was not explicitly specified, the start and end years
761                         // can't be calculated yet. The calculation must be deferred until
762                         // later. For example, this century may be the start of a hyphenated
763                         // range, where the era will be inherited from the era of the end of
764                         // the range; this era won't be known until farther up the parse tree,
765                         // when both sides of the range will have been parsed.
766
767                         stack.push(new DeferredQuarterCenturyStartDate(year, quarter));
768                         stack.push(new DeferredQuarterCenturyEndDate(year, quarter));
769                 }
770         }
771
772         @Override
773         public void exitHalfCentury(HalfCenturyContext ctx) {
774                 if (ctx.exception != null) return;
775
776                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
777                 Integer year = (Integer) stack.pop();
778                 Integer half = (Integer) stack.pop();
779
780                 if (era != null) {
781                         // If the era was explicitly specified, the start and end years
782                         // may be calculated now.
783
784                         stack.push(DateUtils.getHalfCenturyStartDate(year, half, era));
785                         stack.push(DateUtils.getHalfCenturyEndDate(year, half, era));
786                 }
787                 else {
788                         // If the era was not explicitly specified, the start and end years
789                         // can't be calculated yet. The calculation must be deferred until
790                         // later. For example, this half century may be the start of a hyphenated
791                         // range, where the era will be inherited from the era of the end of
792                         // the range; this era won't be known until farther up the parse tree,
793                         // when both sides of the range will have been parsed.
794
795                         stack.push(new DeferredHalfCenturyStartDate(year, half));
796                         stack.push(new DeferredHalfCenturyEndDate(year, half));
797                 }
798         }
799
800         @Override
801         public void exitCentury(CenturyContext ctx) {
802                 if (ctx.exception != null) return;
803
804                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
805                 Integer year = (Integer) stack.pop();
806
807                 if (era != null) {
808                         // If the era was explicitly specified, the start and end years
809                         // may be calculated now.
810
811                         stack.push(DateUtils.getCenturyStartDate(year, era));
812                         stack.push(DateUtils.getCenturyEndDate(year, era));
813                 }
814                 else {
815                         // If the era was not explicitly specified, the start and end years
816                         // can't be calculated yet. The calculation must be deferred until
817                         // later. For example, this quarter century may be the start of a hyphenated
818                         // range, where the era will be inherited from the era of the end of
819                         // the range; this era won't be known until farther up the parse tree,
820                         // when both sides of the range will have been parsed.
821
822                         stack.push(new DeferredCenturyStartDate(year));
823                         stack.push(new DeferredCenturyEndDate(year));
824                 }
825         }
826
827         @Override
828         public void exitMillennium(MillenniumContext ctx) {
829                 if (ctx.exception != null) return;
830
831                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
832                 Integer n = (Integer) stack.pop();
833
834                 if (era != null) {
835                         // If the era was explicitly specified, the start and end years
836                         // may be calculated now.
837
838                         stack.push(DateUtils.getMillenniumStartDate(n, era));
839                         stack.push(DateUtils.getMillenniumEndDate(n, era));
840                 }
841                 else {
842                         // If the era was not explicitly specified, the start and end years
843                         // can't be calculated yet. The calculation must be deferred until
844                         // later. For example, this millennium may be the start of a hyphenated
845                         // range, where the era will be inherited from the era of the end of
846                         // the range; this era won't be known until farther up the parse tree,
847                         // when both sides of the range will have been parsed.
848
849                         stack.push(new DeferredMillenniumStartDate(n));
850                         stack.push(new DeferredMillenniumEndDate(n));
851                 }
852         }
853
854         @Override
855         public void exitStrCentury(StrCenturyContext ctx) {
856                 if (ctx.exception != null) return;
857
858                 Integer n = (Integer) stack.pop();
859
860                 // Convert the nth number to a year number,
861                 // and push on the stack.
862
863                 Integer year = DateUtils.nthCenturyToYear(n);
864
865                 stack.push(year);
866         }
867
868         @Override
869         public void exitNumCentury(NumCenturyContext ctx) {
870                 if (ctx.exception != null) return;
871
872                 // Convert the string to a number,
873                 // and push on the stack.
874
875                 Integer year = new Integer(stripEndLetters(ctx.HUNDREDS().getText()));
876
877                 if (year == 0) {
878                         throw new StructuredDateFormatException("unexpected century '" + ctx.HUNDREDS().getText() + "'");
879                 }
880
881                 stack.push(year);
882         }
883
884         @Override
885         public void exitNumDecade(NumDecadeContext ctx) {
886                 if (ctx.exception != null) return;
887
888                 // Convert the string to a number,
889                 // and push on the stack.
890
891                 Integer year = new Integer(stripEndLetters(ctx.TENS().getText()));
892
893                 if (year == 0) {
894                         throw new StructuredDateFormatException("unexpected decade '" + ctx.TENS().getText() + "'");
895                 }
896
897                 stack.push(year);
898         }
899
900         @Override
901         public void exitNumYear(NumYearContext ctx) {
902                 if (ctx.exception != null) return;
903
904                 // Convert the string to a number,
905                 // and push on the stack.
906
907                 Integer year = new Integer(ctx.NUMBER().getText());
908
909                 if (year == 0) {
910                         throw new StructuredDateFormatException("unexpected year '" + ctx.NUMBER().getText() + "'");
911                 }
912
913                 stack.push(year);
914         }
915
916         @Override
917         public void exitNumMonth(NumMonthContext ctx) {
918                 if (ctx.exception != null) return;
919
920                 // Convert the string a number,
921                 // and push on the stack.
922
923                 Integer month = new Integer(ctx.NUMBER().getText());
924
925                 if (month < 1 || month > 12) {
926                         throw new StructuredDateFormatException("unexpected month '" + ctx.NUMBER().getText() + "'");
927                 }
928
929                 stack.push(month);
930         }
931
932         @Override
933         public void exitNthHalf(NthHalfContext ctx) {
934                 if (ctx.exception != null) return;
935
936                 // Convert LAST to a number (the last half
937                 // is the 2nd). If this rule matched the
938                 // alternative with nth instead of LAST,
939                 // the nth handler will already have pushed
940                 // a number on the stack.
941
942                 if (ctx.LAST() != null) {
943                         stack.push(new Integer(2));
944                 }
945
946                 // Check for a valid half.
947
948                 Integer n = (Integer) stack.peek();
949
950                 if (n < 1 || n > 2) {
951                         throw new StructuredDateFormatException("unexpected half '" + n + "'");
952                 }
953         }
954         
955
956         @Override
957         public void exitNthQuarterInYearRange(NthQuarterInYearRangeContext ctx) {
958                 if (ctx.exception != null) return;
959
960                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
961
962                 stack.push(era);
963         }
964
965         @Override
966         public void exitStrSeasonInYearRange(StrSeasonInYearRangeContext ctx) {
967                 if (ctx.exception != null) return;
968
969                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
970
971                 stack.push(era);
972
973         }
974
975         @Override
976         public void exitNthQuarterYear(NthQuarterYearContext ctx) {
977                 
978                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
979
980                 stack.push(era);
981         }
982
983         @Override
984         public void exitNthQuarter(NthQuarterContext ctx) {
985                 if (ctx.exception != null) return;
986
987                 // Convert LAST to a number (the last quarter
988                 // is the 4th). If this rule matched the
989                 // alternative with nth instead of LAST,
990                 // the nth handler will already have pushed
991                 // a number on the stack.
992
993                 if (ctx.LAST() != null) {
994                         stack.push(new Integer(4));
995                 }
996
997                 // Check for a valid quarter.
998
999                 Integer n = (Integer) stack.peek();
1000
1001                 if (n < 1 || n > 4) {
1002                         throw new StructuredDateFormatException("unexpected quarter '" + n + "'");
1003                 }
1004         }
1005
1006         @Override
1007         public void exitNth(NthContext ctx) {
1008                 if (ctx.exception != null) return;
1009
1010                 // Convert the string to a number,
1011                 // and push on the stack.
1012
1013                 Integer n = null;
1014
1015                 if (ctx.NTHSTR() != null) {
1016                         n = new Integer(stripEndLetters(ctx.NTHSTR().getText()));
1017                 }
1018                 else if (ctx.FIRST() != null) {
1019                         n = 1;
1020                 }
1021                 else if (ctx.SECOND() != null) {
1022                         n = 2;
1023                 }
1024                 else if (ctx.THIRD() != null) {
1025                         n = 3;
1026                 }
1027                 else if (ctx.FOURTH() != null) {
1028                         n = 4;
1029                 }
1030
1031                 stack.push(n);
1032         }
1033
1034         @Override
1035         public void exitStrMonth(StrMonthContext ctx) {
1036                 if (ctx.exception != null) return;
1037
1038                 // Convert the month name to a number,
1039                 // and push on the stack.
1040
1041                 TerminalNode monthNode = ctx.MONTH();
1042
1043                 if (monthNode == null) {
1044                         monthNode = ctx.SHORTMONTH();
1045                 }
1046
1047                 String monthStr = monthNode.getText();
1048
1049                 stack.push(DateUtils.getMonthByName(monthStr));
1050         }
1051
1052         @Override
1053         public void exitStrSeason(StrSeasonContext ctx) {
1054                 if (ctx.exception != null) return;
1055
1056                 // Convert the season to a quarter number,
1057                 // and push on the stack.
1058
1059                 Integer quarter = null;
1060
1061                 if (ctx.WINTER() != null) {
1062                         quarter = 1;
1063                 }
1064                 else if (ctx.SPRING() != null) {
1065                         quarter = 2;
1066                 }
1067                 else if (ctx.SUMMER() != null) {
1068                         quarter = 3;
1069                 }
1070                 else if (ctx.FALL() != null) {
1071                         quarter = 4;
1072                 }
1073
1074                 stack.push(quarter);
1075         }
1076
1077         @Override
1078         public void exitAllOrPartOf(AllOrPartOfContext ctx) {
1079                 if (ctx.exception != null) return;
1080
1081                 // If a part was specified, it will have been
1082                 // pushed on the stack in exitPartOf(). If not,
1083                 // push null on the stack.
1084
1085                 if (ctx.partOf() == null) {
1086                         stack.push(null);
1087                 }
1088         }
1089
1090         @Override
1091         public void exitPartOf(PartOfContext ctx) {
1092                 if (ctx.exception != null) return;
1093
1094                 // Convert the token to a Part,
1095                 // and push on the stack.
1096
1097                 Part part = null;
1098
1099                 if (ctx.EARLY() != null) {
1100                         part = Part.EARLY;
1101                 }
1102                 else if (ctx.MIDDLE() != null) {
1103                         part = Part.MIDDLE;
1104                 }
1105                 else if (ctx.LATE() != null) {
1106                         part = Part.LATE;
1107                 }
1108
1109                 stack.push(part);
1110         }
1111
1112         @Override
1113         public void exitEra(EraContext ctx) {
1114                 if (ctx.exception != null) return;
1115
1116                 // Convert the token to an Era,
1117                 // and push on the stack.
1118
1119                 Era era = null;
1120
1121                 if (ctx.BC() != null) {
1122                         era = Era.BCE;
1123                 }
1124                 else if (ctx.AD() != null) {
1125                         era = Era.CE;
1126                 }
1127
1128                 stack.push(era);
1129         }
1130
1131         @Override
1132         public void exitNumDayOfMonth(NumDayOfMonthContext ctx) {
1133                 if (ctx.exception != null) return;
1134
1135                 // Convert the numeric string to an Integer,
1136                 // and push on the stack.
1137
1138                 Integer dayOfMonth = new Integer(ctx.NUMBER().getText());
1139
1140                 if (dayOfMonth == 0 || dayOfMonth > 31) {
1141                         throw new StructuredDateFormatException("unexpected day of month '" + ctx.NUMBER().getText() + "'");
1142                 }
1143
1144                 stack.push(dayOfMonth);
1145         }
1146
1147         @Override
1148         public void exitNum(NumContext ctx) {
1149                 if (ctx.exception != null) return;
1150
1151                 // Convert the numeric string to an Integer,
1152                 // and push on the stack.
1153
1154                 Integer num = new Integer(ctx.NUMBER().getText());
1155
1156                 stack.push(num);
1157         }
1158
1159         protected String getErrorMessage(RecognitionException re) {
1160                 String message = "";
1161
1162                 Parser recognizer = (Parser) re.getRecognizer();
1163                 TokenStream tokens = recognizer.getInputStream();
1164
1165                 if (re instanceof NoViableAltException) {
1166                         NoViableAltException e = (NoViableAltException) re;
1167                         Token startToken = e.getStartToken();
1168                         String input = (startToken.getType() == Token.EOF ) ? "end of text" : quote(tokens.getText(startToken, e.getOffendingToken()));
1169
1170                         message = "no viable date format found at " + input;
1171                 }
1172                 else if (re instanceof InputMismatchException) {
1173                         InputMismatchException e = (InputMismatchException) re;
1174                         message = "did not expect " + getTokenDisplayString(e.getOffendingToken()) + " while looking for " +
1175                                   e.getExpectedTokens().toString(recognizer.getTokenNames());
1176                 }
1177                 else if (re instanceof FailedPredicateException) {
1178                         FailedPredicateException e = (FailedPredicateException) re;
1179                         String ruleName = recognizer.getRuleNames()[recognizer.getContext().getRuleIndex()];
1180
1181                         message = "failed predicate " + ruleName + ": " + e.getMessage();
1182                 }
1183
1184                 return message;
1185         }
1186
1187         protected String quote(String text) {
1188                 return "'" + text + "'";
1189         }
1190
1191         protected String getTokenDisplayString(Token token) {
1192                 String string;
1193
1194                 if (token == null) {
1195                         string = "[no token]";
1196                 }
1197                 else {
1198                         String text = token.getText();
1199
1200                         if (text == null) {
1201                                 if (token.getType() == Token.EOF ) {
1202                                         string = "end of text";
1203                                 }
1204                                 else {
1205                                         string = "[" + token.getType() + "]";
1206                                 }
1207                         }
1208                         else {
1209                                 string = quote(text);
1210                         }
1211                 }
1212
1213                 return string;
1214         }
1215
1216         protected String stripEndLetters(String input) {
1217                 return input.replaceAll("[^\\d]+$", "");
1218         }
1219
1220         public static void main(String[] args) {
1221                 StructuredDateEvaluator evaluator = new ANTLRStructuredDateEvaluator();
1222
1223                 for (String displayDate : args) {
1224                         try {
1225                                 evaluator.evaluate(displayDate);
1226                         } catch (StructuredDateFormatException e) {
1227                                 e.printStackTrace();
1228                         }
1229                 }
1230         }
1231 }