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