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