KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > quartz > CronExpression


1 package org.quartz;
2
3 import java.io.Serializable JavaDoc;
4 import java.text.ParseException JavaDoc;
5 import java.util.Calendar JavaDoc;
6 import java.util.Date JavaDoc;
7 import java.util.HashMap JavaDoc;
8 import java.util.Iterator JavaDoc;
9 import java.util.Locale JavaDoc;
10 import java.util.Map JavaDoc;
11 import java.util.SortedSet JavaDoc;
12 import java.util.StringTokenizer JavaDoc;
13 import java.util.TimeZone JavaDoc;
14 import java.util.TreeSet JavaDoc;
15
16 /**
17  * Provides a parser and evaluator for unix-like cron expressions. Cron
18  * expressions provide the ability to specify complex time combinations such as
19  * "At 8:00am every Monday through Friday" or "At 1:30am every
20  * last Friday of the month".
21  * <P>
22  * Cron expressions are comprised of 6 required fields and one optional field
23  * separated by white space. The fields respectively are described as follows:
24  *
25  * <table cellspacing="8">
26  * <tr>
27  * <th align="left">Field Name</th>
28  * <th align="left">&nbsp;</th>
29  * <th align="left">Allowed Values</th>
30  * <th align="left">&nbsp;</th>
31  * <th align="left">Allowed Special Characters</th>
32  * </tr>
33  * <tr>
34  * <td align="left"><code>Seconds</code></td>
35  * <td align="left">&nbsp;</th>
36  * <td align="left"><code>0-59</code></td>
37  * <td align="left">&nbsp;</th>
38  * <td align="left"><code>, - * /</code></td>
39  * </tr>
40  * <tr>
41  * <td align="left"><code>Minutes</code></td>
42  * <td align="left">&nbsp;</th>
43  * <td align="left"><code>0-59</code></td>
44  * <td align="left">&nbsp;</th>
45  * <td align="left"><code>, - * /</code></td>
46  * </tr>
47  * <tr>
48  * <td align="left"><code>Hours</code></td>
49  * <td align="left">&nbsp;</th>
50  * <td align="left"><code>0-23</code></td>
51  * <td align="left">&nbsp;</th>
52  * <td align="left"><code>, - * /</code></td>
53  * </tr>
54  * <tr>
55  * <td align="left"><code>Day-of-month</code></td>
56  * <td align="left">&nbsp;</th>
57  * <td align="left"><code>1-31</code></td>
58  * <td align="left">&nbsp;</th>
59  * <td align="left"><code>, - * ? / L W</code></td>
60  * </tr>
61  * <tr>
62  * <td align="left"><code>Month</code></td>
63  * <td align="left">&nbsp;</th>
64  * <td align="left"><code>1-12 or JAN-DEC</code></td>
65  * <td align="left">&nbsp;</th>
66  * <td align="left"><code>, - * /</code></td>
67  * </tr>
68  * <tr>
69  * <td align="left"><code>Day-of-Week</code></td>
70  * <td align="left">&nbsp;</th>
71  * <td align="left"><code>1-7 or SUN-SAT</code></td>
72  * <td align="left">&nbsp;</th>
73  * <td align="left"><code>, - * ? / L #</code></td>
74  * </tr>
75  * <tr>
76  * <td align="left"><code>Year (Optional)</code></td>
77  * <td align="left">&nbsp;</th>
78  * <td align="left"><code>empty, 1970-2099</code></td>
79  * <td align="left">&nbsp;</th>
80  * <td align="left"><code>, - * /</code></td>
81  * </tr>
82  * </table>
83  * <P>
84  * The '*' character is used to specify all values. For example, &quot;*&quot;
85  * in the minute field means &quot;every minute&quot;.
86  * <P>
87  * The '?' character is allowed for the day-of-month and day-of-week fields. It
88  * is used to specify 'no specific value'. This is useful when you need to
89  * specify something in one of the two fileds, but not the other.
90  * <P>
91  * The '-' character is used to specify ranges For example &quot;10-12&quot; in
92  * the hour field means &quot;the hours 10, 11 and 12&quot;.
93  * <P>
94  * The ',' character is used to specify additional values. For example
95  * &quot;MON,WED,FRI&quot; in the day-of-week field means &quot;the days Monday,
96  * Wednesday, and Friday&quot;.
97  * <P>
98  * The '/' character is used to specify increments. For example &quot;0/15&quot;
99  * in the seconds field means &quot;the seconds 0, 15, 30, and 45&quot;. And
100  * &quot;5/15&quot; in the seconds field means &quot;the seconds 5, 20, 35, and
101  * 50&quot;. Specifying '*' before the '/' is equivalent to specifying 0 is
102  * the value to start with. Essentially, for each field in the expression, there
103  * is a set of numbers that can be turned on or off. For seconds and minutes,
104  * the numbers range from 0 to 59. For hours 0 to 23, for days of the month 0 to
105  * 31, and for months 1 to 12. The &quot;/&quot; character simply helps you turn
106  * on every &quot;nth&quot; value in the given set. Thus &quot;7/6&quot; in the
107  * month field only turns on month &quot;7&quot;, it does NOT mean every 6th
108  * month, please note that subtlety.
109  * <P>
110  * The 'L' character is allowed for the day-of-month and day-of-week fields.
111  * This character is short-hand for &quot;last&quot;, but it has different
112  * meaning in each of the two fields. For example, the value &quot;L&quot; in
113  * the day-of-month field means &quot;the last day of the month&quot; - day 31
114  * for January, day 28 for February on non-leap years. If used in the
115  * day-of-week field by itself, it simply means &quot;7&quot; or
116  * &quot;SAT&quot;. But if used in the day-of-week field after another value, it
117  * means &quot;the last xxx day of the month&quot; - for example &quot;6L&quot;
118  * means &quot;the last friday of the month&quot;. When using the 'L' option, it
119  * is important not to specify lists, or ranges of values, as you'll get
120  * confusing results.
121  * <P>
122  * The 'W' character is allowed for the day-of-month field. This character
123  * is used to specify the weekday (Monday-Friday) nearest the given day. As an
124  * example, if you were to specify &quot;15W&quot; as the value for the
125  * day-of-month field, the meaning is: &quot;the nearest weekday to the 15th of
126  * the month&quot;. So if the 15th is a Saturday, the trigger will fire on
127  * Friday the 14th. If the 15th is a Sunday, the trigger will fire on Monday the
128  * 16th. If the 15th is a Tuesday, then it will fire on Tuesday the 15th.
129  * However if you specify &quot;1W&quot; as the value for day-of-month, and the
130  * 1st is a Saturday, the trigger will fire on Monday the 3rd, as it will not
131  * 'jump' over the boundary of a month's days. The 'W' character can only be
132  * specified when the day-of-month is a single day, not a range or list of days.
133  * <P>
134  * The 'L' and 'W' characters can also be combined for the day-of-month
135  * expression to yield 'LW', which translates to &quot;last weekday of the
136  * month&quot;.
137  * <P>
138  * The '#' character is allowed for the day-of-week field. This character is
139  * used to specify &quot;the nth&quot; XXX day of the month. For example, the
140  * value of &quot;6#3&quot; in the day-of-week field means the third Friday of
141  * the month (day 6 = Friday and &quot;#3&quot; = the 3rd one in the month).
142  * Other examples: &quot;2#1&quot; = the first Monday of the month and
143  * &quot;4#5&quot; = the fifth Wednesday of the month. Note that if you specify
144  * &quot;#5&quot; and there is not 5 of the given day-of-week in the month, then
145  * no firing will occur that month.
146  * <P>
147  * <!--The 'C' character is allowed for the day-of-month and day-of-week fields.
148  * This character is short-hand for "calendar". This means values are
149  * calculated against the associated calendar, if any. If no calendar is
150  * associated, then it is equivalent to having an all-inclusive calendar. A
151  * value of "5C" in the day-of-month field means "the first day included by the
152  * calendar on or after the 5th". A value of "1C" in the day-of-week field
153  * means "the first day included by the calendar on or after sunday".-->
154  * <P>
155  * The legal characters and the names of months and days of the week are not
156  * case sensitive.
157  *
158  * <p>
159  * <b>NOTES:</b>
160  * <ul>
161  * <li>Support for specifying both a day-of-week and a day-of-month value is
162  * not complete (you'll need to use the '?' character in on of these fields).
163  * </li>
164  * </ul>
165  * </p>
166  *
167  *
168  * @author Sharada Jambula, James House
169  * @author Contributions from Mads Henderson
170  * @author Refactoring from CronTrigger to CronExpression by Aaron Craven
171  */

