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