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