172 public class CronExpression implements Serializable JavaDoc, Cloneable JavaDoc {
173
174     private static final long serialVersionUID = 12423409423L;
175     
176     protected static final int SECOND = 0;
177     protected static final int MINUTE = 1;
178     protected static final int HOUR = 2;
179     protected static final int DAY_OF_MONTH = 3;
180     protected static final int MONTH = 4;
181     protected static final int DAY_OF_WEEK = 5;
182     protected static final int YEAR = 6;
183     protected static final int ALL_SPEC_INT = 99; // '*'
184
protected static final int NO_SPEC_INT = 98; // '?'
185
protected static final Integer JavaDoc ALL_SPEC = new Integer JavaDoc(ALL_SPEC_INT);
186     protected static final Integer JavaDoc NO_SPEC = new Integer JavaDoc(NO_SPEC_INT);
187     
188     protected static Map JavaDoc monthMap = new HashMap JavaDoc(20);
189     protected static Map JavaDoc dayMap = new HashMap JavaDoc(60);
190     static {
191         monthMap.put("JAN", new Integer JavaDoc(0));
192         monthMap.put("FEB", new Integer JavaDoc(1));
193         monthMap.put("MAR", new Integer JavaDoc(2));
194         monthMap.put("APR", new Integer JavaDoc(3));
195         monthMap.put("MAY", new Integer JavaDoc(4));
196         monthMap.put("JUN", new Integer JavaDoc(5));
197         monthMap.put("JUL", new Integer JavaDoc(6));
198         monthMap.put("AUG", new Integer JavaDoc(7));
199         monthMap.put("SEP", new Integer JavaDoc(8));
200         monthMap.put("OCT", new Integer JavaDoc(9));
201         monthMap.put("NOV", new Integer JavaDoc(10));
202         monthMap.put("DEC", new Integer JavaDoc(11));
203
204         dayMap.put("SUN", new Integer JavaDoc(1));
205         dayMap.put("MON", new Integer JavaDoc(2));
206         dayMap.put("TUE", new Integer JavaDoc(3));
207         dayMap.put("WED", new Integer JavaDoc(4));
208         dayMap.put("THU", new Integer JavaDoc(5));
209         dayMap.put("FRI", new Integer JavaDoc(6));
210         dayMap.put("SAT", new Integer JavaDoc(7));
211     }
212
213     private String JavaDoc cronExpression = null;
214     private TimeZone JavaDoc timeZone = null;
215     protected transient TreeSet JavaDoc seconds;
216     protected transient TreeSet JavaDoc minutes;
217     protected transient TreeSet JavaDoc hours;
218     protected transient TreeSet JavaDoc daysOfMonth;
219     protected transient TreeSet JavaDoc months;
220     protected transient TreeSet JavaDoc daysOfWeek;
221     protected transient TreeSet JavaDoc years;
222
223     protected transient boolean lastdayOfWeek = false;
224     protected transient int nthdayOfWeek = 0;
225     protected transient boolean lastdayOfMonth = false;
226     protected transient boolean nearestWeekday = false;
227     protected transient boolean expressionParsed = false;
228     
229     /**
230      * Constructs a new <CODE>CronExpression</CODE> based on the specified
231      * parameter.
232      *
233      * @param cronExpression String representation of the cron expression the
234      * new object should represent
235      * @throws java.text.ParseException
236      * if the string expression cannot be parsed into a valid
237      * <CODE>CronExpression</CODE>
238      */

239     public CronExpression(String JavaDoc cronExpression) throws ParseException JavaDoc {
240         if (cronExpression == null) {
241             throw new IllegalArgumentException JavaDoc("cronExpression cannot be null");
242         }
243         
244         this.cronExpression = cronExpression;
245         
246         buildExpression(cronExpression.toUpperCase(Locale.US));
247     }
248     
249     /**
250      * Indicates whether the given date satisfies the cron expression. Note that
251      * milliseconds are ignored, so two Dates falling on different milliseconds
252      * of the same second will always have the same result here.
253      *
254      * @param date the date to evaluate
255      * @return a boolean indicating whether the given date satisfies the cron
256      * expression
257      */

258     public boolean isSatisfiedBy(Date JavaDoc date) {
259         Calendar testDateCal = Calendar.getInstance();
260         testDateCal.setTime(date);
261         testDateCal.set(Calendar.MILLISECOND, 0);
262         Date JavaDoc originalDate = testDateCal.getTime();
263         
264         testDateCal.add(Calendar.SECOND, -1);
265         
266         Date JavaDoc timeAfter = getTimeAfter(testDateCal.getTime());
267         
268         return ((timeAfter != null) && (timeAfter.equals(originalDate)));
269     }
270     
271     /**
272      * Returns the next date/time <I>after</I> the given date/time which
273      * satisfies the cron expression.
274      *
275      * @param date the date/time at which to begin the search for the next valid
276      * date/time
277      * @return the next valid date/time
278      */

279     public Date JavaDoc getNextValidTimeAfter(Date JavaDoc date) {
280         return getTimeAfter(date);
281     }
282     
283     /**
284      * Returns the next date/time <I>after</I> the given date/time which does
285      * <I>not</I> satisfy the expression
286      *
287      * @param date the date/time at which to begin the search for the next
288      * invalid date/time
289      * @return the next valid date/time
290      */

291     public Date JavaDoc getNextInvalidTimeAfter(Date JavaDoc date) {
292         long difference = 1000;
293         
294         //move back to the nearest second so differences will be accurate
295
Calendar adjustCal = Calendar.getInstance();
296         adjustCal.setTime(date);
297         adjustCal.set(Calendar.MILLISECOND, 0);
298         Date JavaDoc lastDate = adjustCal.getTime();
299         
300         Date JavaDoc newDate = null;
301         
302         //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.
303

304         //keep getting the next included time until it's farther than one second
305
// apart. At that point, lastDate is the last valid fire time. We return
306
// the second immediately following it.
307
while (difference == 1000) {
308             newDate = getTimeAfter(lastDate);
309             
310             difference = newDate.getTime() - lastDate.getTime();
311             
312             if (difference == 1000) {
313                 lastDate = newDate;
314             }
315         }
316         
317         return new Date JavaDoc(lastDate.getTime() + 1000);
318     }
319     
320     /**
321      * Returns the time zone for which this <code>CronExpression</code>
322      * will be resolved.
323      */

324     public TimeZone JavaDoc getTimeZone() {
325         if (timeZone == null) {
326             timeZone = TimeZone.getDefault();
327         }
328
329         return timeZone;
330     }
331
332     /**
333      * Sets the time zone for which this <code>CronExpression</code>
334      * will be resolved.
335      */

336     public void setTimeZone(TimeZone JavaDoc timeZone) {
337         this.timeZone = timeZone;
338     }
339     
340     /**
341      * Returns the string representation of the <CODE>CronExpression</CODE>
342      *
343      * @return a string representation of the <CODE>CronExpression</CODE>
344      */

345     public String JavaDoc toString() {
346         return cronExpression;
347     }
348     
349     /**
350      * Indicates whether the specified cron expression can be parsed into a
351      * valid cron expression
352      *
353      * @param cronExpression the expression to evaluate
354      * @return a boolean indicating whether the given expression is a valid cron
355      * expression
356      */

357     public static boolean isValidExpression(String JavaDoc cronExpression) {
358         
359         try {
360             new CronExpression(cronExpression);
361         } catch (ParseException JavaDoc pe) {
362             return false;
363         }
364         
365         return true;
366     }
367     
368     ////////////////////////////////////////////////////////////////////////////
369
//
370
// Expression Parsing Functions
371
//
372
////////////////////////////////////////////////////////////////////////////
373

374     protected void buildExpression(String JavaDoc expression) throws ParseException JavaDoc {
375         expressionParsed = true;
376
377         try {
378
379             if (seconds == null) {
380                 seconds = new TreeSet JavaDoc();
381             }
382             if (minutes == null) {
383                 minutes = new TreeSet JavaDoc();
384             }
385             if (hours == null) {
386                 hours = new TreeSet JavaDoc();
387             }
388             if (daysOfMonth == null) {
389                 daysOfMonth = new TreeSet JavaDoc();
390             }
391             if (months == null) {
392                 months = new TreeSet JavaDoc();
393             }
394             if (daysOfWeek == null) {
395                 daysOfWeek = new TreeSet JavaDoc();
396             }
397             if (years == null) {
398                 years = new TreeSet JavaDoc();
399             }
400
401             int exprOn = SECOND;
402
403             StringTokenizer JavaDoc exprsTok = new StringTokenizer JavaDoc(expression, " \t",
404                     false);
405
406             while (exprsTok.hasMoreTokens() && exprOn <= YEAR) {
407                 String JavaDoc expr = exprsTok.nextToken().trim();
408                 StringTokenizer JavaDoc vTok = new StringTokenizer JavaDoc(expr, ",");
409                 while (vTok.hasMoreTokens()) {
410                     String JavaDoc v = vTok.nextToken();
411                     storeExpressionVals(0, v, exprOn);
412                 }
413
414                 exprOn++;
415             }
416
417             if (exprOn <= DAY_OF_WEEK) {
418                 throw new ParseException JavaDoc("Unexpected end of expression.",
419                             expression.length());
420             }
421
422             if (exprOn <= YEAR) {
423                 storeExpressionVals(0, "*", YEAR);
424             }
425
426         } catch (ParseException JavaDoc pe) {
427             throw pe;
428         } catch (Exception JavaDoc e) {
429             throw new ParseException JavaDoc("Illegal cron expression format ("
430                     + e.toString() + ")", 0);
431         }
432     }
433
434     protected int storeExpressionVals(int pos, String JavaDoc s, int type)
435         throws ParseException JavaDoc {
436         
437         int incr = 0;
438         int i = skipWhiteSpace(pos, s);
439         if (i >= s.length()) {
440             return i;
441         }
442         char c = s.charAt(i);
443         if ((c >= 'A') && (c <= 'Z') && (!s.equals("L")) && (!s.equals("LW"))) {
444             String JavaDoc sub = s.substring(i, i + 3);
445             int sval = -1;
446             int eval = -1;
447             if (type == MONTH) {
448                 sval = getMonthNumber(sub) + 1;
449                 if (sval < 0) {
450                     throw new ParseException JavaDoc("Invalid Month value: '" + sub + "'", i);
451                 }
452                 if (s.length() > i + 3) {
453                     c = s.charAt(i + 3);
454                     if (c == '-') {
455                         i += 4;
456                         sub = s.substring(i, i + 3);
457                         eval = getMonthNumber(sub) + 1;
458                         if (eval < 0) {
459                             throw new ParseException JavaDoc("Invalid Month value: '" + sub + "'", i);
460                         }
461                     }
462                 }
463             } else if (type == DAY_OF_WEEK) {
464                 sval = getDayOfWeekNumber(sub);
465                 if (sval < 0) {
466                     throw new ParseException JavaDoc("Invalid Day-of-Week value: '"
467                                 + sub + "'", i);
468                 }
469                 if (s.length() > i + 3) {
470                     c = s.charAt(i + 3);
471                     if (c == '-') {
472                         i += 4;
473                         sub = s.substring(i, i + 3);
474                         eval = getDayOfWeekNumber(sub);
475                         if (eval < 0) {
476                             throw new ParseException JavaDoc(
477                                     "Invalid Day-of-Week value: '" + sub
478                                         + "'", i);
479                         }
480                         if (sval > eval) {
481                             throw new ParseException JavaDoc(
482                                     "Invalid Day-of-Week sequence: " + sval
483                                         + " > " + eval, i);
484                         }
485                     } else if (c == '#') {
486                         try {
487                             i += 4;
488                             nthdayOfWeek = Integer.parseInt(s.substring(i));
489                             if (nthdayOfWeek < 1 || nthdayOfWeek > 5) {
490                                 throw new Exception JavaDoc();
491                             }
492                         } catch (Exception JavaDoc e) {
493                             throw new ParseException JavaDoc(
494                                     "A numeric value between 1 and 5 must follow the '#' option",
495                                     i);
496                         }
497                     } else if (c == 'L') {
498                         lastdayOfWeek = true;
499                         i++;
500                     }
501                 }
502
503             } else {
504                 throw new ParseException JavaDoc(
505                         "Illegal characters for this position: '" + sub + "'",
506                         i);
507             }
508             if (eval != -1) {
509                 incr = 1;
510             }
511             addToSet(sval, eval, incr, type);
512             return (i + 3);
513         }
514
515         if (c == '?') {
516             i++;
517             if ((i + 1) < s.length()
518                     && (s.charAt(i) != ' ' && s.charAt(i + 1) != '\t')) {
519                 throw new ParseException JavaDoc("Illegal character after '?': "
520                             + s.charAt(i), i);
521             }
522             if (type != DAY_OF_WEEK && type != DAY_OF_MONTH) {
523                 throw new ParseException JavaDoc(
524                             "'?' can only be specfied for Day-of-Month or Day-of-Week.",
525                             i);
526             }
527             if (type == DAY_OF_WEEK && !lastdayOfMonth) {
528                 int val = ((Integer JavaDoc) daysOfMonth.last()).intValue();
529                 if (val == NO_SPEC_INT) {
530                     throw new ParseException JavaDoc(
531                                 "'?' can only be specfied for Day-of-Month -OR- Day-of-Week.",
532                                 i);
533                 }
534             }
535
536             addToSet(NO_SPEC_INT, -1, 0, type);
537             return i;
538         }
539
540         if (c == '*' || c == '/') {
541             if (c == '*' && (i + 1) >= s.length()) {
542                 addToSet(ALL_SPEC_INT, -1, incr, type);
543                 return i + 1;
544             } else if (c == '/'
545                     && ((i + 1) >= s.length() || s.charAt(i + 1) == ' ' || s
546                             .charAt(i + 1) == '\t')) {
547                 throw new ParseException JavaDoc("'/' must be followed by an integer.", i);
548             } else if (c == '*') {
549                 i++;
550             }
551             c = s.charAt(i);
552             if (c == '/') { // is an increment specified?
553
i++;
554                 if (i >= s.length()) {
555                     throw new ParseException JavaDoc("Unexpected end of string.", i);
556                 }
557
558                 incr = getNumericValue(s, i);
559
560                 i++;
561                 if (incr > 10) {
562                     i++;
563                 }
564                 if (incr > 59 && (type == SECOND || type == MINUTE)) {
565                     throw new ParseException JavaDoc("Increment > 60 : " + incr, i);
566                 } else if (incr > 23 && (type == HOUR)) {
567                     throw new ParseException JavaDoc("Increment > 24 : " + incr, i);
568                 } else if (incr > 31 && (type == DAY_OF_MONTH)) {
569                     throw new ParseException JavaDoc("Increment > 31 : " + incr, i);
570                 } else if (incr > 7 && (type == DAY_OF_WEEK)) {
571                     throw new ParseException JavaDoc("Increment > 7 : " + incr, i);
572                 } else if (incr > 12 && (type == MONTH)) {
573                     throw new ParseException JavaDoc("Increment > 12 : " + incr, i);
574                 }
575             } else {
576                 incr = 1;
577             }
578
579             addToSet(ALL_SPEC_INT, -1, incr, type);
580             return i;
581         } else if (c == 'L') {
582             i++;
583             if (type == DAY_OF_MONTH) {
584                 lastdayOfMonth = true;
585             }
586             if (type == DAY_OF_WEEK) {
587                 addToSet(7, 7, 0, type);
588             }
589             if(type == DAY_OF_MONTH && s.length() > i) {
590                 c = s.charAt(i);
591                 if(c == 'W') {
592                     nearestWeekday = true;
593                     i++;
594                 }
595             }
596             return i;
597         } else if (c >= '0' && c <= '9') {
598             int val = Integer.parseInt(String.valueOf(c));
599             i++;
600             if (i >= s.length()) {
601                 addToSet(val, -1, -1, type);
602             } else {
603                 c = s.charAt(i);
604                 if (c >= '0' && c <= '9') {
605                     ValueSet vs = getValue(val, s, i);
606                     val = vs.value;
607                     i = vs.pos;
608                 }
609                 i = checkNext(i, s, val, type);
610                 return i;
611             }
612         } else {
613             throw new ParseException JavaDoc("Unexpected character: " + c, i);
614         }
615
616         return i;
617     }
618
619     protected int checkNext(int pos, String JavaDoc s, int val, int type)
620         throws ParseException JavaDoc {
621         
622         int end = -1;
623         int i = pos;
624
625         if (i >= s.length()) {
626             addToSet(val, end, -1, type);
627             return i;
628         }
629
630         char c = s.charAt(pos);
631
632         if (c == 'L') {
633             if (type == DAY_OF_WEEK) {
634                 lastdayOfWeek = true;
635             } else {
636                 throw new ParseException JavaDoc("'L' option is not valid here. (pos=" + i + ")", i);
637             }
638             TreeSet JavaDoc set = getSet(type);
639             set.add(new Integer JavaDoc(val));
640             i++;
641             return i;
642         }
643         
644         if (c == 'W') {
645             if (type == DAY_OF_MONTH) {
646                 nearestWeekday = true;
647             } else {
648                 throw new ParseException JavaDoc("'W' option is not valid here. (pos=" + i + ")", i);
649             }
650             TreeSet JavaDoc set = getSet(type);
651             set.add(new Integer JavaDoc(val));
652             i++;
653             return i;
654         }
655
656         if (c == '#') {
657             if (type != DAY_OF_WEEK) {
658                 throw new ParseException JavaDoc("'#' option is not valid here. (pos=" + i + ")", i);
659             }
660             i++;
661             try {
662                 nthdayOfWeek = Integer.parseInt(s.substring(i));
663                 if (nthdayOfWeek < 1 || nthdayOfWeek > 5) {
664                     throw new Exception JavaDoc();
665                 }
666             } catch (Exception JavaDoc e) {
667                 throw new ParseException JavaDoc(
668                         "A numeric value between 1 and 5 must follow the '#' option",
669                         i);
670             }
671
672             TreeSet JavaDoc set = getSet(type);
673             set.add(new Integer JavaDoc(val));
674             i++;
675             return i;
676         }
677
678         if (c == '-') {
679             i++;
680             c = s.charAt(i);
681             int v = Integer.parseInt(String.valueOf(c));
682             end = v;
683             i++;
684             if (i >= s.length()) {
685                 addToSet(val, end, 1, type);
686                 return i;
687             }
688             c = s.charAt(i);
689             if (c >= '0' && c <= '9') {
690                 ValueSet vs = getValue(v, s, i);
691                 int v1 = vs.value;
692                 end = v1;
693                 i = vs.pos;
694             }
695             if (i < s.length() && ((c = s.charAt(i)) == '/')) {
696                 i++;
697                 c = s.charAt(i);
698                 int v2 = Integer.parseInt(String.valueOf(c));
699                 i++;
700                 if (i >= s.length()) {
701                     addToSet(val, end, v2, type);
702                     return i;
703                 }
704                 c = s.charAt(i);
705                 if (c >= '0' && c <= '9') {
706                     ValueSet vs = getValue(v2, s, i);
707                     int v3 = vs.value;
708                     addToSet(val, end, v3, type);
709                     i = vs.pos;
710                     return i;
711                 } else {
712                     addToSet(val, end, v2, type);
713                     return i;
714                 }
715             } else {
716                 addToSet(val, end, 1, type);
717                 return i;
718             }
719         }
720
721         if (c == '/') {
722             i++;
723             c = s.charAt(i);
724             int v2 = Integer.parseInt(String.valueOf(c));
725             i++;
726             if (i >= s.length()) {
727                 addToSet(val, end, v2, type);
728                 return i;
729             }
730             c = s.charAt(i);
731             if (c >= '0' && c <= '9') {
732                 ValueSet vs = getValue(v2, s, i);
733                 int v3 = vs.value;
734                 addToSet(val, end, v3, type);
735                 i = vs.pos;
736                 return i;
737             } else {
738                 throw new ParseException JavaDoc("Unexpected character '" + c + "' after '/'", i);
739             }
740         }
741
742         addToSet(val, end, 0, type);
743         i++;
744         return i;
745     }
746
747     public String JavaDoc getCronExpression() {
748         return cronExpression;
749     }
750     
751     public String JavaDoc getExpressionSummary() {
752         StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
753
754         buf.append("seconds: ");
755         buf.append(getExpressionSetSummary(seconds));
756         buf.append("\n");
757         buf.append("minutes: ");
758         buf.append(getExpressionSetSummary(minutes));
759         buf.append("\n");
760         buf.append("hours: ");
761         buf.append(getExpressionSetSummary(hours));
762         buf.append("\n");
763         buf.append("daysOfMonth: ");
764         buf.append(getExpressionSetSummary(daysOfMonth));
765         buf.append("\n");
766         buf.append("months: ");
767         buf.append(getExpressionSetSummary(months));
768         buf.append("\n");
769         buf.append("daysOfWeek: ");
770         buf.append(getExpressionSetSummary(daysOfWeek));
771         buf.append("\n");
772         buf.append("lastdayOfWeek: ");
773         buf.append(lastdayOfWeek);
774         buf.append("\n");
775         buf.append("nearestWeekday: ");
776         buf.append(nearestWeekday);
777         buf.append("\n");
778         buf.append("NthDayOfWeek: ");
779         buf.append(nthdayOfWeek);
780         buf.append("\n");
781         buf.append("lastdayOfMonth: ");
782         buf.append(lastdayOfMonth);
783         buf.append("\n");
784         buf.append("years: ");
785         buf.append(getExpressionSetSummary(years));
786         buf.append("\n");
787
788         return buf.toString();
789     }
790
791     protected String JavaDoc getExpressionSetSummary(java.util.Set JavaDoc set) {
792
793         if (set.contains(NO_SPEC)) {
794             return "?";
795         }
796         if (set.contains(ALL_SPEC)) {
797             return "*";
798         }
799
800         StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
801
802         Iterator JavaDoc itr = set.iterator();
803         boolean first = true;
804         while (itr.hasNext()) {
805             Integer JavaDoc iVal = (Integer JavaDoc) itr.next();
806             String JavaDoc val = iVal.toString();
807             if (!first) {
808                 buf.append(",");
809             }
810             buf.append(val);
811             first = false;
812         }
813
814         return buf.toString();
815     }
816
817     protected String JavaDoc getExpressionSetSummary(java.util.ArrayList JavaDoc list) {
818
819         if (list.contains(NO_SPEC)) {
820             return "?";
821         }
822         if (list.contains(ALL_SPEC)) {
823             return "*";
824         }
825
826         StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
827
828         Iterator JavaDoc itr = list.iterator();
829         boolean first = true;
830         while (itr.hasNext()) {
831             Integer JavaDoc iVal = (Integer JavaDoc) itr.next();
832             String JavaDoc val = iVal.toString();
833             if (!first) {
834                 buf.append(",");
835             }
836             buf.append(val);
837             first = false;
838         }
839
840         return buf.toString();
841     }
842
843     protected int skipWhiteSpace(int i, String JavaDoc s) {
844         for (; i < s.length() && (s.charAt(i) == ' ' || s.charAt(i) == '\t'); i++) {
845             ;
846         }
847
848         return i;
849     }
850
851     protected int findNextWhiteSpace(int i, String JavaDoc s) {
852         for (; i < s.length() && (s.charAt(i) != ' ' || s.charAt(i) != '\t'); i++) {
853             ;
854         }
855
856         return i;
857     }
858
859     protected void addToSet(int val, int end, int incr, int type)
860         throws ParseException JavaDoc {
861         
862         TreeSet JavaDoc set = getSet(type);
863
864         if (type == SECOND || type == MINUTE) {
865             if ((val < 0 || val > 59 || end > 59) && (val != ALL_SPEC_INT)) {
866                 throw new ParseException JavaDoc(
867                         "Minute and Second values must be between 0 and 59",
868                         -1);
869             }
870         } else if (type == HOUR) {
871             if ((val < 0 || val > 23 || end > 23) && (val != ALL_SPEC_INT)) {
872                 throw new ParseException JavaDoc(
873                         "Hour values must be between 0 and 23", -1);
874             }
875         } else if (type == DAY_OF_MONTH) {
876             if ((val < 1 || val > 31 || end > 31) && (val != ALL_SPEC_INT)
877                     && (val != NO_SPEC_INT)) {
878                 throw new ParseException JavaDoc(
879                         "Day of month values must be between 1 and 31", -1);
880             }
881         } else if (type == MONTH) {
882             if ((val < 1 || val > 12 || end > 12) && (val != ALL_SPEC_INT)) {
883                 throw new ParseException JavaDoc(
884                         "Month values must be between 1 and 12", -1);
885             }
886         } else if (type == DAY_OF_WEEK) {
887             if ((val == 0 || val > 7 || end > 7) && (val != ALL_SPEC_INT)
888                     && (val != NO_SPEC_INT)) {
889                 throw new ParseException JavaDoc(
890                         "Day-of-Week values must be between 1 and 7", -1);
891             }
892         }
893
894         if ((incr == 0 || incr == -1) && val != ALL_SPEC_INT) {
895             if (val != -1) {
896                 set.add(new Integer JavaDoc(val));
897             } else {
898                 set.add(NO_SPEC);
899             }
900             
901             return;
902         }
903
904         int startAt = val;
905         int stopAt = end;
906
907         if (val == ALL_SPEC_INT && incr <= 0) {
908             incr = 1;
909             set.add(ALL_SPEC); // put in a marker, but also fill values
910
}
911
912         if (type == SECOND || type == MINUTE) {
913             if (stopAt == -1) {
914                 stopAt = 59;
915             }
916             if (startAt == -1 || startAt == ALL_SPEC_INT) {
917                 startAt = 0;
918             }
919         } else if (type == HOUR) {
920             if (stopAt == -1) {
921                 stopAt = 23;
922             }
923             if (startAt == -1 || startAt == ALL_SPEC_INT) {
924                 startAt = 0;
925             }
926         } else if (type == DAY_OF_MONTH) {
927             if (stopAt == -1) {
928                 stopAt = 31;
929             }
930             if (startAt == -1 || startAt == ALL_SPEC_INT) {
931                 startAt = 1;
932             }
933         } else if (type == MONTH) {
934             if (stopAt == -1) {
935                 stopAt = 12;
936             }
937             if (startAt == -1 || startAt == ALL_SPEC_INT) {
938                 startAt = 1;
939             }
940         } else if (type == DAY_OF_WEEK) {
941             if (stopAt == -1) {
942                 stopAt = 7;
943             }
944             if (startAt == -1 || startAt == ALL_SPEC_INT) {
945                 startAt = 1;
946             }
947         } else if (type == YEAR) {
948             if (stopAt == -1) {
949                 stopAt = 2099;
950             }
951             if (startAt == -1 || startAt == ALL_SPEC_INT) {
952                 startAt = 1970;
953             }
954         }
955
956         for (int i = startAt; i <= stopAt; i += incr) {
957             set.add(new Integer JavaDoc(i));
958         }
959     }
960
961     protected TreeSet JavaDoc getSet(int type) {
962         switch (type) {
963             case SECOND:
964                 return seconds;
965             case MINUTE:
966                 return minutes;
967             case HOUR:
968                 return hours;
969             case DAY_OF_MONTH:
970                 return daysOfMonth;
971             case MONTH:
972                 return months;
973             case DAY_OF_WEEK:
974                 return daysOfWeek;
975             case YEAR:
976                 return years;
977             default:
978                 return null;
979         }
980     }
981
982     protected ValueSet getValue(int v, String JavaDoc s, int i) {
983         char c = s.charAt(i);
984         String JavaDoc s1 = String.valueOf(v);
985         while (c >= '0' && c <= '9') {
986             s1 += c;
987             i++;
988             if (i >= s.length()) {
989                 break;
990             }
991             c = s.charAt(i);
992         }
993         ValueSet val = new ValueSet();
994         
995         val.pos = (i < s.length()) ? i : i + 1;
996         val.value = Integer.parseInt(s1);
997         return val;
998     }
999
1000    protected int getNumericValue(String JavaDoc s, int i) {
1001        int endOfVal = findNextWhiteSpace(i, s);
1002        String JavaDoc val = s.substring(i, endOfVal);
1003        return Integer.parseInt(val);
1004    }
1005
1006    protected int getMonthNumber(String JavaDoc s) {
1007        Integer JavaDoc integer = (Integer JavaDoc) monthMap.get(s);
1008
1009        if (integer == null) {
1010            return -1;
1011        }
1012
1013        return integer.intValue();
1014    }
1015
1016    protected int getDayOfWeekNumber(String JavaDoc s) {
1017        Integer JavaDoc integer = (Integer JavaDoc) dayMap.get(s);
1018
1019        if (integer == null) {
1020            return -1;
1021        }
1022
1023        return integer.intValue();
1024    }
1025
1026    ////////////////////////////////////////////////////////////////////////////
1027
//
1028
// Computation Functions
1029
//
1030
////////////////////////////////////////////////////////////////////////////
1031

1032    protected Date JavaDoc getTimeAfter(Date JavaDoc afterTime) {
1033
1034        Calendar cl = Calendar.getInstance(getTimeZone());
1035
1036        // move ahead one second, since we're computing the time *after* the
1037
// given time
1038
afterTime = new Date JavaDoc(afterTime.getTime() + 1000);
1039        // CronTrigger does not deal with milliseconds
1040
cl.setTime(afterTime);
1041        cl.set(Calendar.MILLISECOND, 0);
1042
1043        boolean gotOne = false;
1044        // loop until we've computed the next time, or we've past the endTime
1045
while (!gotOne) {
1046
1047            //if (endTime != null && cl.getTime().after(endTime)) return null;
1048
if(cl.get(Calendar.YEAR) > 2999) // prevent endless loop...
1049
return null;
1050
1051            SortedSet JavaDoc st = null;
1052            int t = 0;
1053
1054            int sec = cl.get(Calendar.SECOND);
1055            int min = cl.get(Calendar.MINUTE);
1056
1057            // get second.................................................
1058
st = seconds.tailSet(new Integer JavaDoc(sec));
1059            if (st != null && st.size() != 0) {
1060                sec = ((Integer JavaDoc) st.first()).intValue();
1061            } else {
1062                sec = ((Integer JavaDoc) seconds.first()).intValue();
1063                min++;
1064                cl.set(Calendar.MINUTE, min);
1065            }
1066            cl.set(Calendar.SECOND, sec);
1067
1068            min = cl.get(Calendar.MINUTE);
1069            int hr = cl.get(Calendar.HOUR_OF_DAY);
1070            t = -1;
1071
1072            // get minute.................................................
1073
st = minutes.tailSet(new Integer JavaDoc(min));
1074            if (st != null && st.size() != 0) {
1075                t = min;
1076                min = ((Integer JavaDoc) st.first()).intValue();
1077            } else {
1078                min = ((Integer JavaDoc) minutes.first()).intValue();
1079                hr++;
1080            }
1081            if (min != t) {
1082                cl.set(Calendar.SECOND, 0);
1083                cl.set(Calendar.MINUTE, min);
1084                setCalendarHour(cl, hr);
1085                continue;
1086            }
1087            cl.set(Calendar.MINUTE, min);
1088
1089            hr = cl.get(Calendar.HOUR_OF_DAY);
1090            int day = cl.get(Calendar.DAY_OF_MONTH);
1091            t = -1;
1092
1093            // get hour...................................................
1094
st = hours.tailSet(new Integer JavaDoc(hr));
1095            if (st != null && st.size() != 0) {
1096                t = hr;
1097                hr = ((Integer JavaDoc) st.first()).intValue();
1098            } else {
1099                hr = ((Integer JavaDoc) hours.first()).intValue();
1100                day++;
1101            }
1102            if (hr != t) {
1103                cl.set(Calendar.SECOND, 0);
1104                cl.set(Calendar.MINUTE, 0);
1105                cl.set(Calendar.DAY_OF_MONTH, day);
1106                setCalendarHour(cl, hr);
1107                continue;
1108            }
1109            cl.set(Calendar.HOUR_OF_DAY, hr);
1110
1111            day = cl.get(Calendar.DAY_OF_MONTH);
1112            int mon = cl.get(Calendar.MONTH) + 1;
1113            // '+ 1' because calendar is 0-based for this field, and we are
1114
// 1-based
1115
t = -1;
1116            int tmon = mon;
1117            
1118            // get day...................................................
1119
boolean dayOfMSpec = !daysOfMonth.contains(NO_SPEC);
1120            boolean dayOfWSpec = !daysOfWeek.contains(NO_SPEC);
1121            if (dayOfMSpec && !dayOfWSpec) { // get day by day of month rule
1122
st = daysOfMonth.tailSet(new Integer JavaDoc(day));
1123                if (lastdayOfMonth) {
1124                    if(!nearestWeekday) {
1125                        t = day;
1126                        day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
1127                    } else {
1128                        t = day;
1129                        day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
1130                        
1131                        java.util.Calendar JavaDoc tcal = java.util.Calendar.getInstance();
1132                        tcal.set(Calendar.SECOND, 0);
1133                        tcal.set(Calendar.MINUTE, 0);
1134                        tcal.set(Calendar.HOUR_OF_DAY, 0);
1135                        tcal.set(Calendar.DAY_OF_MONTH, day);
1136                        tcal.set(Calendar.MONTH, mon - 1);
1137                        tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR));
1138                        
1139                        int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
1140                        int dow = tcal.get(Calendar.DAY_OF_WEEK);
1141
1142                        if(dow == Calendar.SATURDAY && day == 1) {
1143                            day += 2;
1144                        } else if(dow == Calendar.SATURDAY) {
1145                            day -= 1;
1146                        } else if(dow == Calendar.SUNDAY && day == ldom) {
1147                            day -= 2;
1148                        } else if(dow == Calendar.SUNDAY) {
1149                            day += 1;
1150                        }
1151                    
1152                        tcal.set(Calendar.SECOND, sec);
1153                        tcal.set(Calendar.MINUTE, min);
1154                        tcal.set(Calendar.HOUR_OF_DAY, hr);
1155                        tcal.set(Calendar.DAY_OF_MONTH, day);
1156                        tcal.set(Calendar.MONTH, mon - 1);
1157                        Date JavaDoc nTime = tcal.getTime();
1158                        if(nTime.before(afterTime)) {
1159                            day = 1;
1160                            mon++;
1161                        }
1162                    }
1163                } else if(nearestWeekday) {
1164                    t = day;
1165                    day = ((Integer JavaDoc) daysOfMonth.first()).intValue();
1166
1167                    java.util.Calendar JavaDoc tcal = java.util.Calendar.getInstance();
1168                    tcal.set(Calendar.SECOND, 0);
1169                    tcal.set(Calendar.MINUTE, 0);
1170                    tcal.set(Calendar.HOUR_OF_DAY, 0);
1171                    tcal.set(Calendar.DAY_OF_MONTH, day);
1172                    tcal.set(Calendar.MONTH, mon - 1);
1173                    tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR));
1174                    
1175                    int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
1176                    int dow = tcal.get(Calendar.DAY_OF_WEEK);
1177
1178                    if(dow == Calendar.SATURDAY && day == 1) {
1179                        day += 2;
1180                    } else if(dow == Calendar.SATURDAY) {
1181                        day -= 1;
1182                    } else if(dow == Calendar.SUNDAY && day == ldom) {
1183                        day -= 2;
1184                    } else if(dow == Calendar.SUNDAY) {
1185                        day += 1;
1186                    }
1187                        
1188                
1189                    tcal.set(Calendar.SECOND, sec);
1190                    tcal.set(Calendar.MINUTE, min);
1191                    tcal.set(Calendar.HOUR_OF_DAY, hr);
1192                    tcal.set(Calendar.DAY_OF_MONTH, day);
1193                    tcal.set(Calendar.MONTH, mon - 1);
1194                    Date JavaDoc nTime = tcal.getTime();
1195                    if(nTime.before(afterTime)) {
1196                        day = ((Integer JavaDoc) daysOfMonth.first()).intValue();;
1197                        mon++;
1198                    }
1199                } else if (st != null && st.size() != 0) {
1200                    t = day;
1201                    day = ((Integer JavaDoc) st.first()).intValue();
1202                } else {
1203                    day = ((Integer JavaDoc) daysOfMonth.first()).intValue();
1204                    mon++;
1205                }
1206                
1207                if (day != t || mon != tmon) {
1208                    cl.set(Calendar.SECOND, 0);
1209                    cl.set(Calendar.MINUTE, 0);
1210                    cl.set(Calendar.HOUR_OF_DAY, 0);
1211                    cl.set(Calendar.DAY_OF_MONTH, day);
1212                    cl.set(Calendar.MONTH, mon - 1);
1213                    // '- 1' because calendar is 0-based for this field, and we
1214
// are 1-based
1215
continue;
1216                }
1217            } else if (dayOfWSpec && !dayOfMSpec) { // get day by day of week rule
1218
if (lastdayOfWeek) { // are we looking for the last XXX day of
1219
// the month?
1220
int dow = ((Integer JavaDoc) daysOfWeek.first()).intValue(); // desired
1221
// d-o-w
1222
int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
1223
int daysToAdd = 0;
1224                    if (cDow < dow) {
1225                        daysToAdd = dow - cDow;
1226                    }
1227                    if (cDow > dow) {
1228                        daysToAdd = dow + (7 - cDow);
1229                    }
1230
1231                    int lDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
1232
1233                    if (day + daysToAdd > lDay) { // did we already miss the
1234
// last one?
1235
cl.set(Calendar.SECOND, 0);
1236                        cl.set(Calendar.MINUTE, 0);
1237                        cl.set(Calendar.HOUR_OF_DAY, 0);
1238                        cl.set(Calendar.DAY_OF_MONTH, 1);
1239                        cl.set(Calendar.MONTH, mon);
1240                        // no '- 1' here because we are promoting the month
1241
continue;
1242                    }
1243
1244                    // find date of last occurance of this day in this month...
1245
while ((day + daysToAdd + 7) <= lDay) {
1246                        daysToAdd += 7;
1247                    }
1248
1249                    day += daysToAdd;
1250
1251                    if (daysToAdd > 0) {
1252                        cl.set(Calendar.SECOND, 0);
1253                        cl.set(Calendar.MINUTE, 0);
1254                        cl.set(Calendar.HOUR_OF_DAY, 0);
1255                        cl.set(Calendar.DAY_OF_MONTH, day);
1256                        cl.set(Calendar.MONTH, mon - 1);
1257                        // '- 1' here because we are not promoting the month
1258
continue;
1259                    }
1260
1261                } else if (nthdayOfWeek != 0) {
1262                    // are we looking for the Nth XXX day in the month?
1263
int dow = ((Integer JavaDoc) daysOfWeek.first()).intValue(); // desired
1264
// d-o-w
1265
int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
1266
int daysToAdd = 0;
1267                    if (cDow < dow) {
1268                        daysToAdd = dow - cDow;
1269                    } else if (cDow > dow) {
1270                        daysToAdd = dow + (7 - cDow);
1271                    }
1272
1273                    boolean dayShifted = false;
1274                    if (daysToAdd > 0) {
1275                        dayShifted = true;
1276                    }
1277
1278                    day += daysToAdd;
1279                    int weekOfMonth = day / 7;
1280                    if (day % 7 > 0) {
1281                        weekOfMonth++;
1282                    }
1283
1284                    daysToAdd = (nthdayOfWeek - weekOfMonth) * 7;
1285                    day += daysToAdd;
1286                    if (daysToAdd < 0
1287                            || day > getLastDayOfMonth(mon, cl
1288                                    .get(Calendar.YEAR))) {
1289                        cl.set(Calendar.SECOND, 0);
1290                        cl.set(Calendar.MINUTE, 0);
1291                        cl.set(Calendar.HOUR_OF_DAY, 0);
1292                        cl.set(Calendar.DAY_OF_MONTH, 1);
1293                        cl.set(Calendar.MONTH, mon);
1294                        // no '- 1' here because we are promoting the month
1295
continue;
1296                    } else if (daysToAdd > 0 || dayShifted) {
1297                        cl.set(Calendar.SECOND, 0);
1298                        cl.set(Calendar.MINUTE, 0);
1299                        cl.set(Calendar.HOUR_OF_DAY, 0);
1300                        cl.set(Calendar.DAY_OF_MONTH, day);
1301                        cl.set(Calendar.MONTH, mon - 1);
1302                        // '- 1' here because we are NOT promoting the month
1303
continue;
1304                    }
1305                } else {
1306                    int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
1307
int dow = ((Integer JavaDoc) daysOfWeek.first()).intValue(); // desired
1308
// d-o-w
1309
st = daysOfWeek.tailSet(new Integer JavaDoc(cDow));
1310                    if (st != null && st.size() > 0) {
1311                        dow = ((Integer JavaDoc) st.first()).intValue();
1312                    }
1313
1314                    int daysToAdd = 0;
1315                    if (cDow < dow) {
1316                        daysToAdd = dow - cDow;
1317                    }
1318                    if (cDow > dow) {
1319                        daysToAdd = dow + (7 - cDow);
1320                    }
1321
1322                    int lDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
1323
1324                    if (day + daysToAdd > lDay) { // will we pass the end of
1325
// the month?
1326
cl.set(Calendar.SECOND, 0);
1327                        cl.set(Calendar.MINUTE, 0);
1328                        cl.set(Calendar.HOUR_OF_DAY, 0);
1329                        cl.set(Calendar.DAY_OF_MONTH, 1);
1330                        cl.set(Calendar.MONTH, mon);
1331                        // no '- 1' here because we are promoting the month
1332
continue;
1333                    } else if (daysToAdd > 0) { // are we swithing days?
1334
cl.set(Calendar.SECOND, 0);
1335                        cl.set(Calendar.MINUTE, 0);
1336                        cl.set(Calendar.HOUR_OF_DAY, 0);
1337                        cl.set(Calendar.DAY_OF_MONTH, day + daysToAdd);
1338                        cl.set(Calendar.MONTH, mon - 1);
1339                        // '- 1' because calendar is 0-based for this field,
1340
// and we are 1-based
1341
continue;
1342                    }
1343                }
1344            } else { // dayOfWSpec && !dayOfMSpec
1345
throw new UnsupportedOperationException JavaDoc(
1346                        "Support for specifying both a day-of-week AND a day-of-month parameter is not implemented.");
1347                // TODO:
1348
}
1349            cl.set(Calendar.DAY_OF_MONTH, day);
1350
1351            mon = cl.get(Calendar.MONTH) + 1;
1352            // '+ 1' because calendar is 0-based for this field, and we are
1353
// 1-based
1354
int year = cl.get(Calendar.YEAR);
1355            t = -1;
1356
1357            // test for expressions that never generate a valid fire date,
1358
// but keep looping...
1359
if (year > 2099) {
1360                return null;
1361            }
1362
1363            // get month...................................................
1364
st = months.tailSet(new Integer JavaDoc(mon));
1365            if (st != null && st.size() != 0) {
1366                t = mon;
1367                mon = ((Integer JavaDoc) st.first()).intValue();
1368            } else {
1369                mon = ((Integer JavaDoc) months.first()).intValue();
1370                year++;
1371            }
1372            if (mon != t) {
1373                cl.set(Calendar.SECOND, 0);
1374                cl.set(Calendar.MINUTE, 0);
1375                cl.set(Calendar.HOUR_OF_DAY, 0);
1376                cl.set(Calendar.DAY_OF_MONTH, 1);
1377                cl.set(Calendar.MONTH, mon - 1);
1378                // '- 1' because calendar is 0-based for this field, and we are
1379
// 1-based
1380
cl.set(Calendar.YEAR, year);
1381                continue;
1382            }
1383            cl.set(Calendar.MONTH, mon - 1);
1384            // '- 1' because calendar is 0-based for this field, and we are
1385
// 1-based
1386

