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