0001: package org.quartz;
0002:
0003: import java.io.Serializable;
0004: import java.text.ParseException;
0005: import java.util.Calendar;
0006: import java.util.Date;
0007: import java.util.HashMap;
0008: import java.util.Iterator;
0009: import java.util.Locale;
0010: import java.util.Map;
0011: import java.util.SortedSet;
0012: import java.util.StringTokenizer;
0013: import java.util.TimeZone;
0014: import java.util.TreeSet;
0015:
0016: /**
0017: * Provides a parser and evaluator for unix-like cron expressions. Cron
0018: * expressions provide the ability to specify complex time combinations such as
0019: * "At 8:00am every Monday through Friday" or "At 1:30am every
0020: * last Friday of the month".
0021: * <P>
0022: * Cron expressions are comprised of 6 required fields and one optional field
0023: * separated by white space. The fields respectively are described as follows:
0024: *
0025: * <table cellspacing="8">
0026: * <tr>
0027: * <th align="left">Field Name</th>
0028: * <th align="left"> </th>
0029: * <th align="left">Allowed Values</th>
0030: * <th align="left"> </th>
0031: * <th align="left">Allowed Special Characters</th>
0032: * </tr>
0033: * <tr>
0034: * <td align="left"><code>Seconds</code></td>
0035: * <td align="left"> </th>
0036: * <td align="left"><code>0-59</code></td>
0037: * <td align="left"> </th>
0038: * <td align="left"><code>, - * /</code></td>
0039: * </tr>
0040: * <tr>
0041: * <td align="left"><code>Minutes</code></td>
0042: * <td align="left"> </th>
0043: * <td align="left"><code>0-59</code></td>
0044: * <td align="left"> </th>
0045: * <td align="left"><code>, - * /</code></td>
0046: * </tr>
0047: * <tr>
0048: * <td align="left"><code>Hours</code></td>
0049: * <td align="left"> </th>
0050: * <td align="left"><code>0-23</code></td>
0051: * <td align="left"> </th>
0052: * <td align="left"><code>, - * /</code></td>
0053: * </tr>
0054: * <tr>
0055: * <td align="left"><code>Day-of-month</code></td>
0056: * <td align="left"> </th>
0057: * <td align="left"><code>1-31</code></td>
0058: * <td align="left"> </th>
0059: * <td align="left"><code>, - * ? / L W</code></td>
0060: * </tr>
0061: * <tr>
0062: * <td align="left"><code>Month</code></td>
0063: * <td align="left"> </th>
0064: * <td align="left"><code>1-12 or JAN-DEC</code></td>
0065: * <td align="left"> </th>
0066: * <td align="left"><code>, - * /</code></td>
0067: * </tr>
0068: * <tr>
0069: * <td align="left"><code>Day-of-Week</code></td>
0070: * <td align="left"> </th>
0071: * <td align="left"><code>1-7 or SUN-SAT</code></td>
0072: * <td align="left"> </th>
0073: * <td align="left"><code>, - * ? / L #</code></td>
0074: * </tr>
0075: * <tr>
0076: * <td align="left"><code>Year (Optional)</code></td>
0077: * <td align="left"> </th>
0078: * <td align="left"><code>empty, 1970-2099</code></td>
0079: * <td align="left"> </th>
0080: * <td align="left"><code>, - * /</code></td>
0081: * </tr>
0082: * </table>
0083: * <P>
0084: * The '*' character is used to specify all values. For example, "*"
0085: * in the minute field means "every minute".
0086: * <P>
0087: * The '?' character is allowed for the day-of-month and day-of-week fields. It
0088: * is used to specify 'no specific value'. This is useful when you need to
0089: * specify something in one of the two fileds, but not the other.
0090: * <P>
0091: * The '-' character is used to specify ranges For example "10-12" in
0092: * the hour field means "the hours 10, 11 and 12".
0093: * <P>
0094: * The ',' character is used to specify additional values. For example
0095: * "MON,WED,FRI" in the day-of-week field means "the days Monday,
0096: * Wednesday, and Friday".
0097: * <P>
0098: * The '/' character is used to specify increments. For example "0/15"
0099: * in the seconds field means "the seconds 0, 15, 30, and 45". And
0100: * "5/15" in the seconds field means "the seconds 5, 20, 35, and
0101: * 50". Specifying '*' before the '/' is equivalent to specifying 0 is
0102: * the value to start with. Essentially, for each field in the expression, there
0103: * is a set of numbers that can be turned on or off. For seconds and minutes,
0104: * the numbers range from 0 to 59. For hours 0 to 23, for days of the month 0 to
0105: * 31, and for months 1 to 12. The "/" character simply helps you turn
0106: * on every "nth" value in the given set. Thus "7/6" in the
0107: * month field only turns on month "7", it does NOT mean every 6th
0108: * month, please note that subtlety.
0109: * <P>
0110: * The 'L' character is allowed for the day-of-month and day-of-week fields.
0111: * This character is short-hand for "last", but it has different
0112: * meaning in each of the two fields. For example, the value "L" in
0113: * the day-of-month field means "the last day of the month" - day 31
0114: * for January, day 28 for February on non-leap years. If used in the
0115: * day-of-week field by itself, it simply means "7" or
0116: * "SAT". But if used in the day-of-week field after another value, it
0117: * means "the last xxx day of the month" - for example "6L"
0118: * means "the last friday of the month". When using the 'L' option, it
0119: * is important not to specify lists, or ranges of values, as you'll get
0120: * confusing results.
0121: * <P>
0122: * The 'W' character is allowed for the day-of-month field. This character
0123: * is used to specify the weekday (Monday-Friday) nearest the given day. As an
0124: * example, if you were to specify "15W" as the value for the
0125: * day-of-month field, the meaning is: "the nearest weekday to the 15th of
0126: * the month". So if the 15th is a Saturday, the trigger will fire on
0127: * Friday the 14th. If the 15th is a Sunday, the trigger will fire on Monday the
0128: * 16th. If the 15th is a Tuesday, then it will fire on Tuesday the 15th.
0129: * However if you specify "1W" as the value for day-of-month, and the
0130: * 1st is a Saturday, the trigger will fire on Monday the 3rd, as it will not
0131: * 'jump' over the boundary of a month's days. The 'W' character can only be
0132: * specified when the day-of-month is a single day, not a range or list of days.
0133: * <P>
0134: * The 'L' and 'W' characters can also be combined for the day-of-month
0135: * expression to yield 'LW', which translates to "last weekday of the
0136: * month".
0137: * <P>
0138: * The '#' character is allowed for the day-of-week field. This character is
0139: * used to specify "the nth" XXX day of the month. For example, the
0140: * value of "6#3" in the day-of-week field means the third Friday of
0141: * the month (day 6 = Friday and "#3" = the 3rd one in the month).
0142: * Other examples: "2#1" = the first Monday of the month and
0143: * "4#5" = the fifth Wednesday of the month. Note that if you specify
0144: * "#5" and there is not 5 of the given day-of-week in the month, then
0145: * no firing will occur that month.
0146: * <P>
0147: * <!--The 'C' character is allowed for the day-of-month and day-of-week fields.
0148: * This character is short-hand for "calendar". This means values are
0149: * calculated against the associated calendar, if any. If no calendar is
0150: * associated, then it is equivalent to having an all-inclusive calendar. A
0151: * value of "5C" in the day-of-month field means "the first day included by the
0152: * calendar on or after the 5th". A value of "1C" in the day-of-week field
0153: * means "the first day included by the calendar on or after sunday".-->
0154: * <P>
0155: * The legal characters and the names of months and days of the week are not
0156: * case sensitive.
0157: *
0158: * <p>
0159: * <b>NOTES:</b>
0160: * <ul>
0161: * <li>Support for specifying both a day-of-week and a day-of-month value is
0162: * not complete (you'll need to use the '?' character in on of these fields).
0163: * </li>
0164: * </ul>
0165: * </p>
0166: *
0167: *
0168: * @author Sharada Jambula, James House
0169: * @author Contributions from Mads Henderson
0170: * @author Refactoring from CronTrigger to CronExpression by Aaron Craven
0171: */
0172: public class CronExpression implements Serializable, Cloneable {
0173:
0174: private static final long serialVersionUID = 12423409423L;
0175:
0176: protected static final int SECOND = 0;
0177: protected static final int MINUTE = 1;
0178: protected static final int HOUR = 2;
0179: protected static final int DAY_OF_MONTH = 3;
0180: protected static final int MONTH = 4;
0181: protected static final int DAY_OF_WEEK = 5;
0182: protected static final int YEAR = 6;
0183: protected static final int ALL_SPEC_INT = 99; // '*'
0184: protected static final int NO_SPEC_INT = 98; // '?'
0185: protected static final Integer ALL_SPEC = new Integer(ALL_SPEC_INT);
0186: protected static final Integer NO_SPEC = new Integer(NO_SPEC_INT);
0187:
0188: protected static Map monthMap = new HashMap(20);
0189: protected static Map dayMap = new HashMap(60);
0190: static {
0191: monthMap.put("JAN", new Integer(0));
0192: monthMap.put("FEB", new Integer(1));
0193: monthMap.put("MAR", new Integer(2));
0194: monthMap.put("APR", new Integer(3));
0195: monthMap.put("MAY", new Integer(4));
0196: monthMap.put("JUN", new Integer(5));
0197: monthMap.put("JUL", new Integer(6));
0198: monthMap.put("AUG", new Integer(7));
0199: monthMap.put("SEP", new Integer(8));
0200: monthMap.put("OCT", new Integer(9));
0201: monthMap.put("NOV", new Integer(10));
0202: monthMap.put("DEC", new Integer(11));
0203:
0204: dayMap.put("SUN", new Integer(1));
0205: dayMap.put("MON", new Integer(2));
0206: dayMap.put("TUE", new Integer(3));
0207: dayMap.put("WED", new Integer(4));
0208: dayMap.put("THU", new Integer(5));
0209: dayMap.put("FRI", new Integer(6));
0210: dayMap.put("SAT", new Integer(7));
0211: }
0212:
0213: private String cronExpression = null;
0214: private TimeZone timeZone = null;
0215: protected transient TreeSet seconds;
0216: protected transient TreeSet minutes;
0217: protected transient TreeSet hours;
0218: protected transient TreeSet daysOfMonth;
0219: protected transient TreeSet months;
0220: protected transient TreeSet daysOfWeek;
0221: protected transient TreeSet years;
0222:
0223: protected transient boolean lastdayOfWeek = false;
0224: protected transient int nthdayOfWeek = 0;
0225: protected transient boolean lastdayOfMonth = false;
0226: protected transient boolean nearestWeekday = false;
0227: protected transient boolean expressionParsed = false;
0228:
0229: /**
0230: * Constructs a new <CODE>CronExpression</CODE> based on the specified
0231: * parameter.
0232: *
0233: * @param cronExpression String representation of the cron expression the
0234: * new object should represent
0235: * @throws java.text.ParseException
0236: * if the string expression cannot be parsed into a valid
0237: * <CODE>CronExpression</CODE>
0238: */
0239: public CronExpression(String cronExpression) throws ParseException {
0240: if (cronExpression == null) {
0241: throw new IllegalArgumentException(
0242: "cronExpression cannot be null");
0243: }
0244:
0245: this .cronExpression = cronExpression;
0246:
0247: buildExpression(cronExpression.toUpperCase(Locale.US));
0248: }
0249:
0250: /**
0251: * Indicates whether the given date satisfies the cron expression. Note that
0252: * milliseconds are ignored, so two Dates falling on different milliseconds
0253: * of the same second will always have the same result here.
0254: *
0255: * @param date the date to evaluate
0256: * @return a boolean indicating whether the given date satisfies the cron
0257: * expression
0258: */
0259: public boolean isSatisfiedBy(Date date) {
0260: Calendar testDateCal = Calendar.getInstance();
0261: testDateCal.setTime(date);
0262: testDateCal.set(Calendar.MILLISECOND, 0);
0263: Date originalDate = testDateCal.getTime();
0264:
0265: testDateCal.add(Calendar.SECOND, -1);
0266:
0267: Date timeAfter = getTimeAfter(testDateCal.getTime());
0268:
0269: return ((timeAfter != null) && (timeAfter.equals(originalDate)));
0270: }
0271:
0272: /**
0273: * Returns the next date/time <I>after</I> the given date/time which
0274: * satisfies the cron expression.
0275: *
0276: * @param date the date/time at which to begin the search for the next valid
0277: * date/time
0278: * @return the next valid date/time
0279: */
0280: public Date getNextValidTimeAfter(Date date) {
0281: return getTimeAfter(date);
0282: }
0283:
0284: /**
0285: * Returns the next date/time <I>after</I> the given date/time which does
0286: * <I>not</I> satisfy the expression
0287: *
0288: * @param date the date/time at which to begin the search for the next
0289: * invalid date/time
0290: * @return the next valid date/time
0291: */
0292: public Date getNextInvalidTimeAfter(Date date) {
0293: long difference = 1000;
0294:
0295: //move back to the nearest second so differences will be accurate
0296: Calendar adjustCal = Calendar.getInstance();
0297: adjustCal.setTime(date);
0298: adjustCal.set(Calendar.MILLISECOND, 0);
0299: Date lastDate = adjustCal.getTime();
0300:
0301: Date newDate = null;
0302:
0303: //TODO: (QUARTZ-481) IMPROVE THIS! The following is a BAD solution to this problem. Performance will be very bad here, depending on the cron expression. It is, however A solution.
0304:
0305: //keep getting the next included time until it's farther than one second
0306: // apart. At that point, lastDate is the last valid fire time. We return
0307: // the second immediately following it.
0308: while (difference == 1000) {
0309: newDate = getTimeAfter(lastDate);
0310:
0311: difference = newDate.getTime() - lastDate.getTime();
0312:
0313: if (difference == 1000) {
0314: lastDate = newDate;
0315: }
0316: }
0317:
0318: return new Date(lastDate.getTime() + 1000);
0319: }
0320:
0321: /**
0322: * Returns the time zone for which this <code>CronExpression</code>
0323: * will be resolved.
0324: */
0325: public TimeZone getTimeZone() {
0326: if (timeZone == null) {
0327: timeZone = TimeZone.getDefault();
0328: }
0329:
0330: return timeZone;
0331: }
0332:
0333: /**
0334: * Sets the time zone for which this <code>CronExpression</code>
0335: * will be resolved.
0336: */
0337: public void setTimeZone(TimeZone timeZone) {
0338: this .timeZone = timeZone;
0339: }
0340:
0341: /**
0342: * Returns the string representation of the <CODE>CronExpression</CODE>
0343: *
0344: * @return a string representation of the <CODE>CronExpression</CODE>
0345: */
0346: public String toString() {
0347: return cronExpression;
0348: }
0349:
0350: /**
0351: * Indicates whether the specified cron expression can be parsed into a
0352: * valid cron expression
0353: *
0354: * @param cronExpression the expression to evaluate
0355: * @return a boolean indicating whether the given expression is a valid cron
0356: * expression
0357: */
0358: public static boolean isValidExpression(String cronExpression) {
0359:
0360: try {
0361: new CronExpression(cronExpression);
0362: } catch (ParseException pe) {
0363: return false;
0364: }
0365:
0366: return true;
0367: }
0368:
0369: ////////////////////////////////////////////////////////////////////////////
0370: //
0371: // Expression Parsing Functions
0372: //
0373: ////////////////////////////////////////////////////////////////////////////
0374:
0375: protected void buildExpression(String expression)
0376: throws ParseException {
0377: expressionParsed = true;
0378:
0379: try {
0380:
0381: if (seconds == null) {
0382: seconds = new TreeSet();
0383: }
0384: if (minutes == null) {
0385: minutes = new TreeSet();
0386: }
0387: if (hours == null) {
0388: hours = new TreeSet();
0389: }
0390: if (daysOfMonth == null) {
0391: daysOfMonth = new TreeSet();
0392: }
0393: if (months == null) {
0394: months = new TreeSet();
0395: }
0396: if (daysOfWeek == null) {
0397: daysOfWeek = new TreeSet();
0398: }
0399: if (years == null) {
0400: years = new TreeSet();
0401: }
0402:
0403: int exprOn = SECOND;
0404:
0405: StringTokenizer exprsTok = new StringTokenizer(expression,
0406: " \t", false);
0407:
0408: while (exprsTok.hasMoreTokens() && exprOn <= YEAR) {
0409: String expr = exprsTok.nextToken().trim();
0410: StringTokenizer vTok = new StringTokenizer(expr, ",");
0411: while (vTok.hasMoreTokens()) {
0412: String v = vTok.nextToken();
0413: storeExpressionVals(0, v, exprOn);
0414: }
0415:
0416: exprOn++;
0417: }
0418:
0419: if (exprOn <= DAY_OF_WEEK) {
0420: throw new ParseException(
0421: "Unexpected end of expression.", expression
0422: .length());
0423: }
0424:
0425: if (exprOn <= YEAR) {
0426: storeExpressionVals(0, "*", YEAR);
0427: }
0428:
0429: } catch (ParseException pe) {
0430: throw pe;
0431: } catch (Exception e) {
0432: throw new ParseException("Illegal cron expression format ("
0433: + e.toString() + ")", 0);
0434: }
0435: }
0436:
0437: protected int storeExpressionVals(int pos, String s, int type)
0438: throws ParseException {
0439:
0440: int incr = 0;
0441: int i = skipWhiteSpace(pos, s);
0442: if (i >= s.length()) {
0443: return i;
0444: }
0445: char c = s.charAt(i);
0446: if ((c >= 'A') && (c <= 'Z') && (!s.equals("L"))
0447: && (!s.equals("LW"))) {
0448: String sub = s.substring(i, i + 3);
0449: int sval = -1;
0450: int eval = -1;
0451: if (type == MONTH) {
0452: sval = getMonthNumber(sub) + 1;
0453: if (sval < 0) {
0454: throw new ParseException("Invalid Month value: '"
0455: + sub + "'", i);
0456: }
0457: if (s.length() > i + 3) {
0458: c = s.charAt(i + 3);
0459: if (c == '-') {
0460: i += 4;
0461: sub = s.substring(i, i + 3);
0462: eval = getMonthNumber(sub) + 1;
0463: if (eval < 0) {
0464: throw new ParseException(
0465: "Invalid Month value: '" + sub
0466: + "'", i);
0467: }
0468: }
0469: }
0470: } else if (type == DAY_OF_WEEK) {
0471: sval = getDayOfWeekNumber(sub);
0472: if (sval < 0) {
0473: throw new ParseException(
0474: "Invalid Day-of-Week value: '" + sub + "'",
0475: i);
0476: }
0477: if (s.length() > i + 3) {
0478: c = s.charAt(i + 3);
0479: if (c == '-') {
0480: i += 4;
0481: sub = s.substring(i, i + 3);
0482: eval = getDayOfWeekNumber(sub);
0483: if (eval < 0) {
0484: throw new ParseException(
0485: "Invalid Day-of-Week value: '"
0486: + sub + "'", i);
0487: }
0488: if (sval > eval) {
0489: throw new ParseException(
0490: "Invalid Day-of-Week sequence: "
0491: + sval + " > " + eval, i);
0492: }
0493: } else if (c == '#') {
0494: try {
0495: i += 4;
0496: nthdayOfWeek = Integer.parseInt(s
0497: .substring(i));
0498: if (nthdayOfWeek < 1 || nthdayOfWeek > 5) {
0499: throw new Exception();
0500: }
0501: } catch (Exception e) {
0502: throw new ParseException(
0503: "A numeric value between 1 and 5 must follow the '#' option",
0504: i);
0505: }
0506: } else if (c == 'L') {
0507: lastdayOfWeek = true;
0508: i++;
0509: }
0510: }
0511:
0512: } else {
0513: throw new ParseException(
0514: "Illegal characters for this position: '" + sub
0515: + "'", i);
0516: }
0517: if (eval != -1) {
0518: incr = 1;
0519: }
0520: addToSet(sval, eval, incr, type);
0521: return (i + 3);
0522: }
0523:
0524: if (c == '?') {
0525: i++;
0526: if ((i + 1) < s.length()
0527: && (s.charAt(i) != ' ' && s.charAt(i + 1) != '\t')) {
0528: throw new ParseException(
0529: "Illegal character after '?': " + s.charAt(i),
0530: i);
0531: }
0532: if (type != DAY_OF_WEEK && type != DAY_OF_MONTH) {
0533: throw new ParseException(
0534: "'?' can only be specfied for Day-of-Month or Day-of-Week.",
0535: i);
0536: }
0537: if (type == DAY_OF_WEEK && !lastdayOfMonth) {
0538: int val = ((Integer) daysOfMonth.last()).intValue();
0539: if (val == NO_SPEC_INT) {
0540: throw new ParseException(
0541: "'?' can only be specfied for Day-of-Month -OR- Day-of-Week.",
0542: i);
0543: }
0544: }
0545:
0546: addToSet(NO_SPEC_INT, -1, 0, type);
0547: return i;
0548: }
0549:
0550: if (c == '*' || c == '/') {
0551: if (c == '*' && (i + 1) >= s.length()) {
0552: addToSet(ALL_SPEC_INT, -1, incr, type);
0553: return i + 1;
0554: } else if (c == '/'
0555: && ((i + 1) >= s.length() || s.charAt(i + 1) == ' ' || s
0556: .charAt(i + 1) == '\t')) {
0557: throw new ParseException(
0558: "'/' must be followed by an integer.", i);
0559: } else if (c == '*') {
0560: i++;
0561: }
0562: c = s.charAt(i);
0563: if (c == '/') { // is an increment specified?
0564: i++;
0565: if (i >= s.length()) {
0566: throw new ParseException(
0567: "Unexpected end of string.", i);
0568: }
0569:
0570: incr = getNumericValue(s, i);
0571:
0572: i++;
0573: if (incr > 10) {
0574: i++;
0575: }
0576: if (incr > 59 && (type == SECOND || type == MINUTE)) {
0577: throw new ParseException(
0578: "Increment > 60 : " + incr, i);
0579: } else if (incr > 23 && (type == HOUR)) {
0580: throw new ParseException(
0581: "Increment > 24 : " + incr, i);
0582: } else if (incr > 31 && (type == DAY_OF_MONTH)) {
0583: throw new ParseException(
0584: "Increment > 31 : " + incr, i);
0585: } else if (incr > 7 && (type == DAY_OF_WEEK)) {
0586: throw new ParseException("Increment > 7 : " + incr,
0587: i);
0588: } else if (incr > 12 && (type == MONTH)) {
0589: throw new ParseException(
0590: "Increment > 12 : " + incr, i);
0591: }
0592: } else {
0593: incr = 1;
0594: }
0595:
0596: addToSet(ALL_SPEC_INT, -1, incr, type);
0597: return i;
0598: } else if (c == 'L') {
0599: i++;
0600: if (type == DAY_OF_MONTH) {
0601: lastdayOfMonth = true;
0602: }
0603: if (type == DAY_OF_WEEK) {
0604: addToSet(7, 7, 0, type);
0605: }
0606: if (type == DAY_OF_MONTH && s.length() > i) {
0607: c = s.charAt(i);
0608: if (c == 'W') {
0609: nearestWeekday = true;
0610: i++;
0611: }
0612: }
0613: return i;
0614: } else if (c >= '0' && c <= '9') {
0615: int val = Integer.parseInt(String.valueOf(c));
0616: i++;
0617: if (i >= s.length()) {
0618: addToSet(val, -1, -1, type);
0619: } else {
0620: c = s.charAt(i);
0621: if (c >= '0' && c <= '9') {
0622: ValueSet vs = getValue(val, s, i);
0623: val = vs.value;
0624: i = vs.pos;
0625: }
0626: i = checkNext(i, s, val, type);
0627: return i;
0628: }
0629: } else {
0630: throw new ParseException("Unexpected character: " + c, i);
0631: }
0632:
0633: return i;
0634: }
0635:
0636: protected int checkNext(int pos, String s, int val, int type)
0637: throws ParseException {
0638:
0639: int end = -1;
0640: int i = pos;
0641:
0642: if (i >= s.length()) {
0643: addToSet(val, end, -1, type);
0644: return i;
0645: }
0646:
0647: char c = s.charAt(pos);
0648:
0649: if (c == 'L') {
0650: if (type == DAY_OF_WEEK) {
0651: lastdayOfWeek = true;
0652: } else {
0653: throw new ParseException(
0654: "'L' option is not valid here. (pos=" + i + ")",
0655: i);
0656: }
0657: TreeSet set = getSet(type);
0658: set.add(new Integer(val));
0659: i++;
0660: return i;
0661: }
0662:
0663: if (c == 'W') {
0664: if (type == DAY_OF_MONTH) {
0665: nearestWeekday = true;
0666: } else {
0667: throw new ParseException(
0668: "'W' option is not valid here. (pos=" + i + ")",
0669: i);
0670: }
0671: TreeSet set = getSet(type);
0672: set.add(new Integer(val));
0673: i++;
0674: return i;
0675: }
0676:
0677: if (c == '#') {
0678: if (type != DAY_OF_WEEK) {
0679: throw new ParseException(
0680: "'#' option is not valid here. (pos=" + i + ")",
0681: i);
0682: }
0683: i++;
0684: try {
0685: nthdayOfWeek = Integer.parseInt(s.substring(i));
0686: if (nthdayOfWeek < 1 || nthdayOfWeek > 5) {
0687: throw new Exception();
0688: }
0689: } catch (Exception e) {
0690: throw new ParseException(
0691: "A numeric value between 1 and 5 must follow the '#' option",
0692: i);
0693: }
0694:
0695: TreeSet set = getSet(type);
0696: set.add(new Integer(val));
0697: i++;
0698: return i;
0699: }
0700:
0701: if (c == '-') {
0702: i++;
0703: c = s.charAt(i);
0704: int v = Integer.parseInt(String.valueOf(c));
0705: end = v;
0706: i++;
0707: if (i >= s.length()) {
0708: addToSet(val, end, 1, type);
0709: return i;
0710: }
0711: c = s.charAt(i);
0712: if (c >= '0' && c <= '9') {
0713: ValueSet vs = getValue(v, s, i);
0714: int v1 = vs.value;
0715: end = v1;
0716: i = vs.pos;
0717: }
0718: if (i < s.length() && ((c = s.charAt(i)) == '/')) {
0719: i++;
0720: c = s.charAt(i);
0721: int v2 = Integer.parseInt(String.valueOf(c));
0722: i++;
0723: if (i >= s.length()) {
0724: addToSet(val, end, v2, type);
0725: return i;
0726: }
0727: c = s.charAt(i);
0728: if (c >= '0' && c <= '9') {
0729: ValueSet vs = getValue(v2, s, i);
0730: int v3 = vs.value;
0731: addToSet(val, end, v3, type);
0732: i = vs.pos;
0733: return i;
0734: } else {
0735: addToSet(val, end, v2, type);
0736: return i;
0737: }
0738: } else {
0739: addToSet(val, end, 1, type);
0740: return i;
0741: }
0742: }
0743:
0744: if (c == '/') {
0745: i++;
0746: c = s.charAt(i);
0747: int v2 = Integer.parseInt(String.valueOf(c));
0748: i++;
0749: if (i >= s.length()) {
0750: addToSet(val, end, v2, type);
0751: return i;
0752: }
0753: c = s.charAt(i);
0754: if (c >= '0' && c <= '9') {
0755: ValueSet vs = getValue(v2, s, i);
0756: int v3 = vs.value;
0757: addToSet(val, end, v3, type);
0758: i = vs.pos;
0759: return i;
0760: } else {
0761: throw new ParseException("Unexpected character '" + c
0762: + "' after '/'", i);
0763: }
0764: }
0765:
0766: addToSet(val, end, 0, type);
0767: i++;
0768: return i;
0769: }
0770:
0771: public String getCronExpression() {
0772: return cronExpression;
0773: }
0774:
0775: public String getExpressionSummary() {
0776: StringBuffer buf = new StringBuffer();
0777:
0778: buf.append("seconds: ");
0779: buf.append(getExpressionSetSummary(seconds));
0780: buf.append("\n");
0781: buf.append("minutes: ");
0782: buf.append(getExpressionSetSummary(minutes));
0783: buf.append("\n");
0784: buf.append("hours: ");
0785: buf.append(getExpressionSetSummary(hours));
0786: buf.append("\n");
0787: buf.append("daysOfMonth: ");
0788: buf.append(getExpressionSetSummary(daysOfMonth));
0789: buf.append("\n");
0790: buf.append("months: ");
0791: buf.append(getExpressionSetSummary(months));
0792: buf.append("\n");
0793: buf.append("daysOfWeek: ");
0794: buf.append(getExpressionSetSummary(daysOfWeek));
0795: buf.append("\n");
0796: buf.append("lastdayOfWeek: ");
0797: buf.append(lastdayOfWeek);
0798: buf.append("\n");
0799: buf.append("nearestWeekday: ");
0800: buf.append(nearestWeekday);
0801: buf.append("\n");
0802: buf.append("NthDayOfWeek: ");
0803: buf.append(nthdayOfWeek);
0804: buf.append("\n");
0805: buf.append("lastdayOfMonth: ");
0806: buf.append(lastdayOfMonth);
0807: buf.append("\n");
0808: buf.append("years: ");
0809: buf.append(getExpressionSetSummary(years));
0810: buf.append("\n");
0811:
0812: return buf.toString();
0813: }
0814:
0815: protected String getExpressionSetSummary(java.util.Set set) {
0816:
0817: if (set.contains(NO_SPEC)) {
0818: return "?";
0819: }
0820: if (set.contains(ALL_SPEC)) {
0821: return "*";
0822: }
0823:
0824: StringBuffer buf = new StringBuffer();
0825:
0826: Iterator itr = set.iterator();
0827: boolean first = true;
0828: while (itr.hasNext()) {
0829: Integer iVal = (Integer) itr.next();
0830: String val = iVal.toString();
0831: if (!first) {
0832: buf.append(",");
0833: }
0834: buf.append(val);
0835: first = false;
0836: }
0837:
0838: return buf.toString();
0839: }
0840:
0841: protected String getExpressionSetSummary(java.util.ArrayList list) {
0842:
0843: if (list.contains(NO_SPEC)) {
0844: return "?";
0845: }
0846: if (list.contains(ALL_SPEC)) {
0847: return "*";
0848: }
0849:
0850: StringBuffer buf = new StringBuffer();
0851:
0852: Iterator itr = list.iterator();
0853: boolean first = true;
0854: while (itr.hasNext()) {
0855: Integer iVal = (Integer) itr.next();
0856: String val = iVal.toString();
0857: if (!first) {
0858: buf.append(",");
0859: }
0860: buf.append(val);
0861: first = false;
0862: }
0863:
0864: return buf.toString();
0865: }
0866:
0867: protected int skipWhiteSpace(int i, String s) {
0868: for (; i < s.length()
0869: && (s.charAt(i) == ' ' || s.charAt(i) == '\t'); i++) {
0870: ;
0871: }
0872:
0873: return i;
0874: }
0875:
0876: protected int findNextWhiteSpace(int i, String s) {
0877: for (; i < s.length()
0878: && (s.charAt(i) != ' ' || s.charAt(i) != '\t'); i++) {
0879: ;
0880: }
0881:
0882: return i;
0883: }
0884:
0885: protected void addToSet(int val, int end, int incr, int type)
0886: throws ParseException {
0887:
0888: TreeSet set = getSet(type);
0889:
0890: if (type == SECOND || type == MINUTE) {
0891: if ((val < 0 || val > 59 || end > 59)
0892: && (val != ALL_SPEC_INT)) {
0893: throw new ParseException(
0894: "Minute and Second values must be between 0 and 59",
0895: -1);
0896: }
0897: } else if (type == HOUR) {
0898: if ((val < 0 || val > 23 || end > 23)
0899: && (val != ALL_SPEC_INT)) {
0900: throw new ParseException(
0901: "Hour values must be between 0 and 23", -1);
0902: }
0903: } else if (type == DAY_OF_MONTH) {
0904: if ((val < 1 || val > 31 || end > 31)
0905: && (val != ALL_SPEC_INT) && (val != NO_SPEC_INT)) {
0906: throw new ParseException(
0907: "Day of month values must be between 1 and 31",
0908: -1);
0909: }
0910: } else if (type == MONTH) {
0911: if ((val < 1 || val > 12 || end > 12)
0912: && (val != ALL_SPEC_INT)) {
0913: throw new ParseException(
0914: "Month values must be between 1 and 12", -1);
0915: }
0916: } else if (type == DAY_OF_WEEK) {
0917: if ((val == 0 || val > 7 || end > 7)
0918: && (val != ALL_SPEC_INT) && (val != NO_SPEC_INT)) {
0919: throw new ParseException(
0920: "Day-of-Week values must be between 1 and 7",
0921: -1);
0922: }
0923: }
0924:
0925: if ((incr == 0 || incr == -1) && val != ALL_SPEC_INT) {
0926: if (val != -1) {
0927: set.add(new Integer(val));
0928: } else {
0929: set.add(NO_SPEC);
0930: }
0931:
0932: return;
0933: }
0934:
0935: int startAt = val;
0936: int stopAt = end;
0937:
0938: if (val == ALL_SPEC_INT && incr <= 0) {
0939: incr = 1;
0940: set.add(ALL_SPEC); // put in a marker, but also fill values
0941: }
0942:
0943: if (type == SECOND || type == MINUTE) {
0944: if (stopAt == -1) {
0945: stopAt = 59;
0946: }
0947: if (startAt == -1 || startAt == ALL_SPEC_INT) {
0948: startAt = 0;
0949: }
0950: } else if (type == HOUR) {
0951: if (stopAt == -1) {
0952: stopAt = 23;
0953: }
0954: if (startAt == -1 || startAt == ALL_SPEC_INT) {
0955: startAt = 0;
0956: }
0957: } else if (type == DAY_OF_MONTH) {
0958: if (stopAt == -1) {
0959: stopAt = 31;
0960: }
0961: if (startAt == -1 || startAt == ALL_SPEC_INT) {
0962: startAt = 1;
0963: }
0964: } else if (type == MONTH) {
0965: if (stopAt == -1) {
0966: stopAt = 12;
0967: }
0968: if (startAt == -1 || startAt == ALL_SPEC_INT) {
0969: startAt = 1;
0970: }
0971: } else if (type == DAY_OF_WEEK) {
0972: if (stopAt == -1) {
0973: stopAt = 7;
0974: }
0975: if (startAt == -1 || startAt == ALL_SPEC_INT) {
0976: startAt = 1;
0977: }
0978: } else if (type == YEAR) {
0979: if (stopAt == -1) {
0980: stopAt = 2099;
0981: }
0982: if (startAt == -1 || startAt == ALL_SPEC_INT) {
0983: startAt = 1970;
0984: }
0985: }
0986:
0987: for (int i = startAt; i <= stopAt; i += incr) {
0988: set.add(new Integer(i));
0989: }
0990: }
0991:
0992: protected TreeSet getSet(int type) {
0993: switch (type) {
0994: case SECOND:
0995: return seconds;
0996: case MINUTE:
0997: return minutes;
0998: case HOUR:
0999: return hours;
1000: case DAY_OF_MONTH:
1001: return daysOfMonth;
1002: case MONTH:
1003: return months;
1004: case DAY_OF_WEEK:
1005: return daysOfWeek;
1006: case YEAR:
1007: return years;
1008: default:
1009: return null;
1010: }
1011: }
1012:
1013: protected ValueSet getValue(int v, String s, int i) {
1014: char c = s.charAt(i);
1015: String s1 = String.valueOf(v);
1016: while (c >= '0' && c <= '9') {
1017: s1 += c;
1018: i++;
1019: if (i >= s.length()) {
1020: break;
1021: }
1022: c = s.charAt(i);
1023: }
1024: ValueSet val = new ValueSet();
1025:
1026: val.pos = (i < s.length()) ? i : i + 1;
1027: val.value = Integer.parseInt(s1);
1028: return val;
1029: }
1030:
1031: protected int getNumericValue(String s, int i) {
1032: int endOfVal = findNextWhiteSpace(i, s);
1033: String val = s.substring(i, endOfVal);
1034: return Integer.parseInt(val);
1035: }
1036:
1037: protected int getMonthNumber(String s) {
1038: Integer integer = (Integer) monthMap.get(s);
1039:
1040: if (integer == null) {
1041: return -1;
1042: }
1043:
1044: return integer.intValue();
1045: }
1046:
1047: protected int getDayOfWeekNumber(String s) {
1048: Integer integer = (Integer) dayMap.get(s);
1049:
1050: if (integer == null) {
1051: return -1;
1052: }
1053:
1054: return integer.intValue();
1055: }
1056:
1057: ////////////////////////////////////////////////////////////////////////////
1058: //
1059: // Computation Functions
1060: //
1061: ////////////////////////////////////////////////////////////////////////////
1062:
1063: protected Date getTimeAfter(Date afterTime) {
1064:
1065: Calendar cl = Calendar.getInstance(getTimeZone());
1066:
1067: // move ahead one second, since we're computing the time *after* the
1068: // given time
1069: afterTime = new Date(afterTime.getTime() + 1000);
1070: // CronTrigger does not deal with milliseconds
1071: cl.setTime(afterTime);
1072: cl.set(Calendar.MILLISECOND, 0);
1073:
1074: boolean gotOne = false;
1075: // loop until we've computed the next time, or we've past the endTime
1076: while (!gotOne) {
1077:
1078: //if (endTime != null && cl.getTime().after(endTime)) return null;
1079: if (cl.get(Calendar.YEAR) > 2999) // prevent endless loop...
1080: return null;
1081:
1082: SortedSet st = null;
1083: int t = 0;
1084:
1085: int sec = cl.get(Calendar.SECOND);
1086: int min = cl.get(Calendar.MINUTE);
1087:
1088: // get second.................................................
1089: st = seconds.tailSet(new Integer(sec));
1090: if (st != null && st.size() != 0) {
1091: sec = ((Integer) st.first()).intValue();
1092: } else {
1093: sec = ((Integer) seconds.first()).intValue();
1094: min++;
1095: cl.set(Calendar.MINUTE, min);
1096: }
1097: cl.set(Calendar.SECOND, sec);
1098:
1099: min = cl.get(Calendar.MINUTE);
1100: int hr = cl.get(Calendar.HOUR_OF_DAY);
1101: t = -1;
1102:
1103: // get minute.................................................
1104: st = minutes.tailSet(new Integer(min));
1105: if (st != null && st.size() != 0) {
1106: t = min;
1107: min = ((Integer) st.first()).intValue();
1108: } else {
1109: min = ((Integer) minutes.first()).intValue();
1110: hr++;
1111: }
1112: if (min != t) {
1113: cl.set(Calendar.SECOND, 0);
1114: cl.set(Calendar.MINUTE, min);
1115: setCalendarHour(cl, hr);
1116: continue;
1117: }
1118: cl.set(Calendar.MINUTE, min);
1119:
1120: hr = cl.get(Calendar.HOUR_OF_DAY);
1121: int day = cl.get(Calendar.DAY_OF_MONTH);
1122: t = -1;
1123:
1124: // get hour...................................................
1125: st = hours.tailSet(new Integer(hr));
1126: if (st != null && st.size() != 0) {
1127: t = hr;
1128: hr = ((Integer) st.first()).intValue();
1129: } else {
1130: hr = ((Integer) hours.first()).intValue();
1131: day++;
1132: }
1133: if (hr != t) {
1134: cl.set(Calendar.SECOND, 0);
1135: cl.set(Calendar.MINUTE, 0);
1136: cl.set(Calendar.DAY_OF_MONTH, day);
1137: setCalendarHour(cl, hr);
1138: continue;
1139: }
1140: cl.set(Calendar.HOUR_OF_DAY, hr);
1141:
1142: day = cl.get(Calendar.DAY_OF_MONTH);
1143: int mon = cl.get(Calendar.MONTH) + 1;
1144: // '+ 1' because calendar is 0-based for this field, and we are
1145: // 1-based
1146: t = -1;
1147: int tmon = mon;
1148:
1149: // get day...................................................
1150: boolean dayOfMSpec = !daysOfMonth.contains(NO_SPEC);
1151: boolean dayOfWSpec = !daysOfWeek.contains(NO_SPEC);
1152: if (dayOfMSpec && !dayOfWSpec) { // get day by day of month rule
1153: st = daysOfMonth.tailSet(new Integer(day));
1154: if (lastdayOfMonth) {
1155: if (!nearestWeekday) {
1156: t = day;
1157: day = getLastDayOfMonth(mon, cl
1158: .get(Calendar.YEAR));
1159: } else {
1160: t = day;
1161: day = getLastDayOfMonth(mon, cl
1162: .get(Calendar.YEAR));
1163:
1164: java.util.Calendar tcal = java.util.Calendar
1165: .getInstance();
1166: tcal.set(Calendar.SECOND, 0);
1167: tcal.set(Calendar.MINUTE, 0);
1168: tcal.set(Calendar.HOUR_OF_DAY, 0);
1169: tcal.set(Calendar.DAY_OF_MONTH, day);
1170: tcal.set(Calendar.MONTH, mon - 1);
1171: tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR));
1172:
1173: int ldom = getLastDayOfMonth(mon, cl
1174: .get(Calendar.YEAR));
1175: int dow = tcal.get(Calendar.DAY_OF_WEEK);
1176:
1177: if (dow == Calendar.SATURDAY && day == 1) {
1178: day += 2;
1179: } else if (dow == Calendar.SATURDAY) {
1180: day -= 1;
1181: } else if (dow == Calendar.SUNDAY
1182: && day == ldom) {
1183: day -= 2;
1184: } else if (dow == Calendar.SUNDAY) {
1185: day += 1;
1186: }
1187:
1188: tcal.set(Calendar.SECOND, sec);
1189: tcal.set(Calendar.MINUTE, min);
1190: tcal.set(Calendar.HOUR_OF_DAY, hr);
1191: tcal.set(Calendar.DAY_OF_MONTH, day);
1192: tcal.set(Calendar.MONTH, mon - 1);
1193: Date nTime = tcal.getTime();
1194: if (nTime.before(afterTime)) {
1195: day = 1;
1196: mon++;
1197: }
1198: }
1199: } else if (nearestWeekday) {
1200: t = day;
1201: day = ((Integer) daysOfMonth.first()).intValue();
1202:
1203: java.util.Calendar tcal = java.util.Calendar
1204: .getInstance();
1205: tcal.set(Calendar.SECOND, 0);
1206: tcal.set(Calendar.MINUTE, 0);
1207: tcal.set(Calendar.HOUR_OF_DAY, 0);
1208: tcal.set(Calendar.DAY_OF_MONTH, day);
1209: tcal.set(Calendar.MONTH, mon - 1);
1210: tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR));
1211:
1212: int ldom = getLastDayOfMonth(mon, cl
1213: .get(Calendar.YEAR));
1214: int dow = tcal.get(Calendar.DAY_OF_WEEK);
1215:
1216: if (dow == Calendar.SATURDAY && day == 1) {
1217: day += 2;
1218: } else if (dow == Calendar.SATURDAY) {
1219: day -= 1;
1220: } else if (dow == Calendar.SUNDAY && day == ldom) {
1221: day -= 2;
1222: } else if (dow == Calendar.SUNDAY) {
1223: day += 1;
1224: }
1225:
1226: tcal.set(Calendar.SECOND, sec);
1227: tcal.set(Calendar.MINUTE, min);
1228: tcal.set(Calendar.HOUR_OF_DAY, hr);
1229: tcal.set(Calendar.DAY_OF_MONTH, day);
1230: tcal.set(Calendar.MONTH, mon - 1);
1231: Date nTime = tcal.getTime();
1232: if (nTime.before(afterTime)) {
1233: day = ((Integer) daysOfMonth.first())
1234: .intValue();
1235: ;
1236: mon++;
1237: }
1238: } else if (st != null && st.size() != 0) {
1239: t = day;
1240: day = ((Integer) st.first()).intValue();
1241: } else {
1242: day = ((Integer) daysOfMonth.first()).intValue();
1243: mon++;
1244: }
1245:
1246: if (day != t || mon != tmon) {
1247: cl.set(Calendar.SECOND, 0);
1248: cl.set(Calendar.MINUTE, 0);
1249: cl.set(Calendar.HOUR_OF_DAY, 0);
1250: cl.set(Calendar.DAY_OF_MONTH, day);
1251: cl.set(Calendar.MONTH, mon - 1);
1252: // '- 1' because calendar is 0-based for this field, and we
1253: // are 1-based
1254: continue;
1255: }
1256: } else if (dayOfWSpec && !dayOfMSpec) { // get day by day of week rule
1257: if (lastdayOfWeek) { // are we looking for the last XXX day of
1258: // the month?
1259: int dow = ((Integer) daysOfWeek.first()).intValue(); // desired
1260: // d-o-w
1261: int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
1262: int daysToAdd = 0;
1263: if (cDow < dow) {
1264: daysToAdd = dow - cDow;
1265: }
1266: if (cDow > dow) {
1267: daysToAdd = dow + (7 - cDow);
1268: }
1269:
1270: int lDay = getLastDayOfMonth(mon, cl
1271: .get(Calendar.YEAR));
1272:
1273: if (day + daysToAdd > lDay) { // did we already miss the
1274: // last one?
1275: cl.set(Calendar.SECOND, 0);
1276: cl.set(Calendar.MINUTE, 0);
1277: cl.set(Calendar.HOUR_OF_DAY, 0);
1278: cl.set(Calendar.DAY_OF_MONTH, 1);
1279: cl.set(Calendar.MONTH, mon);
1280: // no '- 1' here because we are promoting the month
1281: continue;
1282: }
1283:
1284: // find date of last occurance of this day in this month...
1285: while ((day + daysToAdd + 7) <= lDay) {
1286: daysToAdd += 7;
1287: }
1288:
1289: day += daysToAdd;
1290:
1291: if (daysToAdd > 0) {
1292: cl.set(Calendar.SECOND, 0);
1293: cl.set(Calendar.MINUTE, 0);
1294: cl.set(Calendar.HOUR_OF_DAY, 0);
1295: cl.set(Calendar.DAY_OF_MONTH, day);
1296: cl.set(Calendar.MONTH, mon - 1);
1297: // '- 1' here because we are not promoting the month
1298: continue;
1299: }
1300:
1301: } else if (nthdayOfWeek != 0) {
1302: // are we looking for the Nth XXX day in the month?
1303: int dow = ((Integer) daysOfWeek.first()).intValue(); // desired
1304: // d-o-w
1305: int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
1306: int daysToAdd = 0;
1307: if (cDow < dow) {
1308: daysToAdd = dow - cDow;
1309: } else if (cDow > dow) {
1310: daysToAdd = dow + (7 - cDow);
1311: }
1312:
1313: boolean dayShifted = false;
1314: if (daysToAdd > 0) {
1315: dayShifted = true;
1316: }
1317:
1318: day += daysToAdd;
1319: int weekOfMonth = day / 7;
1320: if (day % 7 > 0) {
1321: weekOfMonth++;
1322: }
1323:
1324: daysToAdd = (nthdayOfWeek - weekOfMonth) * 7;
1325: day += daysToAdd;
1326: if (daysToAdd < 0
1327: || day > getLastDayOfMonth(mon, cl
1328: .get(Calendar.YEAR))) {
1329: cl.set(Calendar.SECOND, 0);
1330: cl.set(Calendar.MINUTE, 0);
1331: cl.set(Calendar.HOUR_OF_DAY, 0);
1332: cl.set(Calendar.DAY_OF_MONTH, 1);
1333: cl.set(Calendar.MONTH, mon);
1334: // no '- 1' here because we are promoting the month
1335: continue;
1336: } else if (daysToAdd > 0 || dayShifted) {
1337: cl.set(Calendar.SECOND, 0);
1338: cl.set(Calendar.MINUTE, 0);
1339: cl.set(Calendar.HOUR_OF_DAY, 0);
1340: cl.set(Calendar.DAY_OF_MONTH, day);
1341: cl.set(Calendar.MONTH, mon - 1);
1342: // '- 1' here because we are NOT promoting the month
1343: continue;
1344: }
1345: } else {
1346: int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
1347: int dow = ((Integer) daysOfWeek.first()).intValue(); // desired
1348: // d-o-w
1349: st = daysOfWeek.tailSet(new Integer(cDow));
1350: if (st != null && st.size() > 0) {
1351: dow = ((Integer) st.first()).intValue();
1352: }
1353:
1354: int daysToAdd = 0;
1355: if (cDow < dow) {
1356: daysToAdd = dow - cDow;
1357: }
1358: if (cDow > dow) {
1359: daysToAdd = dow + (7 - cDow);
1360: }
1361:
1362: int lDay = getLastDayOfMonth(mon, cl
1363: .get(Calendar.YEAR));
1364:
1365: if (day + daysToAdd > lDay) { // will we pass the end of
1366: // the month?
1367: cl.set(Calendar.SECOND, 0);
1368: cl.set(Calendar.MINUTE, 0);
1369: cl.set(Calendar.HOUR_OF_DAY, 0);
1370: cl.set(Calendar.DAY_OF_MONTH, 1);
1371: cl.set(Calendar.MONTH, mon);
1372: // no '- 1' here because we are promoting the month
1373: continue;
1374: } else if (daysToAdd > 0) { // are we swithing days?
1375: cl.set(Calendar.SECOND, 0);
1376: cl.set(Calendar.MINUTE, 0);
1377: cl.set(Calendar.HOUR_OF_DAY, 0);
1378: cl.set(Calendar.DAY_OF_MONTH, day + daysToAdd);
1379: cl.set(Calendar.MONTH, mon - 1);
1380: // '- 1' because calendar is 0-based for this field,
1381: // and we are 1-based
1382: continue;
1383: }
1384: }
1385: } else { // dayOfWSpec && !dayOfMSpec
1386: throw new UnsupportedOperationException(
1387: "Support for specifying both a day-of-week AND a day-of-month parameter is not implemented.");
1388: // TODO:
1389: }
1390: cl.set(Calendar.DAY_OF_MONTH, day);
1391:
1392: mon = cl.get(Calendar.MONTH) + 1;
1393: // '+ 1' because calendar is 0-based for this field, and we are
1394: // 1-based
1395: int year = cl.get(Calendar.YEAR);
1396: t = -1;
1397:
1398: // test for expressions that never generate a valid fire date,
1399: // but keep looping...
1400: if (year > 2099) {
1401: return null;
1402: }
1403:
1404: // get month...................................................
1405: st = months.tailSet(new Integer(mon));
1406: if (st != null && st.size() != 0) {
1407: t = mon;
1408: mon = ((Integer) st.first()).intValue();
1409: } else {
1410: mon = ((Integer) months.first()).intValue();
1411: year++;
1412: }
1413: if (mon != t) {
1414: cl.set(Calendar.SECOND, 0);
1415: cl.set(Calendar.MINUTE, 0);
1416: cl.set(Calendar.HOUR_OF_DAY, 0);
1417: cl.set(Calendar.DAY_OF_MONTH, 1);
1418: cl.set(Calendar.MONTH, mon - 1);
1419: // '- 1' because calendar is 0-based for this field, and we are
1420: // 1-based
1421: cl.set(Calendar.YEAR, year);
1422: continue;
1423: }
1424: cl.set(Calendar.MONTH, mon - 1);
1425: // '- 1' because calendar is 0-based for this field, and we are
1426: // 1-based
1427:
1428: year = cl.get(Calendar.YEAR);
1429: t = -1;
1430:
1431: // get year...................................................
1432: st = years.tailSet(new Integer(year));
1433: if (st != null && st.size() != 0) {
1434: t = year;
1435: year = ((Integer) st.first()).intValue();
1436: } else {
1437: return null; // ran out of years...
1438: }
1439:
1440: if (year != t) {
1441: cl.set(Calendar.SECOND, 0);
1442: cl.set(Calendar.MINUTE, 0);
1443: cl.set(Calendar.HOUR_OF_DAY, 0);
1444: cl.set(Calendar.DAY_OF_MONTH, 1);
1445: cl.set(Calendar.MONTH, 0);
1446: // '- 1' because calendar is 0-based for this field, and we are
1447: // 1-based
1448: cl.set(Calendar.YEAR, year);
1449: continue;
1450: }
1451: cl.set(Calendar.YEAR, year);
1452:
1453: gotOne = true;
1454: } // while( !done )
1455:
1456: return cl.getTime();
1457: }
1458:
1459: /**
1460: * Advance the calendar to the particular hour paying particular attention
1461: * to daylight saving problems.
1462: *
1463: * @param cal
1464: * @param hour
1465: */
1466: protected void setCalendarHour(Calendar cal, int hour) {
1467: cal.set(java.util.Calendar.HOUR_OF_DAY, hour);
1468: if (cal.get(java.util.Calendar.HOUR_OF_DAY) != hour
1469: && hour != 24) {
1470: cal.set(java.util.Calendar.HOUR_OF_DAY, hour + 1);
1471: }
1472: }
1473:
1474: /**
1475: * NOT YET IMPLEMENTED: Returns the time before the given time
1476: * that the <code>CronExpression</code> matches.
1477: */
1478: protected Date getTimeBefore(Date endTime) {
1479: // TODO: implement QUARTZ-423
1480: return null;
1481: }
1482:
1483: /**
1484: * NOT YET IMPLEMENTED: Returns the final time that the
1485: * <code>CronExpression</code> will match.
1486: */
1487: public Date getFinalFireTime() {
1488: // TODO: implement QUARTZ-423
1489: return null;
1490: }
1491:
1492: protected boolean isLeapYear(int year) {
1493: return ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0));
1494: }
1495:
1496: protected int getLastDayOfMonth(int monthNum, int year) {
1497:
1498: switch (monthNum) {
1499: case 1:
1500: return 31;
1501: case 2:
1502: return (isLeapYear(year)) ? 29 : 28;
1503: case 3:
1504: return 31;
1505: case 4:
1506: return 30;
1507: case 5:
1508: return 31;
1509: case 6:
1510: return 30;
1511: case 7:
1512: return 31;
1513: case 8:
1514: return 31;
1515: case 9:
1516: return 30;
1517: case 10:
1518: return 31;
1519: case 11:
1520: return 30;
1521: case 12:
1522: return 31;
1523: default:
1524: throw new IllegalArgumentException("Illegal month number: "
1525: + monthNum);
1526: }
1527: }
1528:
1529: private void readObject(java.io.ObjectInputStream stream)
1530: throws java.io.IOException, ClassNotFoundException {
1531:
1532: stream.defaultReadObject();
1533: try {
1534: buildExpression(cronExpression);
1535: } catch (Exception ignore) {
1536: } // never happens
1537: }
1538:
1539: public Object clone() {
1540: CronExpression copy = null;
1541: try {
1542: copy = new CronExpression(getCronExpression());
1543: copy.setTimeZone(getTimeZone());
1544: } catch (ParseException ex) { // never happens since the source is valid...
1545: throw new IncompatibleClassChangeError("Not Cloneable.");
1546: }
1547: return copy;
1548: }
1549: }
1550:
1551: class ValueSet {
1552: public int value;
1553:
1554: public int pos;
1555: }
|