1387            year = cl.get(Calendar.YEAR);
1388            t = -1;
1389
1390            // get year...................................................
1391
st = years.tailSet(new Integer JavaDoc(year));
1392            if (st != null && st.size() != 0) {
1393                t = year;
1394                year = ((Integer JavaDoc) st.first()).intValue();
1395            } else {
1396                return null; // ran out of years...
1397
}
1398
1399            if (year != t) {
1400                cl.set(Calendar.SECOND, 0);
1401                cl.set(Calendar.MINUTE, 0);
1402                cl.set(Calendar.HOUR_OF_DAY, 0);
1403                cl.set(Calendar.DAY_OF_MONTH, 1);
1404                cl.set(Calendar.MONTH, 0);
1405                // '- 1' because calendar is 0-based for this field, and we are
1406
// 1-based
1407
cl.set(Calendar.YEAR, year);
1408                continue;
1409            }
1410            cl.set(Calendar.YEAR, year);
1411
1412            gotOne = true;
1413        } // while( !done )
1414

1415        return cl.getTime();
1416    }
1417
1418    /**
1419     * Advance the calendar to the particular hour paying particular attention
1420     * to daylight saving problems.
1421     *
1422     * @param cal
1423     * @param hour
1424     */

1425    protected void setCalendarHour(Calendar cal, int hour) {
1426        cal.set(java.util.Calendar.HOUR_OF_DAY, hour);
1427        if (cal.get(java.util.Calendar.HOUR_OF_DAY) != hour && hour != 24) {
1428            cal.set(java.util.Calendar.HOUR_OF_DAY, hour + 1);
1429        }
1430    }
1431
1432    /**
1433     * NOT YET IMPLEMENTED: Returns the time before the given time
1434     * that the <code>CronExpression</code> matches.
1435     */

