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