1 5 package com.opensymphony.oscache.util; 6 7 import java.text.ParseException ; 8 9 import java.util.*; 10 import java.util.Calendar ; 11 12 20 public class FastCronParser { 21 private static final int NUMBER_OF_CRON_FIELDS = 5; 22 private static final int MINUTE = 0; 23 private static final int HOUR = 1; 24 private static final int DAY_OF_MONTH = 2; 25 private static final int MONTH = 3; 26 private static final int DAY_OF_WEEK = 4; 27 28 private static final int[] MIN_VALUE = {0, 0, 1, 1, 0}; 31 private static final int[] MAX_VALUE = {59, 23, 31, 12, 6}; 32 33 37 private static final int[] DAYS_IN_MONTH = { 38 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 39 }; 40 41 44 private String cronExpression = null; 45 46 56 private long[] lookup = { 57 Long.MAX_VALUE, Long.MAX_VALUE, Long.MAX_VALUE, Long.MAX_VALUE, 58 Long.MAX_VALUE 59 }; 60 61 65 private int[] lookupMax = {-1, -1, -1, -1, -1}; 66 67 71 private int[] lookupMin = { 72 Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE, 73 Integer.MAX_VALUE, Integer.MAX_VALUE 74 }; 75 76 80 public FastCronParser() { 81 } 82 83 88 public FastCronParser(String cronExpression) throws ParseException { 89 setCronExpression(cronExpression); 90 } 91 92 99 public void setCronExpression(String cronExpression) throws ParseException { 100 if (cronExpression == null) { 101 throw new IllegalArgumentException ("Cron time expression cannot be null"); 102 } 103 104 this.cronExpression = cronExpression; 105 parseExpression(cronExpression); 106 } 107 108 113 public String getCronExpression() { 114 return this.cronExpression; 115 } 116 117 126 public boolean hasMoreRecentMatch(long time) { 127 return time < getTimeBefore(System.currentTimeMillis()); 128 } 129 130 138 public long getTimeBefore(long time) { 139 Calendar cal = new GregorianCalendar(); 142 cal.setTimeInMillis(time); 143 144 int minute = cal.get(Calendar.MINUTE); 145 int hour = cal.get(Calendar.HOUR_OF_DAY); 146 int dayOfMonth = cal.get(Calendar.DAY_OF_MONTH); 147 int month = cal.get(Calendar.MONTH) + 1; int year = cal.get(Calendar.YEAR); 149 150 long validMinutes = lookup[MINUTE]; 151 long validHours = lookup[HOUR]; 152 long validDaysOfMonth = lookup[DAY_OF_MONTH]; 153 long validMonths = lookup[MONTH]; 154 long validDaysOfWeek = lookup[DAY_OF_WEEK]; 155 156 boolean haveDOM = validDaysOfMonth != Long.MAX_VALUE; 158 boolean haveDOW = validDaysOfWeek != Long.MAX_VALUE; 159 160 boolean skippedNonLeapYear = false; 161 162 while (true) { 163 boolean retry = false; 164 165 if (month < 1) { 167 month += 12; 168 year--; 169 } 170 171 boolean found = false; 173 174 if (validMonths != Long.MAX_VALUE) { 175 for (int i = month + 11; i > (month - 1); i--) { 176 int testMonth = (i % 12) + 1; 177 178 if (((1L << (testMonth - 1)) & validMonths) != 0) { 180 if ((testMonth > month) || skippedNonLeapYear) { 181 year--; 182 } 183 184 int numDays = numberOfDaysInMonth(testMonth, year); 186 187 if (!haveDOM || (numDays >= lookupMin[DAY_OF_MONTH])) { 188 if ((month != testMonth) || skippedNonLeapYear) { 189 dayOfMonth = (numDays <= lookupMax[DAY_OF_MONTH]) ? numDays : lookupMax[DAY_OF_MONTH]; 191 hour = lookupMax[HOUR]; 192 minute = lookupMax[MINUTE]; 193 month = testMonth; 194 } 195 196 found = true; 197 break; 198 } 199 } 200 } 201 202 skippedNonLeapYear = false; 203 204 if (!found) { 205 skippedNonLeapYear = true; 207 continue; 208 } 209 } 210 211 if (dayOfMonth < 1) { 213 month--; 214 dayOfMonth += numberOfDaysInMonth(month, year); 215 hour = lookupMax[HOUR]; 216 continue; 217 } 218 219 if (haveDOM && !haveDOW) { 222 int daysInThisMonth = numberOfDaysInMonth(month, year); 223 int daysInPreviousMonth = numberOfDaysInMonth(month - 1, year); 224 225 for (int i = dayOfMonth + 30; i > (dayOfMonth - 1); i--) { 227 int testDayOfMonth = (i % 31) + 1; 228 229 if ((testDayOfMonth <= dayOfMonth) && (testDayOfMonth > daysInThisMonth)) { 231 continue; 232 } 233 234 if ((testDayOfMonth > dayOfMonth) && (testDayOfMonth > daysInPreviousMonth)) { 235 continue; 236 } 237 238 if (((1L << (testDayOfMonth - 1)) & validDaysOfMonth) != 0) { 239 if (testDayOfMonth > dayOfMonth) { 240 month--; 242 retry = true; 243 } 244 245 if (dayOfMonth != testDayOfMonth) { 246 hour = lookupMax[HOUR]; 247 minute = lookupMax[MINUTE]; 248 } 249 250 dayOfMonth = testDayOfMonth; 251 break; 252 } 253 } 254 255 if (retry) { 256 continue; 257 } 258 } else if (haveDOW && !haveDOM) { 260 int daysLost = 0; 261 int currentDOW = dayOfWeek(dayOfMonth, month, year); 262 263 for (int i = currentDOW + 7; i > currentDOW; i--) { 264 int testDOW = i % 7; 265 266 if (((1L << testDOW) & validDaysOfWeek) != 0) { 267 dayOfMonth -= daysLost; 268 269 if (dayOfMonth < 1) { 270 month--; 272 dayOfMonth += numberOfDaysInMonth(month, year); 273 retry = true; 274 } 275 276 if (currentDOW != testDOW) { 277 hour = lookupMax[HOUR]; 278 minute = lookupMax[MINUTE]; 279 } 280 281 break; 282 } 283 284 daysLost++; 285 } 286 287 if (retry) { 288 continue; 289 } 290 } 291 292 if (hour < 0) { 294 hour += 24; 295 dayOfMonth--; 296 continue; 297 } 298 299 if (validHours != Long.MAX_VALUE) { 301 for (int i = hour + 24; i > hour; i--) { 303 int testHour = i % 24; 304 305 if (((1L << testHour) & validHours) != 0) { 306 if (testHour > hour) { 307 dayOfMonth--; 309 retry = true; 310 } 311 312 if (hour != testHour) { 313 minute = lookupMax[MINUTE]; 314 } 315 316 hour = testHour; 317 break; 318 } 319 } 320 321 if (retry) { 322 continue; 323 } 324 } 325 326 if (validMinutes != Long.MAX_VALUE) { 328 for (int i = minute + 60; i > minute; i--) { 330 int testMinute = i % 60; 331 332 if (((1L << testMinute) & validMinutes) != 0) { 333 if (testMinute > minute) { 334 hour--; 336 retry = true; 337 } 338 339 minute = testMinute; 340 break; 341 } 342 } 343 344 if (retry) { 345 continue; 346 } 347 } 348 349 break; 350 } 351 352 cal.set(Calendar.YEAR, year); 354 cal.set(Calendar.MONTH, month - 1); cal.set(Calendar.DAY_OF_MONTH, dayOfMonth); 356 cal.set(Calendar.HOUR_OF_DAY, hour); 357 cal.set(Calendar.MINUTE, minute); 358 cal.set(Calendar.SECOND, 0); 359 cal.set(Calendar.MILLISECOND, 0); 360 361 return cal.getTime().getTime(); 362 } 363 364 371 private void parseExpression(String expression) throws ParseException { 372 try { 373 for (int i = 0; i < lookup.length; lookup[i++] = 0) { 375 lookupMin[i] = Integer.MAX_VALUE; 376 lookupMax[i] = -1; 377 } 378 379 char[][] token = new char[NUMBER_OF_CRON_FIELDS][]; 381 382 int length = expression.length(); 385 char[] expr = new char[length]; 386 expression.getChars(0, length, expr, 0); 387 388 int field = 0; 389 int startIndex = 0; 390 boolean inWhitespace = true; 391 392 for (int i = 0; (i < length) && (field < NUMBER_OF_CRON_FIELDS); 394 i++) { 395 boolean haveChar = (expr[i] != ' ') && (expr[i] != '\t'); 396 397 if (haveChar) { 398 if (inWhitespace) { 400 startIndex = i; inWhitespace = false; 402 } 403 } 404 405 if (i == (length - 1)) { i++; 407 } 408 409 if (!(haveChar || inWhitespace) || (i == length)) { 410 token[field] = new char[i - startIndex]; 412 System.arraycopy(expr, startIndex, token[field], 0, i - startIndex); 413 inWhitespace = true; 414 field++; 415 } 416 } 417 418 if (field < NUMBER_OF_CRON_FIELDS) { 419 throw new ParseException ("Unexpected end of expression while parsing \"" + expression + "\". Cron expressions require 5 separate fields.", length); 420 } 421 422 for (field = 0; field < NUMBER_OF_CRON_FIELDS; field++) { 425 startIndex = 0; 426 427 boolean inDelimiter = true; 428 429 int elementLength = token[field].length; 431 432 for (int i = 0; i < elementLength; i++) { 433 boolean haveElement = token[field][i] != ','; 434 435 if (haveElement) { 436 if (inDelimiter) { 438 startIndex = i; 439 inDelimiter = false; 440 } 441 } 442 443 if (i == (elementLength - 1)) { i++; 445 } 446 447 if (!(haveElement || inDelimiter) || (i == elementLength)) { 448 char[] element = new char[i - startIndex]; 450 System.arraycopy(token[field], startIndex, element, 0, i - startIndex); 451 452 storeExpressionValues(element, field); 454 455 inDelimiter = true; 456 } 457 } 458 459 if (lookup[field] == 0) { 460 throw new ParseException ("Token " + new String (token[field]) + " contains no valid entries for this field.", 0); 461 } 462 } 463 464 switch (lookupMin[DAY_OF_MONTH]) { 466 case 31: 467 lookup[MONTH] &= (0xFFF - 0x528); case 30: 469 lookup[MONTH] &= (0xFFF - 0x2); 471 if (lookup[MONTH] == 0) { 472 throw new ParseException ("The cron expression \"" + expression + "\" will never match any months - the day of month field is out of range.", 0); 473 } 474 } 475 476 if ((lookup[DAY_OF_MONTH] != Long.MAX_VALUE) && (lookup[DAY_OF_WEEK] != Long.MAX_VALUE)) { 478 throw new ParseException ("The cron expression \"" + expression + "\" is invalid. Having both a day-of-month and day-of-week field is not supported.", 0); 479 } 480 } catch (Exception e) { 481 if (e instanceof ParseException ) { 482 throw (ParseException ) e; 483 } else { 484 throw new ParseException ("Illegal cron expression format (" + e.toString() + ")", 0); 485 } 486 } 487 } 488 489 500 private void storeExpressionValues(char[] element, int field) throws ParseException { 501 int i = 0; 502 503 int start = -99; 504 int end = -99; 505 int interval = -1; 506 boolean wantValue = true; 507 boolean haveInterval = false; 508 509 while ((interval < 0) && (i < element.length)) { 510 char ch = element[i++]; 511 512 if ((i == 1) && (ch == '*')) { 514 if (i >= element.length) { 516 addToLookup(-1, -1, field, 1); 517 return; 518 } 519 520 start = -1; 521 end = -1; 522 wantValue = false; 523 continue; 524 } 525 526 if (wantValue) { 527 if ((ch >= '0') && (ch <= '9')) { 529 ValueSet vs = getValue(ch - '0', element, i); 530 531 if (start == -99) { 532 start = vs.value; 533 } else if (!haveInterval) { 534 end = vs.value; 535 } else { 536 if (end == -99) { 537 end = MAX_VALUE[field]; 538 } 539 540 interval = vs.value; 541 } 542 543 i = vs.pos; 544 wantValue = false; 545 continue; 546 } 547 548 if (!haveInterval && (end == -99)) { 549 if (field == MONTH) { 551 if (start == -99) { 552 start = getMonthVal(ch, element, i++); 553 } else { 554 end = getMonthVal(ch, element, i++); 555 } 556 557 wantValue = false; 558 559 while (++i < element.length) { 561 int c = element[i] | 0x20; 562 563 if ((c < 'a') || (c > 'z')) { 564 break; 565 } 566 } 567 568 continue; 569 } else if (field == DAY_OF_WEEK) { 570 if (start == -99) { 571 start = getDayOfWeekVal(ch, element, i++); 572 } else { 573 end = getDayOfWeekVal(ch, element, i++); 574 } 575 576 wantValue = false; 577 578 while (++i < element.length) { 580 int c = element[i] | 0x20; 581 582 if ((c < 'a') || (c > 'z')) { 583 break; 584 } 585 } 586 587 continue; 588 } 589 } 590 } else { 591 if ((ch == '-') && (start != -99) && (end == -99)) { 593 wantValue = true; 594 continue; 595 } 596 597 if ((ch == '/') && (start != -99)) { 599 wantValue = true; 600 haveInterval = true; 601 continue; 602 } 603 } 604 605 throw makeParseException("Invalid character encountered while parsing element", element, i); 606 } 607 608 if (element.length > i) { 609 throw makeParseException("Extraneous characters found while parsing element", element, i); 610 } 611 612 if (end == -99) { 613 end = start; 614 } 615 616 if (interval < 0) { 617 interval = 1; 618 } 619 620 addToLookup(start, end, field, interval); 621 } 622 623 632 private ValueSet getValue(int value, char[] element, int i) { 633 ValueSet result = new ValueSet(); 634 result.value = value; 635 636 if (i >= element.length) { 637 result.pos = i; 638 return result; 639 } 640 641 char ch = element[i]; 642 643 while ((ch >= '0') && (ch <= '9')) { 644 result.value = (result.value * 10) + (ch - '0'); 645 646 if (++i >= element.length) { 647 break; 648 } 649 650 ch = element[i]; 651 } 652 653 result.pos = i; 654 655 return result; 656 } 657 658 673 private void addToLookup(int start, int end, int field, int interval) throws ParseException { 674 if (start == end) { 676 if (start < 0) { 677 start = lookupMin[field] = MIN_VALUE[field]; 679 end = lookupMax[field] = MAX_VALUE[field]; 680 681 if (interval <= 1) { 682 lookup[field] = Long.MAX_VALUE; 683 return; 684 } 685 } else { 686 if (start < MIN_VALUE[field]) { 688 throw new ParseException ("Value " + start + " in field " + field + " is lower than the minimum allowable value for this field (min=" + MIN_VALUE[field] + ")", 0); 689 } else if (start > MAX_VALUE[field]) { 690 throw new ParseException ("Value " + start + " in field " + field + " is higher than the maximum allowable value for this field (max=" + MAX_VALUE[field] + ")", 0); 691 } 692 } 693 } else { 694 if (start > end) { 696 end ^= start; 697 start ^= end; 698 end ^= start; 699 } 700 701 if (start < 0) { 702 start = MIN_VALUE[field]; 703 } else if (start < MIN_VALUE[field]) { 704 throw new ParseException ("Value " + start + " in field " + field + " is lower than the minimum allowable value for this field (min=" + MIN_VALUE[field] + ")", 0); 705 } 706 707 if (end < 0) { 708 end = MAX_VALUE[field]; 709 } else if (end > MAX_VALUE[field]) { 710 throw new ParseException ("Value " + end + " in field " + field + " is higher than the maximum allowable value for this field (max=" + MAX_VALUE[field] + ")", 0); 711 } 712 } 713 714 if (interval < 1) { 715 interval = 1; 716 } 717 718 int i = start - MIN_VALUE[field]; 719 720 for (i = start - MIN_VALUE[field]; i <= (end - MIN_VALUE[field]); 722 i += interval) { 723 lookup[field] |= (1L << i); 724 } 725 726 if (lookupMin[field] > start) { 729 lookupMin[field] = start; 730 } 731 732 i += (MIN_VALUE[field] - interval); 733 734 if (lookupMax[field] < i) { 735 lookupMax[field] = i; 736 } 737 } 738 739 746 private boolean isLeapYear(int year) { 747 return (((year % 4) == 0) && ((year % 100) != 0)) || ((year % 400) == 0); 748 } 749 750 759 private int dayOfWeek(int day, int month, int year) { 760 day += ((month < 3) ? year-- : (year - 2)); 761 return ((((23 * month) / 9) + day + 4 + (year / 4)) - (year / 100) + (year / 400)) % 7; 762 } 763 764 775 private int numberOfDaysInMonth(int month, int year) { 776 while (month < 1) { 777 month += 12; 778 year--; 779 } 780 781 while (month > 12) { 782 month -= 12; 783 year++; 784 } 785 786 if (month == 2) { 787 return isLeapYear(year) ? 29 : 28; 788 } else { 789 return DAYS_IN_MONTH[month - 1]; 790 } 791 } 792 793 802 private int getDayOfWeekVal(char ch1, char[] element, int i) throws ParseException { 803 if ((i + 1) >= element.length) { 804 throw makeParseException("Unexpected end of element encountered while parsing a day name", element, i); 805 } 806 807 int ch2 = element[i] | 0x20; 808 int ch3 = element[i + 1] | 0x20; 809 810 switch (ch1 | 0x20) { 811 case 's': 813 if ((ch2 == 'u') && (ch3 == 'n')) { 814 return 0; 815 } 816 817 if ((ch2 == 'a') && (ch3 == 't')) { 818 return 6; 819 } 820 821 break; 822 case 'm': 824 if ((ch2 == 'o') && (ch3 == 'n')) { 825 return 1; 826 } 827 828 break; 829 case 't': 831 if ((ch2 == 'u') && (ch3 == 'e')) { 832 return 2; 833 } 834 835 if ((ch2 == 'h') && (ch3 == 'u')) { 836 return 4; 837 } 838 839 break; 840 case 'w': 842 if ((ch2 == 'e') && (ch3 == 'd')) { 843 return 3; 844 } 845 846 break; 847 case 'f': 849 if ((ch2 == 'r') && (ch3 == 'i')) { 850 return 5; 851 } 852 853 break; 854 } 855 856 throw makeParseException("Unexpected character while parsing a day name", element, i - 1); 857 } 858 859 868 private int getMonthVal(char ch1, char[] element, int i) throws ParseException { 869 if ((i + 1) >= element.length) { 870 throw makeParseException("Unexpected end of element encountered while parsing a month name", element, i); 871 } 872 873 int ch2 = element[i] | 0x20; 874 int ch3 = element[i + 1] | 0x20; 875 876 switch (ch1 | 0x20) { 877 case 'j': 879 if ((ch2 == 'a') && (ch3 == 'n')) { 880 return 1; 881 } 882 883 if (ch2 == 'u') { 884 if (ch3 == 'n') { 885 return 6; 886 } 887 888 if (ch3 == 'l') { 889 return 7; 890 } 891 } 892 893 break; 894 case 'f': 896 if ((ch2 == 'e') && (ch3 == 'b')) { 897 return 2; 898 } 899 900 break; 901 case 'm': 903 if (ch2 == 'a') { 904 if (ch3 == 'r') { 905 return 3; 906 } 907 908 if (ch3 == 'y') { 909 return 5; 910 } 911 } 912 913 break; 914 case 'a': 916 if ((ch2 == 'p') && (ch3 == 'r')) { 917 return 4; 918 } 919 920 if ((ch2 == 'u') && (ch3 == 'g')) { 921 return 8; 922 } 923 924 break; 925 case 's': 927 if ((ch2 == 'e') && (ch3 == 'p')) { 928 return 9; 929 } 930 931 break; 932 case 'o': 934 if ((ch2 == 'c') && (ch3 == 't')) { 935 return 10; 936 } 937 938 break; 939 case 'n': 941 if ((ch2 == 'o') && (ch3 == 'v')) { 942 return 11; 943 } 944 945 break; 946 case 'd': 948 if ((ch2 == 'e') && (ch3 == 'c')) { 949 return 12; 950 } 951 952 break; 953 } 954 955 throw makeParseException("Unexpected character while parsing a month name", element, i - 1); 956 } 957 958 965 public String getExpressionSummary() { 966 StringBuffer buf = new StringBuffer (); 967 968 buf.append(getExpressionSetSummary(MINUTE)).append(' '); 969 buf.append(getExpressionSetSummary(HOUR)).append(' '); 970 buf.append(getExpressionSetSummary(DAY_OF_MONTH)).append(' '); 971 buf.append(getExpressionSetSummary(MONTH)).append(' '); 972 buf.append(getExpressionSetSummary(DAY_OF_WEEK)); 973 974 return buf.toString(); 975 } 976 977 989 private String getExpressionSetSummary(int field) { 990 if (lookup[field] == Long.MAX_VALUE) { 991 return "*"; 992 } 993 994 StringBuffer buf = new StringBuffer (); 995 996 boolean first = true; 997 998 for (int i = MIN_VALUE[field]; i <= MAX_VALUE[field]; i++) { 999 if ((lookup[field] & (1L << (i - MIN_VALUE[field]))) != 0) { 1000 if (!first) { 1001 buf.append(","); 1002 } else { 1003 first = false; 1004 } 1005 1006 buf.append(String.valueOf(i)); 1007 } 1008 } 1009 1010 return buf.toString(); 1011 } 1012 1013 1026 private ParseException makeParseException(String msg, char[] data, int offset) { 1027 char[] buf = new char[msg.length() + data.length + 3]; 1028 int msgLen = msg.length(); 1029 System.arraycopy(msg.toCharArray(), 0, buf, 0, msgLen); 1030 buf[msgLen] = ' '; 1031 buf[msgLen + 1] = '['; 1032 System.arraycopy(data, 0, buf, msgLen + 2, data.length); 1033 buf[buf.length - 1] = ']'; 1034 return new ParseException (new String (buf), offset); 1035 } 1036} 1037 1038 1039class ValueSet { 1040 public int pos; 1041 public int value; 1042} 1043 | Popular Tags |