1436    protected Date JavaDoc getTimeBefore(Date JavaDoc endTime) {
1437        // TODO: implement QUARTZ-423
1438
return null;
1439    }
1440
1441    /**
1442     * NOT YET IMPLEMENTED: Returns the final time that the
1443     * <code>CronExpression</code> will match.
1444     */

1445    public Date JavaDoc getFinalFireTime() {
1446        // TODO: implement QUARTZ-423
1447
return null;
1448    }
1449    
1450    protected boolean isLeapYear(int year) {
1451        return ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0));
1452    }
1453
1454    protected int getLastDayOfMonth(int monthNum, int year) {
1455
1456        switch (monthNum) {
1457            case 1:
1458                return 31;
1459            case 2:
1460                return (isLeapYear(year)) ? 29 : 28;
1461            case 3:
1462                return 31;
1463            case 4:
1464                return 30;
1465            case 5:
1466                return 31;
1467            case 6:
1468                return 30;
1469            case 7:
1470                return 31;
1471            case 8:
1472                return 31;
1473            case 9:
1474                return 30;
1475            case 10:
1476                return 31;
1477            case 11:
1478                return 30;
1479            case 12:
1480                return 31;
1481            default:
1482                throw new IllegalArgumentException JavaDoc("Illegal month number: "
1483                        + monthNum);
1484        }
1485    }
1486    
1487
1488    private void readObject(java.io.ObjectInputStream JavaDoc stream)
1489        throws java.io.IOException JavaDoc, ClassNotFoundException JavaDoc {
1490        
1491        stream.defaultReadObject();
1492        try {
1493            buildExpression(cronExpression);
1494        } catch (Exception JavaDoc ignore) {
1495        } // never happens
1496
}
1497    
1498    public Object JavaDoc clone() {
1499        CronExpression copy = null;
1500        try {
1501            copy = new CronExpression(getCronExpression());
1502            copy.setTimeZone(getTimeZone());
1503        } catch (ParseException JavaDoc ex) { // never happens since the source is valid...
1504
throw new IncompatibleClassChangeError JavaDoc("Not Cloneable.");
1505        }
1506        return copy;
1507    }
1508}
1509
1510class ValueSet {
1511    public int value;
1512
1513    public int pos;
1514}
Popular Tags