]> git.aero2k.de Git - tmp/jakarta-migration.git/blob
fcd7da8be6e3e38b0388a889f36d93aa512b976c
[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.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 StructuredDateInternal 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 StructuredDateInternal evaluate(String displayDate) throws StructuredDateFormatException {
107                 stack = new Stack<Object>();
108
109                 result = new StructuredDateInternal();
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                 Era era = null;
460
461                 boolean eraLast = stack.peek() instanceof Integer;
462
463                 // Declare nums
464                 Integer num1;
465                 Integer num2;
466                 Integer num3;
467
468                 if (!eraLast) {
469                         era = (Era) stack.pop(); // damn eras
470                 }
471
472                 num1 = (Integer) stack.pop(); // year or day
473                 num2 = (Integer) stack.pop(); // month
474                 num3 = (Integer) stack.pop(); //  day
475                 
476                 if (eraLast) {
477                         era =  (Era) stack.pop();
478                 }
479
480                 Integer dayOfMonth = num1;
481                 Integer numMonth = num2;
482                 Integer year = num3;
483                 
484                 if (DateUtils.isValidDate(num3, num2, num1, era)) {
485                         // Do nothing, already in the right format (Era Year Month Day)
486                 } else if (DateUtils.isValidDate(num1, num2, num3, era)) {
487                         // Use other format: Day Month Year Era
488                         dayOfMonth = num3;
489                         year = num1;
490                 }
491
492
493                 stack.push(year);
494                 stack.push(numMonth);
495                 stack.push(dayOfMonth);
496                 stack.push(era);
497
498                 if (dayOfMonth > 31 || dayOfMonth <= 0) {
499                         throw new StructuredDateFormatException("unexpected day of month '" + Integer.toString(dayOfMonth) + "'");
500                 }
501                 if (year == 0) {
502                         throw new StructuredDateFormatException("unexpected year '" + Integer.toString(year) + "'");
503                 }
504         }
505
506         @Override
507         public void exitMonth(MonthContext ctx) {
508                 if (ctx.exception != null) return;
509
510                 Era era = (Era) stack.pop();
511                 Integer year = (Integer) stack.pop();
512                 Integer numMonth = (Integer) stack.pop();
513
514                 stack.push(new Date(year, numMonth, 1, era));
515                 stack.push(new Date(year, numMonth, DateUtils.getDaysInMonth(numMonth, year, era), era));
516         }
517
518         @Override
519         public void exitInvMonthYear(InvMonthYearContext ctx) {
520                 if (ctx.exception != null) return;
521
522                 // Invert the arguments.
523
524                 Integer numMonth = (Integer) stack.pop();
525                 Integer year = (Integer) stack.pop();
526                 Era era = (Era) stack.pop();
527
528                 stack.push(numMonth);
529                 stack.push(year);
530                 stack.push(era);
531         }
532
533         @Override
534         public void exitYearSpanningWinter(YearSpanningWinterContext ctx) {
535                 if (ctx.exception != null) return;
536
537                 Era era = (Era) stack.pop();
538                 Integer endYear = (Integer) stack.pop();
539                 Integer startYear = (Integer) stack.pop();
540
541                 stack.push(new Date(startYear, 12, 1).withEra(era));
542                 stack.push(DateUtils.getQuarterYearEndDate(1, endYear, era).withEra(era));
543         }
544
545         @Override
546         public void exitPartialYear(PartialYearContext ctx) {
547                 if (ctx.exception != null) return;
548
549                 Era era = (Era) stack.pop();
550                 Integer year = (Integer) stack.pop();
551                 Part part = (Part) stack.pop();
552
553                 stack.push(DateUtils.getPartialYearStartDate(part, year).withEra(era));
554                 stack.push(DateUtils.getPartialYearEndDate(part, year, era).withEra(era));
555         }
556
557         @Override
558         public void exitQuarterYear(QuarterYearContext ctx) {
559                 if (ctx.exception != null) return;
560
561                 Era era = (Era) stack.pop();
562                 Integer year = (Integer) stack.pop();
563                 Integer quarter = (Integer) stack.pop();
564
565                 stack.push(DateUtils.getQuarterYearStartDate(quarter, year).withEra(era));
566                 stack.push(DateUtils.getQuarterYearEndDate(quarter, year, era).withEra(era));
567         }
568
569         @Override
570         public void exitHalfYear(HalfYearContext ctx) {
571                 if (ctx.exception != null) return;
572
573                 Era era = (Era) stack.pop();
574                 Integer year = (Integer) stack.pop();
575                 Integer half = (Integer) stack.pop();
576
577                 stack.push(DateUtils.getHalfYearStartDate(half, year).withEra(era));
578                 stack.push(DateUtils.getHalfYearEndDate(half, year, era).withEra(era));
579         }
580
581         @Override
582         public void exitInvSeasonYear(InvSeasonYearContext ctx) {
583                 if (ctx.exception != null) return;
584
585                 // Invert the arguments.
586
587                 Integer quarter = (Integer) stack.pop();
588                 Integer year = (Integer) stack.pop();
589                 Era era = (Era) stack.pop();
590
591                 stack.push(quarter);
592                 stack.push(year);
593                 stack.push(era);
594         }
595
596         @Override
597         public void exitYear(YearContext ctx) {
598                 if (ctx.exception != null) return;
599
600                 Era era = (Era) stack.pop();
601                 Integer year = (Integer) stack.pop();
602
603                 stack.push(new Date(year, 1, 1, era));
604                 stack.push(new Date(year, 12, 31, era));
605         }
606
607         @Override
608         public void exitPartialDecade(PartialDecadeContext ctx) {
609                 if (ctx.exception != null) return;
610
611                 Era era = (Era) stack.pop();
612                 Integer year = (Integer) stack.pop();
613                 Part part = (Part) stack.pop();
614
615                 if (era != null) {
616                         // If the era was explicitly specified, the start and end years
617                         // may be calculated now.
618
619                         stack.push(DateUtils.getPartialDecadeStartDate(year, part, era));
620                         stack.push(DateUtils.getPartialDecadeEndDate(year, part, era));
621                 }
622                 else {
623                         // If the era was not explicitly specified, the start and end years
624                         // can't be calculated yet. The calculation must be deferred until
625                         // later. For example, this partial decade may be the start of a hyphenated
626                         // range, where the era will be inherited from the era of the end of
627                         // the range; this era won't be known until farther up the parse tree,
628                         // when both sides of the range will have been parsed.
629
630                         stack.push(new DeferredPartialDecadeStartDate(year, part));
631                         stack.push(new DeferredPartialDecadeEndDate(year, part));
632                 }
633         }
634
635         @Override
636         public void exitDecade(DecadeContext ctx) {
637                 if (ctx.exception != null) return;
638
639                 Era era = (Era) stack.pop();
640                 Integer year = (Integer) stack.pop();
641
642                 // Calculate the start and end year of the decade, which depends on the era.
643
644                 if (era != null) {
645                         // If the era was explicitly specified, the start and end years
646                         // may be calculated now.
647
648                         stack.push(DateUtils.getDecadeStartDate(year, era));
649                         stack.push(DateUtils.getDecadeEndDate(year, era));
650                 }
651                 else {
652                         // If the era was not explicitly specified, the start and end years
653                         // can't be calculated yet. The calculation must be deferred until
654                         // later. For example, this decade may be the start of a hyphenated
655                         // range, where the era will be inherited from the era of the end of
656                         // the range; this era won't be known until farther up the parse tree,
657                         // when both sides of the range will have been parsed.
658
659                         stack.push(new DeferredDecadeStartDate(year));
660                         stack.push(new DeferredDecadeEndDate(year));
661                 }
662         }
663
664         @Override
665         public void exitPartialCentury(PartialCenturyContext ctx) {
666                 if (ctx.exception != null) return;
667
668                 Era era = (Era) stack.pop();
669                 Integer year = (Integer) stack.pop();
670                 Part part = (Part) stack.pop();
671
672                 if (era != null) {
673                         // If the era was explicitly specified, the start and end years
674                         // may be calculated now.
675
676                         stack.push(DateUtils.getPartialCenturyStartDate(year, part, era));
677                         stack.push(DateUtils.getPartialCenturyEndDate(year, part, era));
678                 }
679                 else {
680                         // If the era was not explicitly specified, the start and end years
681                         // can't be calculated yet. The calculation must be deferred until
682                         // later. For example, this partial century may be the start of a hyphenated
683                         // range, where the era will be inherited from the era of the end of
684                         // the range; this era won't be known until farther up the parse tree,
685                         // when both sides of the range will have been parsed.
686
687                         stack.push(new DeferredPartialCenturyStartDate(year, part));
688                         stack.push(new DeferredPartialCenturyEndDate(year, part));
689                 }
690         }
691
692         @Override
693         public void exitQuarterCentury(QuarterCenturyContext ctx) {
694                 if (ctx.exception != null) return;
695
696                 Era era = (Era) stack.pop();
697                 Integer year = (Integer) stack.pop();
698                 Integer quarter = (Integer) stack.pop();
699
700                 if (era != null) {
701                         // If the era was explicitly specified, the start and end years
702                         // may be calculated now.
703
704                         stack.push(DateUtils.getQuarterCenturyStartDate(year, quarter, era));
705                         stack.push(DateUtils.getQuarterCenturyEndDate(year, quarter, era));
706                 }
707                 else {
708                         // If the era was not explicitly specified, the start and end years
709                         // can't be calculated yet. The calculation must be deferred until
710                         // later. For example, this century may be the start of a hyphenated
711                         // range, where the era will be inherited from the era of the end of
712                         // the range; this era won't be known until farther up the parse tree,
713                         // when both sides of the range will have been parsed.
714
715                         stack.push(new DeferredQuarterCenturyStartDate(year, quarter));
716                         stack.push(new DeferredQuarterCenturyEndDate(year, quarter));
717                 }
718         }
719
720         @Override
721         public void exitHalfCentury(HalfCenturyContext ctx) {
722                 if (ctx.exception != null) return;
723
724                 Era era = (Era) stack.pop();
725                 Integer year = (Integer) stack.pop();
726                 Integer half = (Integer) stack.pop();
727
728                 if (era != null) {
729                         // If the era was explicitly specified, the start and end years
730                         // may be calculated now.
731
732                         stack.push(DateUtils.getHalfCenturyStartDate(year, half, era));
733                         stack.push(DateUtils.getHalfCenturyEndDate(year, half, era));
734                 }
735                 else {
736                         // If the era was not explicitly specified, the start and end years
737                         // can't be calculated yet. The calculation must be deferred until
738                         // later. For example, this half century may be the start of a hyphenated
739                         // range, where the era will be inherited from the era of the end of
740                         // the range; this era won't be known until farther up the parse tree,
741                         // when both sides of the range will have been parsed.
742
743                         stack.push(new DeferredHalfCenturyStartDate(year, half));
744                         stack.push(new DeferredHalfCenturyEndDate(year, half));
745                 }
746         }
747
748         @Override
749         public void exitCentury(CenturyContext ctx) {
750                 if (ctx.exception != null) return;
751
752                 Era era = (Era) stack.pop();
753                 Integer year = (Integer) stack.pop();
754
755                 if (era != null) {
756                         // If the era was explicitly specified, the start and end years
757                         // may be calculated now.
758
759                         stack.push(DateUtils.getCenturyStartDate(year, era));
760                         stack.push(DateUtils.getCenturyEndDate(year, era));
761                 }
762                 else {
763                         // If the era was not explicitly specified, the start and end years
764                         // can't be calculated yet. The calculation must be deferred until
765                         // later. For example, this quarter century may be the start of a hyphenated
766                         // range, where the era will be inherited from the era of the end of
767                         // the range; this era won't be known until farther up the parse tree,
768                         // when both sides of the range will have been parsed.
769
770                         stack.push(new DeferredCenturyStartDate(year));
771                         stack.push(new DeferredCenturyEndDate(year));
772                 }
773         }
774
775         @Override
776         public void exitMillennium(MillenniumContext ctx) {
777                 if (ctx.exception != null) return;
778
779                 Era era = (Era) stack.pop();
780                 Integer n = (Integer) stack.pop();
781
782                 if (era != null) {
783                         // If the era was explicitly specified, the start and end years
784                         // may be calculated now.
785
786                         stack.push(DateUtils.getMillenniumStartDate(n, era));
787                         stack.push(DateUtils.getMillenniumEndDate(n, era));
788                 }
789                 else {
790                         // If the era was not explicitly specified, the start and end years
791                         // can't be calculated yet. The calculation must be deferred until
792                         // later. For example, this millennium may be the start of a hyphenated
793                         // range, where the era will be inherited from the era of the end of
794                         // the range; this era won't be known until farther up the parse tree,
795                         // when both sides of the range will have been parsed.
796
797                         stack.push(new DeferredMillenniumStartDate(n));
798                         stack.push(new DeferredMillenniumEndDate(n));
799                 }
800         }
801
802         @Override
803         public void exitStrCentury(StrCenturyContext ctx) {
804                 if (ctx.exception != null) return;
805
806                 Integer n = (Integer) stack.pop();
807
808                 // Convert the nth number to a year number,
809                 // and push on the stack.
810
811                 Integer year = DateUtils.nthCenturyToYear(n);
812
813                 stack.push(year);
814         }
815
816         @Override
817         public void exitNumCentury(NumCenturyContext ctx) {
818                 if (ctx.exception != null) return;
819
820                 // Convert the string to a number,
821                 // and push on the stack.
822
823                 Integer year = new Integer(stripEndLetters(ctx.HUNDREDS().getText()));
824
825                 if (year == 0) {
826                         throw new StructuredDateFormatException("unexpected century '" + ctx.HUNDREDS().getText() + "'");
827                 }
828
829                 stack.push(year);
830         }
831
832         @Override
833         public void exitNumDecade(NumDecadeContext ctx) {
834                 if (ctx.exception != null) return;
835
836                 // Convert the string to a number,
837                 // and push on the stack.
838
839                 Integer year = new Integer(stripEndLetters(ctx.TENS().getText()));
840
841                 if (year == 0) {
842                         throw new StructuredDateFormatException("unexpected decade '" + ctx.TENS().getText() + "'");
843                 }
844
845                 stack.push(year);
846         }
847
848         @Override
849         public void exitNumYear(NumYearContext ctx) {
850                 if (ctx.exception != null) return;
851
852                 // Convert the string to a number,
853                 // and push on the stack.
854
855                 Integer year = new Integer(ctx.NUMBER().getText());
856
857                 if (year == 0) {
858                         throw new StructuredDateFormatException("unexpected year '" + ctx.NUMBER().getText() + "'");
859                 }
860
861                 stack.push(year);
862         }
863
864         @Override
865         public void exitNumMonth(NumMonthContext ctx) {
866                 if (ctx.exception != null) return;
867
868                 // Convert the string a number,
869                 // and push on the stack.
870
871                 Integer month = new Integer(ctx.NUMBER().getText());
872
873                 if (month < 1 || month > 12) {
874                         throw new StructuredDateFormatException("unexpected month '" + ctx.NUMBER().getText() + "'");
875                 }
876
877                 stack.push(month);
878         }
879
880         @Override
881         public void exitNthHalf(NthHalfContext ctx) {
882                 if (ctx.exception != null) return;
883
884                 // Convert LAST to a number (the last half
885                 // is the 2nd). If this rule matched the
886                 // alternative with nth instead of LAST,
887                 // the nth handler will already have pushed
888                 // a number on the stack.
889
890                 if (ctx.LAST() != null) {
891                         stack.push(new Integer(2));
892                 }
893
894                 // Check for a valid half.
895
896                 Integer n = (Integer) stack.peek();
897
898                 if (n < 1 || n > 2) {
899                         throw new StructuredDateFormatException("unexpected half '" + n + "'");
900                 }
901         }
902
903         @Override
904         public void exitNthQuarter(NthQuarterContext ctx) {
905                 if (ctx.exception != null) return;
906
907                 // Convert LAST to a number (the last quarter
908                 // is the 4th). If this rule matched the
909                 // alternative with nth instead of LAST,
910                 // the nth handler will already have pushed
911                 // a number on the stack.
912
913                 if (ctx.LAST() != null) {
914                         stack.push(new Integer(4));
915                 }
916
917                 // Check for a valid quarter.
918
919                 Integer n = (Integer) stack.peek();
920
921                 if (n < 1 || n > 4) {
922                         throw new StructuredDateFormatException("unexpected quarter '" + n + "'");
923                 }
924         }
925
926         @Override
927         public void exitNth(NthContext ctx) {
928                 if (ctx.exception != null) return;
929
930                 // Convert the string to a number,
931                 // and push on the stack.
932
933                 Integer n = null;
934
935                 if (ctx.NTHSTR() != null) {
936                         n = new Integer(stripEndLetters(ctx.NTHSTR().getText()));
937                 }
938                 else if (ctx.FIRST() != null) {
939                         n = 1;
940                 }
941                 else if (ctx.SECOND() != null) {
942                         n = 2;
943                 }
944                 else if (ctx.THIRD() != null) {
945                         n = 3;
946                 }
947                 else if (ctx.FOURTH() != null) {
948                         n = 4;
949                 }
950
951                 stack.push(n);
952         }
953
954         @Override
955         public void exitStrMonth(StrMonthContext ctx) {
956                 if (ctx.exception != null) return;
957
958                 // Convert the month name to a number,
959                 // and push on the stack.
960
961                 TerminalNode monthNode = ctx.MONTH();
962
963                 if (monthNode == null) {
964                         monthNode = ctx.SHORTMONTH();
965                 }
966
967                 String monthStr = monthNode.getText();
968
969                 stack.push(DateUtils.getMonthByName(monthStr));
970         }
971
972         @Override
973         public void exitStrSeason(StrSeasonContext ctx) {
974                 if (ctx.exception != null) return;
975
976                 // Convert the season to a quarter number,
977                 // and push on the stack.
978
979                 Integer quarter = null;
980
981                 if (ctx.WINTER() != null) {
982                         quarter = 1;
983                 }
984                 else if (ctx.SPRING() != null) {
985                         quarter = 2;
986                 }
987                 else if (ctx.SUMMER() != null) {
988                         quarter = 3;
989                 }
990                 else if (ctx.FALL() != null) {
991                         quarter = 4;
992                 }
993
994                 stack.push(quarter);
995         }
996
997         @Override
998         public void exitAllOrPartOf(AllOrPartOfContext ctx) {
999                 if (ctx.exception != null) return;
1000
1001                 // If a part was specified, it will have been
1002                 // pushed on the stack in exitPartOf(). If not,
1003                 // push null on the stack.
1004
1005                 if (ctx.partOf() == null) {
1006                         stack.push(null);
1007                 }
1008         }
1009
1010         @Override
1011         public void exitPartOf(PartOfContext ctx) {
1012                 if (ctx.exception != null) return;
1013
1014                 // Convert the token to a Part,
1015                 // and push on the stack.
1016
1017                 Part part = null;
1018
1019                 if (ctx.EARLY() != null) {
1020                         part = Part.EARLY;
1021                 }
1022                 else if (ctx.MIDDLE() != null) {
1023                         part = Part.MIDDLE;
1024                 }
1025                 else if (ctx.LATE() != null) {
1026                         part = Part.LATE;
1027                 }
1028
1029                 stack.push(part);
1030         }
1031
1032         @Override
1033         public void exitEra(EraContext ctx) {
1034                 if (ctx.exception != null) return;
1035
1036                 // Convert the token to an Era,
1037                 // and push on the stack.
1038
1039                 Era era = null;
1040
1041                 if (ctx.BC() != null) {
1042                         era = Era.BCE;
1043                 }
1044                 else if (ctx.AD() != null) {
1045                         era = Era.CE;
1046                 }
1047
1048                 stack.push(era);
1049         }
1050
1051         @Override
1052         public void exitNumDayOfMonth(NumDayOfMonthContext ctx) {
1053                 if (ctx.exception != null) return;
1054
1055                 // Convert the numeric string to an Integer,
1056                 // and push on the stack.
1057
1058                 Integer dayOfMonth = new Integer(ctx.NUMBER().getText());
1059
1060                 if (dayOfMonth == 0 || dayOfMonth > 31) {
1061                         throw new StructuredDateFormatException("unexpected day of month '" + ctx.NUMBER().getText() + "'");
1062                 }
1063
1064                 stack.push(dayOfMonth);
1065         }
1066
1067         @Override
1068         public void exitNum(NumContext ctx) {
1069                 if (ctx.exception != null) return;
1070
1071                 // Convert the numeric string to an Integer,
1072                 // and push on the stack.
1073
1074                 Integer num = new Integer(ctx.NUMBER().getText());
1075
1076                 stack.push(num);
1077         }
1078
1079         protected String getErrorMessage(RecognitionException re) {
1080                 String message = "";
1081
1082                 Parser recognizer = (Parser) re.getRecognizer();
1083                 TokenStream tokens = recognizer.getInputStream();
1084
1085                 if (re instanceof NoViableAltException) {
1086                         NoViableAltException e = (NoViableAltException) re;
1087                         Token startToken = e.getStartToken();
1088                         String input = (startToken.getType() == Token.EOF ) ? "end of text" : quote(tokens.getText(startToken, e.getOffendingToken()));
1089
1090                         message = "no viable date format found at " + input;
1091                 }
1092                 else if (re instanceof InputMismatchException) {
1093                         InputMismatchException e = (InputMismatchException) re;
1094                         message = "did not expect " + getTokenDisplayString(e.getOffendingToken()) + " while looking for " +
1095                                   e.getExpectedTokens().toString(recognizer.getTokenNames());
1096                 }
1097                 else if (re instanceof FailedPredicateException) {
1098                         FailedPredicateException e = (FailedPredicateException) re;
1099                         String ruleName = recognizer.getRuleNames()[recognizer.getContext().getRuleIndex()];
1100
1101                         message = "failed predicate " + ruleName + ": " + e.getMessage();
1102                 }
1103
1104                 return message;
1105         }
1106
1107         protected String quote(String text) {
1108                 return "'" + text + "'";
1109         }
1110
1111         protected String getTokenDisplayString(Token token) {
1112                 String string;
1113
1114                 if (token == null) {
1115                         string = "[no token]";
1116                 }
1117                 else {
1118                         String text = token.getText();
1119
1120                         if (text == null) {
1121                                 if (token.getType() == Token.EOF ) {
1122                                         string = "end of text";
1123                                 }
1124                                 else {
1125                                         string = "[" + token.getType() + "]";
1126                                 }
1127                         }
1128                         else {
1129                                 string = quote(text);
1130                         }
1131                 }
1132
1133                 return string;
1134         }
1135
1136         protected String stripEndLetters(String input) {
1137                 return input.replaceAll("[^\\d]+$", "");
1138         }
1139
1140         public static void main(String[] args) {
1141                 StructuredDateEvaluator evaluator = new ANTLRStructuredDateEvaluator();
1142
1143                 for (String displayDate : args) {
1144                         try {
1145                                 evaluator.evaluate(displayDate);
1146                         } catch (StructuredDateFormatException e) {
1147                                 e.printStackTrace();
1148                         }
1149                 }
1150         }
1151 }