PageRenderTime 50ms CodeModel.GetById 5ms RepoModel.GetById 0ms app.codeStats 1ms

/src/main/java/com/wcs/sys/ejbtimer/util/CronExpression.java

https://github.com/YuanZhencai/tms
Java | 1648 lines | 1146 code | 178 blank | 324 comment | 479 complexity | 6bf40767d15f1604c5e2cc5576769548 MD5 | raw file
  1. package com.wcs.sys.ejbtimer.util;
  2. import java.io.Serializable;
  3. import java.text.ParseException;
  4. import java.util.Calendar;
  5. import java.util.Date;
  6. import java.util.HashMap;
  7. import java.util.Iterator;
  8. import java.util.Locale;
  9. import java.util.Map;
  10. import java.util.SortedSet;
  11. import java.util.StringTokenizer;
  12. import java.util.TimeZone;
  13. import java.util.TreeSet;
  14. import org.apache.log4j.Logger;
  15. /**
  16. * Provides a parser and evaluator for unix-like cron expressions. Cron
  17. * expressions provide the ability to specify complex time combinations such as
  18. * "At 8:00am every Monday through Friday" or "At 1:30am every
  19. * last Friday of the month".
  20. * <P>
  21. * Cron expressions are comprised of 6 required fields and one optional field
  22. * separated by white space. The fields respectively are described as follows:
  23. *
  24. * <table cellspacing="8">
  25. * <tr>
  26. * <th align="left">Field Name</th>
  27. * <th align="left">&nbsp;</th>
  28. * <th align="left">Allowed Values</th>
  29. * <th align="left">&nbsp;</th>
  30. * <th align="left">Allowed Special Characters</th>
  31. * </tr>
  32. * <tr>
  33. * <td align="left"><code>Seconds</code></td>
  34. * <td align="left">&nbsp;</th>
  35. * <td align="left"><code>0-59</code></td>
  36. * <td align="left">&nbsp;</th>
  37. * <td align="left"><code>, - * /</code></td>
  38. * </tr>
  39. * <tr>
  40. * <td align="left"><code>Minutes</code></td>
  41. * <td align="left">&nbsp;</th>
  42. * <td align="left"><code>0-59</code></td>
  43. * <td align="left">&nbsp;</th>
  44. * <td align="left"><code>, - * /</code></td>
  45. * </tr>
  46. * <tr>
  47. * <td align="left"><code>Hours</code></td>
  48. * <td align="left">&nbsp;</th>
  49. * <td align="left"><code>0-23</code></td>
  50. * <td align="left">&nbsp;</th>
  51. * <td align="left"><code>, - * /</code></td>
  52. * </tr>
  53. * <tr>
  54. * <td align="left"><code>Day-of-month</code></td>
  55. * <td align="left">&nbsp;</th>
  56. * <td align="left"><code>1-31</code></td>
  57. * <td align="left">&nbsp;</th>
  58. * <td align="left"><code>, - * ? / L W</code></td>
  59. * </tr>
  60. * <tr>
  61. * <td align="left"><code>Month</code></td>
  62. * <td align="left">&nbsp;</th>
  63. * <td align="left"><code>1-12 or JAN-DEC</code></td>
  64. * <td align="left">&nbsp;</th>
  65. * <td align="left"><code>, - * /</code></td>
  66. * </tr>
  67. * <tr>
  68. * <td align="left"><code>Day-of-Week</code></td>
  69. * <td align="left">&nbsp;</th>
  70. * <td align="left"><code>1-7 or SUN-SAT</code></td>
  71. * <td align="left">&nbsp;</th>
  72. * <td align="left"><code>, - * ? / L #</code></td>
  73. * </tr>
  74. * <tr>
  75. * <td align="left"><code>Year (Optional)</code></td>
  76. * <td align="left">&nbsp;</th>
  77. * <td align="left"><code>empty, 1970-2199</code></td>
  78. * <td align="left">&nbsp;</th>
  79. * <td align="left"><code>, - * /</code></td>
  80. * </tr>
  81. * </table>
  82. * <P>
  83. * The '*' character is used to specify all values. For example, &quot;*&quot;
  84. * in the minute field means &quot;every minute&quot;.
  85. * <P>
  86. * The '?' character is allowed for the day-of-month and day-of-week fields. It
  87. * is used to specify 'no specific value'. This is useful when you need to
  88. * specify something in one of the two fields, but not the other.
  89. * <P>
  90. * The '-' character is used to specify ranges For example &quot;10-12&quot; in
  91. * the hour field means &quot;the hours 10, 11 and 12&quot;.
  92. * <P>
  93. * The ',' character is used to specify additional values. For example
  94. * &quot;MON,WED,FRI&quot; in the day-of-week field means &quot;the days Monday,
  95. * Wednesday, and Friday&quot;.
  96. * <P>
  97. * The '/' character is used to specify increments. For example &quot;0/15&quot;
  98. * in the seconds field means &quot;the seconds 0, 15, 30, and 45&quot;. And
  99. * &quot;5/15&quot; in the seconds field means &quot;the seconds 5, 20, 35, and
  100. * 50&quot;. Specifying '*' before the '/' is equivalent to specifying 0 is
  101. * the value to start with. Essentially, for each field in the expression, there
  102. * is a set of numbers that can be turned on or off. For seconds and minutes,
  103. * the numbers range from 0 to 59. For hours 0 to 23, for days of the month 0 to
  104. * 31, and for months 1 to 12. The &quot;/&quot; character simply helps you turn
  105. * on every &quot;nth&quot; value in the given set. Thus &quot;7/6&quot; in the
  106. * month field only turns on month &quot;7&quot;, it does NOT mean every 6th
  107. * month, please note that subtlety.
  108. * <P>
  109. * The 'L' character is allowed for the day-of-month and day-of-week fields.
  110. * This character is short-hand for &quot;last&quot;, but it has different
  111. * meaning in each of the two fields. For example, the value &quot;L&quot; in
  112. * the day-of-month field means &quot;the last day of the month&quot; - day 31
  113. * for January, day 28 for February on non-leap years. If used in the
  114. * day-of-week field by itself, it simply means &quot;7&quot; or
  115. * &quot;SAT&quot;. But if used in the day-of-week field after another value, it
  116. * means &quot;the last xxx day of the month&quot; - for example &quot;6L&quot;
  117. * means &quot;the last friday of the month&quot;. When using the 'L' option, it
  118. * is important not to specify lists, or ranges of values, as you'll get
  119. * confusing results.
  120. * <P>
  121. * The 'W' character is allowed for the day-of-month field. This character
  122. * is used to specify the weekday (Monday-Friday) nearest the given day. As an
  123. * example, if you were to specify &quot;15W&quot; as the value for the
  124. * day-of-month field, the meaning is: &quot;the nearest weekday to the 15th of
  125. * the month&quot;. So if the 15th is a Saturday, the trigger will fire on
  126. * Friday the 14th. If the 15th is a Sunday, the trigger will fire on Monday the
  127. * 16th. If the 15th is a Tuesday, then it will fire on Tuesday the 15th.
  128. * However if you specify &quot;1W&quot; as the value for day-of-month, and the
  129. * 1st is a Saturday, the trigger will fire on Monday the 3rd, as it will not
  130. * 'jump' over the boundary of a month's days. The 'W' character can only be
  131. * specified when the day-of-month is a single day, not a range or list of days.
  132. * <P>
  133. * The 'L' and 'W' characters can also be combined for the day-of-month
  134. * expression to yield 'LW', which translates to &quot;last weekday of the
  135. * month&quot;.
  136. * <P>
  137. * The '#' character is allowed for the day-of-week field. This character is
  138. * used to specify &quot;the nth&quot; XXX day of the month. For example, the
  139. * value of &quot;6#3&quot; in the day-of-week field means the third Friday of
  140. * the month (day 6 = Friday and &quot;#3&quot; = the 3rd one in the month).
  141. * Other examples: &quot;2#1&quot; = the first Monday of the month and
  142. * &quot;4#5&quot; = the fifth Wednesday of the month. Note that if you specify
  143. * &quot;#5&quot; and there is not 5 of the given day-of-week in the month, then
  144. * no firing will occur that month. If the '#' character is used, there can
  145. * only be one expression in the day-of-week field (&quot;3#1,6#3&quot; is
  146. * not valid, since there are two expressions).
  147. * <P>
  148. * <!--The 'C' character is allowed for the day-of-month and day-of-week fields.
  149. * This character is short-hand for "calendar". This means values are
  150. * calculated against the associated calendar, if any. If no calendar is
  151. * associated, then it is equivalent to having an all-inclusive calendar. A
  152. * value of "5C" in the day-of-month field means "the first day included by the
  153. * calendar on or after the 5th". A value of "1C" in the day-of-week field
  154. * means "the first day included by the calendar on or after Sunday".-->
  155. * <P>
  156. * The legal characters and the names of months and days of the week are not
  157. * case sensitive.
  158. *
  159. * <p>
  160. * <b>NOTES:</b>
  161. * <ul>
  162. * <li>Support for specifying both a day-of-week and a day-of-month value is
  163. * not complete (you'll need to use the '?' character in one of these fields).
  164. * </li>
  165. * <li>Overflowing ranges is supported - that is, having a larger number on
  166. * the left hand side than the right. You might do 22-2 to catch 10 o'clock
  167. * at night until 2 o'clock in the morning, or you might have NOV-FEB. It is
  168. * very important to note that overuse of overflowing ranges creates ranges
  169. * that don't make sense and no effort has been made to determine which
  170. * interpretation CronExpression chooses. An example would be
  171. * "0 0 14-6 ? * FRI-MON". </li>
  172. * </ul>
  173. * </p>
  174. *
  175. *
  176. * @author Sharada Jambula, James House
  177. * @author Contributions from Mads Henderson
  178. * @author Refactoring from CronTrigger to CronExpression by Aaron Craven
  179. */
  180. public class CronExpression implements Serializable, Cloneable {
  181. private static final long serialVersionUID = 12423409423L;
  182. private static Logger logger = Logger.getLogger(CronExpression.class);
  183. /**
  184. * <p>
  185. * Instructs the <code>{@link Scheduler}</code> that upon a mis-fire
  186. * situation, the <code>{@link CronTrigger}</code> wants to have it's
  187. * next-fire-time updated to the next time in the schedule after the
  188. * current time (taking into account any associated <code>{@link Calendar}</code>,
  189. * but it does not want to be fired now.
  190. * </p>
  191. */
  192. public static final int MISFIRE_INSTRUCTION_DO_NOTHING = 2;
  193. protected static final int YEAR_TO_GIVEUP_SCHEDULING_AT = 2299;
  194. protected static final int SECOND = 0;
  195. protected static final int MINUTE = 1;
  196. protected static final int HOUR = 2;
  197. protected static final int DAY_OF_MONTH = 3;
  198. protected static final int MONTH = 4;
  199. protected static final int DAY_OF_WEEK = 5;
  200. protected static final int YEAR = 6;
  201. protected static final int ALL_SPEC_INT = 99; // '*'
  202. protected static final int NO_SPEC_INT = 98; // '?'
  203. protected static final Integer ALL_SPEC = Integer.valueOf(ALL_SPEC_INT);
  204. protected static final Integer NO_SPEC = Integer.valueOf(NO_SPEC_INT);
  205. protected static final Map monthMap = new HashMap(20);
  206. protected static final Map dayMap = new HashMap(60);
  207. static {
  208. monthMap.put("JAN", Integer.valueOf(0));
  209. monthMap.put("FEB", Integer.valueOf(1));
  210. monthMap.put("MAR", Integer.valueOf(2));
  211. monthMap.put("APR", Integer.valueOf(3));
  212. monthMap.put("MAY", Integer.valueOf(4));
  213. monthMap.put("JUN", Integer.valueOf(5));
  214. monthMap.put("JUL", Integer.valueOf(6));
  215. monthMap.put("AUG", Integer.valueOf(7));
  216. monthMap.put("SEP", Integer.valueOf(8));
  217. monthMap.put("OCT", Integer.valueOf(9));
  218. monthMap.put("NOV", Integer.valueOf(10));
  219. monthMap.put("DEC", Integer.valueOf(11));
  220. dayMap.put("SUN", Integer.valueOf(1));
  221. dayMap.put("MON", Integer.valueOf(2));
  222. dayMap.put("TUE", Integer.valueOf(3));
  223. dayMap.put("WED", Integer.valueOf(4));
  224. dayMap.put("THU", Integer.valueOf(5));
  225. dayMap.put("FRI", Integer.valueOf(6));
  226. dayMap.put("SAT", Integer.valueOf(7));
  227. }
  228. private String cronExpression = null;
  229. private TimeZone timeZone = null;
  230. protected transient TreeSet seconds;
  231. protected transient TreeSet minutes;
  232. protected transient TreeSet hours;
  233. protected transient TreeSet daysOfMonth;
  234. protected transient TreeSet months;
  235. protected transient TreeSet daysOfWeek;
  236. protected transient TreeSet years;
  237. protected transient boolean lastdayOfWeek = false;
  238. protected transient int nthdayOfWeek = 0;
  239. protected transient boolean lastdayOfMonth = false;
  240. protected transient boolean nearestWeekday = false;
  241. protected transient boolean expressionParsed = false;
  242. /**
  243. * Constructs a new <CODE>CronExpression</CODE> based on the specified
  244. * parameter.
  245. *
  246. * @param cronExpression String representation of the cron expression the
  247. * new object should represent
  248. * @throws java.text.ParseException
  249. * if the string expression cannot be parsed into a valid
  250. * <CODE>CronExpression</CODE>
  251. */
  252. public CronExpression(String cronExpression) throws ParseException {
  253. if (cronExpression == null) {
  254. throw new IllegalArgumentException("cronExpression cannot be null");
  255. }
  256. this.cronExpression = cronExpression.toUpperCase(Locale.US);
  257. buildExpression(this.cronExpression);
  258. }
  259. /**
  260. * Indicates whether the given date satisfies the cron expression. Note that
  261. * milliseconds are ignored, so two Dates falling on different milliseconds
  262. * of the same second will always have the same result here.
  263. *
  264. * @param date the date to evaluate
  265. * @return a boolean indicating whether the given date satisfies the cron
  266. * expression
  267. */
  268. public boolean isSatisfiedBy(Date date) {
  269. Calendar testDateCal = Calendar.getInstance(getTimeZone());
  270. testDateCal.setTime(date);
  271. testDateCal.set(Calendar.MILLISECOND, 0);
  272. Date originalDate = testDateCal.getTime();
  273. testDateCal.add(Calendar.SECOND, -1);
  274. Date timeAfter = getTimeAfter(testDateCal.getTime());
  275. return ((timeAfter != null) && (timeAfter.equals(originalDate)));
  276. }
  277. /**
  278. * Returns the next date/time <I>after</I> the given date/time which
  279. * satisfies the cron expression.
  280. *
  281. * @param date the date/time at which to begin the search for the next valid
  282. * date/time
  283. * @return the next valid date/time
  284. */
  285. public Date getNextValidTimeAfter(Date date) {
  286. return getTimeAfter(date);
  287. }
  288. /**
  289. * Returns the next date/time <I>after</I> the given date/time which does
  290. * <I>not</I> satisfy the expression
  291. *
  292. * @param date the date/time at which to begin the search for the next
  293. * invalid date/time
  294. * @return the next valid date/time
  295. */
  296. public Date getNextInvalidTimeAfter(Date date) {
  297. long difference = 1000;
  298. // move back to the nearest second so differences will be accurate
  299. Calendar adjustCal = Calendar.getInstance(getTimeZone());
  300. adjustCal.setTime(date);
  301. adjustCal.set(Calendar.MILLISECOND, 0);
  302. Date lastDate = adjustCal.getTime();
  303. Date newDate = null;
  304. // TODO: (QUARTZ-481) IMPROVE THIS! The following is a BAD solution to
  305. // this problem. Performance will be very bad here,
  306. // depending on the cron expression. It is, however A solution.
  307. // keep getting the next included time until it's farther than one
  308. // second
  309. // apart. At that point, lastDate is the last valid fire time. We return
  310. // the second immediately following it.
  311. while (difference == 1000) {
  312. newDate = getTimeAfter(lastDate);
  313. difference = newDate.getTime() - lastDate.getTime();
  314. if (difference == 1000) {
  315. lastDate = newDate;
  316. }
  317. }
  318. return new Date(lastDate.getTime() + 1000);
  319. }
  320. /**
  321. * Returns the time zone for which this <code>CronExpression</code>
  322. * will be resolved.
  323. */
  324. public TimeZone getTimeZone() {
  325. if (timeZone == null) {
  326. timeZone = TimeZone.getDefault();
  327. }
  328. return timeZone;
  329. }
  330. /**
  331. * Sets the time zone for which this <code>CronExpression</code>
  332. * will be resolved.
  333. */
  334. public void setTimeZone(TimeZone timeZone) {
  335. this.timeZone = timeZone;
  336. }
  337. /**
  338. * Returns the string representation of the <CODE>CronExpression</CODE>
  339. *
  340. * @return a string representation of the <CODE>CronExpression</CODE>
  341. */
  342. public String toString() {
  343. return cronExpression;
  344. }
  345. /**
  346. * Indicates whether the specified cron expression can be parsed into a
  347. * valid cron expression
  348. *
  349. * @param cronExpression the expression to evaluate
  350. * @return a boolean indicating whether the given expression is a valid cron
  351. * expression
  352. */
  353. public static boolean isValidExpression(String cronExpression) {
  354. try {
  355. new CronExpression(cronExpression);
  356. } catch (ParseException pe) {
  357. return false;
  358. }
  359. return true;
  360. }
  361. // //////////////////////////////////////////////////////////////////////////
  362. //
  363. // Expression Parsing Functions
  364. //
  365. // //////////////////////////////////////////////////////////////////////////
  366. protected void buildExpression(String expression) throws ParseException {
  367. expressionParsed = true;
  368. try {
  369. if (seconds == null) {
  370. seconds = new TreeSet();
  371. }
  372. if (minutes == null) {
  373. minutes = new TreeSet();
  374. }
  375. if (hours == null) {
  376. hours = new TreeSet();
  377. }
  378. if (daysOfMonth == null) {
  379. daysOfMonth = new TreeSet();
  380. }
  381. if (months == null) {
  382. months = new TreeSet();
  383. }
  384. if (daysOfWeek == null) {
  385. daysOfWeek = new TreeSet();
  386. }
  387. if (years == null) {
  388. years = new TreeSet();
  389. }
  390. int exprOn = SECOND;
  391. StringTokenizer exprsTok = new StringTokenizer(expression, " \t", false);
  392. while (exprsTok.hasMoreTokens() && exprOn <= YEAR) {
  393. String expr = exprsTok.nextToken().trim();
  394. // throw an exception if L is used with other days of the month
  395. if (exprOn == DAY_OF_MONTH && expr.indexOf('L') != -1 && expr.length() > 1 && expr.indexOf(',') >= 0) {
  396. throw new ParseException(
  397. "Support for specifying 'L' and 'LW' with other days of the month is not implemented", -1);
  398. }
  399. // throw an exception if L is used with other days of the week
  400. if (exprOn == DAY_OF_WEEK && expr.indexOf('L') != -1 && expr.length() > 1 && expr.indexOf(',') >= 0) {
  401. throw new ParseException(
  402. "Support for specifying 'L' with other days of the week is not implemented", -1);
  403. }
  404. StringTokenizer vTok = new StringTokenizer(expr, ",");
  405. while (vTok.hasMoreTokens()) {
  406. String v = vTok.nextToken();
  407. storeExpressionVals(0, v, exprOn);
  408. }
  409. exprOn++;
  410. }
  411. if (exprOn <= DAY_OF_WEEK) {
  412. throw new ParseException("Unexpected end of expression.", expression.length());
  413. }
  414. if (exprOn <= YEAR) {
  415. storeExpressionVals(0, "*", YEAR);
  416. }
  417. TreeSet dow = getSet(DAY_OF_WEEK);
  418. TreeSet dom = getSet(DAY_OF_MONTH);
  419. // Copying the logic from the UnsupportedOperationException below
  420. boolean dayOfMSpec = !dom.contains(NO_SPEC);
  421. boolean dayOfWSpec = !dow.contains(NO_SPEC);
  422. if (dayOfMSpec && !dayOfWSpec) {
  423. // skip
  424. } else if (dayOfWSpec && !dayOfMSpec) {
  425. // skip
  426. } else {
  427. throw new ParseException(
  428. "Support for specifying both a day-of-week AND a day-of-month parameter is not implemented.", 0);
  429. }
  430. } catch (ParseException pe) {
  431. throw pe;
  432. } catch (Exception e) {
  433. throw new ParseException("Illegal cron expression format (" + e.toString() + ")", 0);
  434. }
  435. }
  436. protected int storeExpressionVals(int pos, String s, int type) throws ParseException {
  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 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("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("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("Invalid Day-of-Week value: '" + sub + "'", i);
  467. }
  468. if (s.length() > i + 3) {
  469. c = s.charAt(i + 3);
  470. if (c == '-') {
  471. i += 4;
  472. sub = s.substring(i, i + 3);
  473. eval = getDayOfWeekNumber(sub);
  474. if (eval < 0) {
  475. throw new ParseException("Invalid Day-of-Week value: '" + sub + "'", i);
  476. }
  477. } else if (c == '#') {
  478. try {
  479. i += 4;
  480. nthdayOfWeek = Integer.parseInt(s.substring(i));
  481. if (nthdayOfWeek < 1 || nthdayOfWeek > 5) {
  482. throw new Exception();
  483. }
  484. } catch (Exception e) {
  485. throw new ParseException("A numeric value between 1 and 5 must follow the '#' option", i);
  486. }
  487. } else if (c == 'L') {
  488. lastdayOfWeek = true;
  489. i++;
  490. }
  491. }
  492. } else {
  493. throw new ParseException("Illegal characters for this position: '" + sub + "'", i);
  494. }
  495. if (eval != -1) {
  496. incr = 1;
  497. }
  498. addToSet(sval, eval, incr, type);
  499. return (i + 3);
  500. }
  501. if (c == '?') {
  502. i++;
  503. if ((i + 1) < s.length() && (s.charAt(i) != ' ' && s.charAt(i + 1) != '\t')) {
  504. throw new ParseException(
  505. "Illegal character after '?': " + s.charAt(i), i);
  506. }
  507. if (type != DAY_OF_WEEK && type != DAY_OF_MONTH) {
  508. throw new ParseException(
  509. "'?' can only be specfied for Day-of-Month or Day-of-Week.", i);
  510. }
  511. if (type == DAY_OF_WEEK && !lastdayOfMonth) {
  512. int val = ((Integer) daysOfMonth.last()).intValue();
  513. if (val == NO_SPEC_INT) {
  514. throw new ParseException(
  515. "'?' can only be specfied for Day-of-Month -OR- Day-of-Week.", i);
  516. }
  517. }
  518. addToSet(NO_SPEC_INT, -1, 0, type);
  519. return i;
  520. }
  521. if (c == '*' || c == '/') {
  522. if (c == '*' && (i + 1) >= s.length()) {
  523. addToSet(ALL_SPEC_INT, -1, incr, type);
  524. return i + 1;
  525. } else if (c == '/' && ((i + 1) >= s.length() || s.charAt(i + 1) == ' ' || s.charAt(i + 1) == '\t')) {
  526. throw new ParseException("'/' must be followed by an integer.", i);
  527. } else if (c == '*') {
  528. i++;
  529. }
  530. c = s.charAt(i);
  531. if (c == '/') { // is an increment specified?
  532. i++;
  533. if (i >= s.length()) {
  534. throw new ParseException("Unexpected end of string.", i);
  535. }
  536. incr = getNumericValue(s, i);
  537. i++;
  538. if (incr > 10) {
  539. i++;
  540. }
  541. if (incr > 59 && (type == SECOND || type == MINUTE)) {
  542. throw new ParseException("Increment > 60 : " + incr, i);
  543. } else if (incr > 23 && (type == HOUR)) {
  544. throw new ParseException("Increment > 24 : " + incr, i);
  545. } else if (incr > 31 && (type == DAY_OF_MONTH)) {
  546. throw new ParseException("Increment > 31 : " + incr, i);
  547. } else if (incr > 7 && (type == DAY_OF_WEEK)) {
  548. throw new ParseException("Increment > 7 : " + incr, i);
  549. } else if (incr > 12 && (type == MONTH)) {
  550. throw new ParseException("Increment > 12 : " + incr, i);
  551. }
  552. } else {
  553. incr = 1;
  554. }
  555. addToSet(ALL_SPEC_INT, -1, incr, type);
  556. return i;
  557. } else if (c == 'L') {
  558. i++;
  559. if (type == DAY_OF_MONTH) {
  560. lastdayOfMonth = true;
  561. }
  562. if (type == DAY_OF_WEEK) {
  563. addToSet(7, 7, 0, type);
  564. }
  565. if (type == DAY_OF_MONTH && s.length() > i) {
  566. c = s.charAt(i);
  567. if (c == 'W') {
  568. nearestWeekday = true;
  569. i++;
  570. }
  571. }
  572. return i;
  573. } else if (c >= '0' && c <= '9') {
  574. int val = Integer.parseInt(String.valueOf(c));
  575. i++;
  576. if (i >= s.length()) {
  577. addToSet(val, -1, -1, type);
  578. } else {
  579. c = s.charAt(i);
  580. if (c >= '0' && c <= '9') {
  581. ValueSet vs = getValue(val, s, i);
  582. val = vs.value;
  583. i = vs.pos;
  584. }
  585. i = checkNext(i, s, val, type);
  586. return i;
  587. }
  588. } else {
  589. throw new ParseException("Unexpected character: " + c, i);
  590. }
  591. return i;
  592. }
  593. protected int checkNext(int pos, String s, int val, int type) throws ParseException {
  594. int end = -1;
  595. int i = pos;
  596. if (i >= s.length()) {
  597. addToSet(val, end, -1, type);
  598. return i;
  599. }
  600. char c = s.charAt(pos);
  601. if (c == 'L') {
  602. if (type == DAY_OF_WEEK) {
  603. if (val < 1 || val > 7) {
  604. throw new ParseException("Day-of-Week values must be between 1 and 7", -1);
  605. }
  606. lastdayOfWeek = true;
  607. } else {
  608. throw new ParseException("'L' option is not valid here. (pos=" + i + ")", i);
  609. }
  610. TreeSet set = getSet(type);
  611. set.add(Integer.valueOf(val));
  612. i++;
  613. return i;
  614. }
  615. if (c == 'W') {
  616. if (type == DAY_OF_MONTH) {
  617. nearestWeekday = true;
  618. } else {
  619. throw new ParseException("'W' option is not valid here. (pos=" + i + ")", i);
  620. }
  621. TreeSet set = getSet(type);
  622. set.add(Integer.valueOf(val));
  623. i++;
  624. return i;
  625. }
  626. if (c == '#') {
  627. if (type != DAY_OF_WEEK) {
  628. throw new ParseException("'#' option is not valid here. (pos=" + i + ")", i);
  629. }
  630. i++;
  631. try {
  632. nthdayOfWeek = Integer.parseInt(s.substring(i));
  633. if (nthdayOfWeek < 1 || nthdayOfWeek > 5) {
  634. throw new Exception();
  635. }
  636. } catch (Exception e) {
  637. throw new ParseException("A numeric value between 1 and 5 must follow the '#' option", i);
  638. }
  639. TreeSet set = getSet(type);
  640. set.add(Integer.valueOf(val));
  641. i++;
  642. return i;
  643. }
  644. if (c == '-') {
  645. i++;
  646. c = s.charAt(i);
  647. int v = Integer.parseInt(String.valueOf(c));
  648. end = v;
  649. i++;
  650. if (i >= s.length()) {
  651. addToSet(val, end, 1, type);
  652. return i;
  653. }
  654. c = s.charAt(i);
  655. if (c >= '0' && c <= '9') {
  656. ValueSet vs = getValue(v, s, i);
  657. int v1 = vs.value;
  658. end = v1;
  659. i = vs.pos;
  660. }
  661. if (i < s.length() && ((c = s.charAt(i)) == '/')) {
  662. i++;
  663. c = s.charAt(i);
  664. int v2 = Integer.parseInt(String.valueOf(c));
  665. i++;
  666. if (i >= s.length()) {
  667. addToSet(val, end, v2, type);
  668. return i;
  669. }
  670. c = s.charAt(i);
  671. if (c >= '0' && c <= '9') {
  672. ValueSet vs = getValue(v2, s, i);
  673. int v3 = vs.value;
  674. addToSet(val, end, v3, type);
  675. i = vs.pos;
  676. return i;
  677. } else {
  678. addToSet(val, end, v2, type);
  679. return i;
  680. }
  681. } else {
  682. addToSet(val, end, 1, type);
  683. return i;
  684. }
  685. }
  686. if (c == '/') {
  687. i++;
  688. c = s.charAt(i);
  689. int v2 = Integer.parseInt(String.valueOf(c));
  690. i++;
  691. if (i >= s.length()) {
  692. addToSet(val, end, v2, type);
  693. return i;
  694. }
  695. c = s.charAt(i);
  696. if (c >= '0' && c <= '9') {
  697. ValueSet vs = getValue(v2, s, i);
  698. int v3 = vs.value;
  699. addToSet(val, end, v3, type);
  700. i = vs.pos;
  701. return i;
  702. } else {
  703. throw new ParseException("Unexpected character '" + c + "' after '/'", i);
  704. }
  705. }
  706. addToSet(val, end, 0, type);
  707. i++;
  708. return i;
  709. }
  710. public String getCronExpression() {
  711. return cronExpression;
  712. }
  713. public String getExpressionSummary() {
  714. StringBuffer buf = new StringBuffer();
  715. buf.append("seconds: ");
  716. buf.append(getExpressionSetSummary(seconds));
  717. buf.append("\n");
  718. buf.append("minutes: ");
  719. buf.append(getExpressionSetSummary(minutes));
  720. buf.append("\n");
  721. buf.append("hours: ");
  722. buf.append(getExpressionSetSummary(hours));
  723. buf.append("\n");
  724. buf.append("daysOfMonth: ");
  725. buf.append(getExpressionSetSummary(daysOfMonth));
  726. buf.append("\n");
  727. buf.append("months: ");
  728. buf.append(getExpressionSetSummary(months));
  729. buf.append("\n");
  730. buf.append("daysOfWeek: ");
  731. buf.append(getExpressionSetSummary(daysOfWeek));
  732. buf.append("\n");
  733. buf.append("lastdayOfWeek: ");
  734. buf.append(lastdayOfWeek);
  735. buf.append("\n");
  736. buf.append("nearestWeekday: ");
  737. buf.append(nearestWeekday);
  738. buf.append("\n");
  739. buf.append("NthDayOfWeek: ");
  740. buf.append(nthdayOfWeek);
  741. buf.append("\n");
  742. buf.append("lastdayOfMonth: ");
  743. buf.append(lastdayOfMonth);
  744. buf.append("\n");
  745. buf.append("years: ");
  746. buf.append(getExpressionSetSummary(years));
  747. buf.append("\n");
  748. return buf.toString();
  749. }
  750. protected String getExpressionSetSummary(java.util.Set set) {
  751. if (set.contains(NO_SPEC)) {
  752. return "?";
  753. }
  754. if (set.contains(ALL_SPEC)) {
  755. return "*";
  756. }
  757. StringBuffer buf = new StringBuffer();
  758. Iterator itr = set.iterator();
  759. boolean first = true;
  760. while (itr.hasNext()) {
  761. Integer iVal = (Integer) itr.next();
  762. String val = iVal.toString();
  763. if (!first) {
  764. buf.append(",");
  765. }
  766. buf.append(val);
  767. first = false;
  768. }
  769. return buf.toString();
  770. }
  771. protected String getExpressionSetSummary(java.util.ArrayList list) {
  772. if (list.contains(NO_SPEC)) {
  773. return "?";
  774. }
  775. if (list.contains(ALL_SPEC)) {
  776. return "*";
  777. }
  778. StringBuffer buf = new StringBuffer();
  779. Iterator itr = list.iterator();
  780. boolean first = true;
  781. while (itr.hasNext()) {
  782. Integer iVal = (Integer) itr.next();
  783. String val = iVal.toString();
  784. if (!first) {
  785. buf.append(",");
  786. }
  787. buf.append(val);
  788. first = false;
  789. }
  790. return buf.toString();
  791. }
  792. protected int skipWhiteSpace(int i, String s) {
  793. for (; i < s.length() && (s.charAt(i) == ' ' || s.charAt(i) == '\t'); i++) {
  794. ;
  795. }
  796. return i;
  797. }
  798. protected int findNextWhiteSpace(int i, String s) {
  799. for (; i < s.length() && (s.charAt(i) != ' ' || s.charAt(i) != '\t'); i++) {
  800. ;
  801. }
  802. return i;
  803. }
  804. protected void addToSet(int val, int end, int incr, int type) throws ParseException {
  805. TreeSet set = getSet(type);
  806. if (type == SECOND || type == MINUTE) {
  807. if ((val < 0 || val > 59 || end > 59) && (val != ALL_SPEC_INT)) {
  808. throw new ParseException(
  809. "Minute and Second values must be between 0 and 59", -1);
  810. }
  811. } else if (type == HOUR) {
  812. if ((val < 0 || val > 23 || end > 23) && (val != ALL_SPEC_INT)) {
  813. throw new ParseException(
  814. "Hour values must be between 0 and 23", -1);
  815. }
  816. } else if (type == DAY_OF_MONTH) {
  817. if ((val < 1 || val > 31 || end > 31) && (val != ALL_SPEC_INT) && (val != NO_SPEC_INT)) {
  818. throw new ParseException(
  819. "Day of month values must be between 1 and 31", -1);
  820. }
  821. } else if (type == MONTH) {
  822. if ((val < 1 || val > 12 || end > 12) && (val != ALL_SPEC_INT)) {
  823. throw new ParseException(
  824. "Month values must be between 1 and 12", -1);
  825. }
  826. } else if (type == DAY_OF_WEEK) {
  827. if ((val == 0 || val > 7 || end > 7) && (val != ALL_SPEC_INT) && (val != NO_SPEC_INT)) {
  828. throw new ParseException(
  829. "Day-of-Week values must be between 1 and 7", -1);
  830. }
  831. }
  832. if ((incr == 0 || incr == -1) && val != ALL_SPEC_INT) {
  833. if (val != -1) {
  834. set.add(Integer.valueOf(val));
  835. } else {
  836. set.add(NO_SPEC);
  837. }
  838. return;
  839. }
  840. int startAt = val;
  841. int stopAt = end;
  842. if (val == ALL_SPEC_INT && incr <= 0) {
  843. incr = 1;
  844. set.add(ALL_SPEC); // put in a marker, but also fill values
  845. }
  846. if (type == SECOND || type == MINUTE) {
  847. if (stopAt == -1) {
  848. stopAt = 59;
  849. }
  850. if (startAt == -1 || startAt == ALL_SPEC_INT) {
  851. startAt = 0;
  852. }
  853. } else if (type == HOUR) {
  854. if (stopAt == -1) {
  855. stopAt = 23;
  856. }
  857. if (startAt == -1 || startAt == ALL_SPEC_INT) {
  858. startAt = 0;
  859. }
  860. } else if (type == DAY_OF_MONTH) {
  861. if (stopAt == -1) {
  862. stopAt = 31;
  863. }
  864. if (startAt == -1 || startAt == ALL_SPEC_INT) {
  865. startAt = 1;
  866. }
  867. } else if (type == MONTH) {
  868. if (stopAt == -1) {
  869. stopAt = 12;
  870. }
  871. if (startAt == -1 || startAt == ALL_SPEC_INT) {
  872. startAt = 1;
  873. }
  874. } else if (type == DAY_OF_WEEK) {
  875. if (stopAt == -1) {
  876. stopAt = 7;
  877. }
  878. if (startAt == -1 || startAt == ALL_SPEC_INT) {
  879. startAt = 1;
  880. }
  881. } else if (type == YEAR) {
  882. if (stopAt == -1) {
  883. stopAt = YEAR_TO_GIVEUP_SCHEDULING_AT;
  884. }
  885. if (startAt == -1 || startAt == ALL_SPEC_INT) {
  886. startAt = 1970;
  887. }
  888. }
  889. // if the end of the range is before the start, then we need to overflow
  890. // into
  891. // the next day, month etc. This is done by adding the maximum amount
  892. // for that
  893. // type, and using modulus max to determine the value being added.
  894. int max = -1;
  895. if (stopAt < startAt) {
  896. switch (type) {
  897. case SECOND:
  898. max = 60;
  899. break;
  900. case MINUTE:
  901. max = 60;
  902. break;
  903. case HOUR:
  904. max = 24;
  905. break;
  906. case MONTH:
  907. max = 12;
  908. break;
  909. case DAY_OF_WEEK:
  910. max = 7;
  911. break;
  912. case DAY_OF_MONTH:
  913. max = 31;
  914. break;
  915. case YEAR:
  916. throw new IllegalArgumentException("Start year must be less than stop year");
  917. default:
  918. throw new IllegalArgumentException("Unexpected type encountered");
  919. }
  920. stopAt += max;
  921. }
  922. for (int i = startAt; i <= stopAt; i += incr) {
  923. if (max == -1) {
  924. // ie: there's no max to overflow over
  925. set.add(Integer.valueOf(i));
  926. } else {
  927. // take the modulus to get the real value
  928. int i2 = i % max;
  929. // 1-indexed ranges should not include 0, and should include
  930. // their max
  931. if (i2 == 0 && (type == MONTH || type == DAY_OF_WEEK || type == DAY_OF_MONTH)) {
  932. i2 = max;
  933. }
  934. set.add(Integer.valueOf(i2));
  935. }
  936. }
  937. }
  938. protected TreeSet getSet(int type) {
  939. switch (type) {
  940. case SECOND:
  941. return seconds;
  942. case MINUTE:
  943. return minutes;
  944. case HOUR:
  945. return hours;
  946. case DAY_OF_MONTH:
  947. return daysOfMonth;
  948. case MONTH:
  949. return months;
  950. case DAY_OF_WEEK:
  951. return daysOfWeek;
  952. case YEAR:
  953. return years;
  954. default:
  955. return null;
  956. }
  957. }
  958. protected ValueSet getValue(int v, String s, int i) {
  959. char c = s.charAt(i);
  960. String s1 = String.valueOf(v);
  961. while (c >= '0' && c <= '9') {
  962. s1 += c;
  963. i++;
  964. if (i >= s.length()) {
  965. break;
  966. }
  967. c = s.charAt(i);
  968. }
  969. ValueSet val = new ValueSet();
  970. val.pos = (i < s.length()) ? i : i + 1;
  971. val.value = Integer.parseInt(s1);
  972. return val;
  973. }
  974. protected int getNumericValue(String s, int i) {
  975. int endOfVal = findNextWhiteSpace(i, s);
  976. String val = s.substring(i, endOfVal);
  977. return Integer.parseInt(val);
  978. }
  979. protected int getMonthNumber(String s) {
  980. Integer integer = (Integer) monthMap.get(s);
  981. if (integer == null) {
  982. return -1;
  983. }
  984. return integer.intValue();
  985. }
  986. protected int getDayOfWeekNumber(String s) {
  987. Integer integer = (Integer) dayMap.get(s);
  988. if (integer == null) {
  989. return -1;
  990. }
  991. return integer.intValue();
  992. }
  993. // //////////////////////////////////////////////////////////////////////////
  994. //
  995. // Computation Functions
  996. //
  997. // //////////////////////////////////////////////////////////////////////////
  998. protected Date getTimeAfter(Date afterTime) {
  999. // Computation is based on Gregorian year only.
  1000. Calendar cl = new java.util.GregorianCalendar(getTimeZone());
  1001. // move ahead one second, since we're computing the time *after* the
  1002. // given time
  1003. afterTime = new Date(afterTime.getTime() + 1000);
  1004. // CronTrigger does not deal with milliseconds
  1005. cl.setTime(afterTime);
  1006. cl.set(Calendar.MILLISECOND, 0);
  1007. boolean gotOne = false;
  1008. // loop until we've computed the next time, or we've past the endTime
  1009. while (!gotOne) {
  1010. if (cl.get(Calendar.YEAR) > 2999) { // prevent endless loop...
  1011. return null;
  1012. }
  1013. SortedSet st = null;
  1014. int t = 0;
  1015. int sec = cl.get(Calendar.SECOND);
  1016. int min = cl.get(Calendar.MINUTE);
  1017. // get second.................................................
  1018. st = seconds.tailSet(Integer.valueOf(sec));
  1019. if (st != null && st.size() != 0) {
  1020. sec = ((Integer) st.first()).intValue();
  1021. } else {
  1022. sec = ((Integer) seconds.first()).intValue();
  1023. min++;
  1024. cl.set(Calendar.MINUTE, min);
  1025. }
  1026. cl.set(Calendar.SECOND, sec);
  1027. min = cl.get(Calendar.MINUTE);
  1028. int hr = cl.get(Calendar.HOUR_OF_DAY);
  1029. t = -1;
  1030. // get minute.................................................
  1031. st = minutes.tailSet(Integer.valueOf(min));
  1032. if (st != null && st.size() != 0) {
  1033. t = min;
  1034. min = ((Integer) st.first()).intValue();
  1035. } else {
  1036. min = ((Integer) minutes.first()).intValue();
  1037. hr++;
  1038. }
  1039. if (min != t) {
  1040. cl.set(Calendar.SECOND, 0);
  1041. cl.set(Calendar.MINUTE, min);
  1042. setCalendarHour(cl, hr);
  1043. continue;
  1044. }
  1045. cl.set(Calendar.MINUTE, min);
  1046. hr = cl.get(Calendar.HOUR_OF_DAY);
  1047. int day = cl.get(Calendar.DAY_OF_MONTH);
  1048. t = -1;
  1049. // get hour...................................................
  1050. st = hours.tailSet(Integer.valueOf(hr));
  1051. if (st != null && st.size() != 0) {
  1052. t = hr;
  1053. hr = ((Integer) st.first()).intValue();
  1054. } else {
  1055. hr = ((Integer) hours.first()).intValue();
  1056. day++;
  1057. }
  1058. if (hr != t) {
  1059. cl.set(Calendar.SECOND, 0);
  1060. cl.set(Calendar.MINUTE, 0);
  1061. cl.set(Calendar.DAY_OF_MONTH, day);
  1062. setCalendarHour(cl, hr);
  1063. continue;
  1064. }
  1065. cl.set(Calendar.HOUR_OF_DAY, hr);
  1066. day = cl.get(Calendar.DAY_OF_MONTH);
  1067. int mon = cl.get(Calendar.MONTH) + 1;
  1068. // '+ 1' because calendar is 0-based for this field, and we are
  1069. // 1-based
  1070. t = -1;
  1071. int tmon = mon;
  1072. // get day...................................................
  1073. boolean dayOfMSpec = !daysOfMonth.contains(NO_SPEC);
  1074. boolean dayOfWSpec = !daysOfWeek.contains(NO_SPEC);
  1075. if (dayOfMSpec && !dayOfWSpec) { // get day by day of month rule
  1076. st = daysOfMonth.tailSet(Integer.valueOf(day));
  1077. if (lastdayOfMonth) {
  1078. if (!nearestWeekday) {
  1079. t = day;
  1080. day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
  1081. } else {
  1082. t = day;
  1083. day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
  1084. java.util.Calendar tcal = java.util.Calendar.getInstance(getTimeZone());
  1085. tcal.set(Calendar.SECOND, 0);
  1086. tcal.set(Calendar.MINUTE, 0);
  1087. tcal.set(Calendar.HOUR_OF_DAY, 0);
  1088. tcal.set(Calendar.DAY_OF_MONTH, day);
  1089. tcal.set(Calendar.MONTH, mon - 1);
  1090. tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR));
  1091. int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
  1092. int dow = tcal.get(Calendar.DAY_OF_WEEK);
  1093. if (dow == Calendar.SATURDAY && day == 1) {
  1094. day += 2;
  1095. } else if (dow == Calendar.SATURDAY) {
  1096. day -= 1;
  1097. } else if (dow == Calendar.SUNDAY && day == ldom) {
  1098. day -= 2;
  1099. } else if (dow == Calendar.SUNDAY) {
  1100. day += 1;
  1101. }
  1102. tcal.set(Calendar.SECOND, sec);
  1103. tcal.set(Calendar.MINUTE, min);
  1104. tcal.set(Calendar.HOUR_OF_DAY, hr);
  1105. tcal.set(Calendar.DAY_OF_MONTH, day);
  1106. tcal.set(Calendar.MONTH, mon - 1);
  1107. Date nTime = tcal.getTime();
  1108. if (nTime.before(afterTime)) {
  1109. day = 1;
  1110. mon++;
  1111. }
  1112. }
  1113. } else if (nearestWeekday) {
  1114. t = day;
  1115. day = ((Integer) daysOfMonth.first()).intValue();
  1116. java.util.Calendar tcal = java.util.Calendar.getInstance(getTimeZone());
  1117. tcal.set(Calendar.SECOND, 0);
  1118. tcal.set(Calendar.MINUTE, 0);
  1119. tcal.set(Calendar.HOUR_OF_DAY, 0);
  1120. tcal.set(Calendar.DAY_OF_MONTH, day);
  1121. tcal.set(Calendar.MONTH, mon - 1);
  1122. tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR));
  1123. int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
  1124. int dow = tcal.get(Calendar.DAY_OF_WEEK);
  1125. if (dow == Calendar.SATURDAY && day == 1) {
  1126. day += 2;
  1127. } else if (dow == Calendar.SATURDAY) {
  1128. day -= 1;
  1129. } else if (dow == Calendar.SUNDAY && day == ldom) {
  1130. day -= 2;
  1131. } else if (dow == Calendar.SUNDAY) {
  1132. day += 1;
  1133. }
  1134. tcal.set(Calendar.SECOND, sec);
  1135. tcal.set(Calendar.MINUTE, min);
  1136. tcal.set(Calendar.HOUR_OF_DAY, hr);
  1137. tcal.set(Calendar.DAY_OF_MONTH, day);
  1138. tcal.set(Calendar.MONTH, mon - 1);
  1139. Date nTime = tcal.getTime();
  1140. if (nTime.before(afterTime)) {
  1141. day = ((Integer) daysOfMonth.first()).intValue();
  1142. mon++;
  1143. }
  1144. } else if (st != null && st.size() != 0) {
  1145. t = day;
  1146. day = ((Integer) st.first()).intValue();
  1147. // make sure we don't over-run a short month, such as
  1148. // february
  1149. int lastDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
  1150. if (day > lastDay) {
  1151. day = ((Integer) daysOfMonth.first()).intValue();
  1152. mon++;
  1153. }
  1154. } else {
  1155. day = ((Integer) daysOfMonth.first()).intValue();
  1156. mon++;
  1157. }
  1158. if (day != t || mon != tmon) {
  1159. cl.set(Calendar.SECOND, 0);
  1160. cl.set(Calendar.MINUTE, 0);
  1161. cl.set(Calendar.HOUR_OF_DAY, 0);
  1162. cl.set(Calendar.DAY_OF_MONTH, day);
  1163. cl.set(Calendar.MONTH, mon - 1);
  1164. // '- 1' because calendar is 0-based for this field, and we
  1165. // are 1-based
  1166. continue;
  1167. }
  1168. } else if (dayOfWSpec && !dayOfMSpec) { // get day by day of week
  1169. // rule
  1170. if (lastdayOfWeek) { // are we looking for the last XXX day of
  1171. // the month?
  1172. int dow = ((Integer) daysOfWeek.first()).intValue(); // desired
  1173. // d-o-w
  1174. int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
  1175. int daysToAdd = 0;
  1176. if (cDow < dow) {
  1177. daysToAdd = dow - cDow;
  1178. }
  1179. if (cDow > dow) {
  1180. daysToAdd = dow + (7 - cDow);
  1181. }
  1182. int lDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
  1183. if (day + daysToAdd > lDay) { // did we already miss the
  1184. // last one?
  1185. cl.set(Calendar.SECOND, 0);
  1186. cl.set(Calendar.MINUTE, 0);
  1187. cl.set(Calendar.HOUR_OF_DAY, 0);
  1188. cl.set(Calendar.DAY_OF_MONTH, 1);
  1189. cl.set(Calendar.MONTH, mon);
  1190. // no '- 1' here because we are promoting the month
  1191. continue;
  1192. }
  1193. // find date of last occurance of this day in this month...
  1194. while ((day + daysToAdd + 7) <= lDay) {
  1195. daysToAdd += 7;
  1196. }
  1197. day += daysToAdd;
  1198. if (daysToAdd > 0) {
  1199. cl.set(Calendar.SECOND, 0);
  1200. cl.set(Calendar.MINUTE, 0);
  1201. cl.set(Calendar.HOUR_OF_DAY, 0);
  1202. cl.set(Calendar.DAY_OF_MONTH, day);
  1203. cl.set(Calendar.MONTH, mon - 1);
  1204. // '- 1' here because we are not promoting the month
  1205. continue;
  1206. }
  1207. } else if (nthdayOfWeek != 0) {
  1208. // are we looking for the Nth XXX day in the month?
  1209. int dow = ((Integer) daysOfWeek.first()).intValue(); // desired
  1210. // d-o-w
  1211. int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
  1212. int daysToAdd = 0;
  1213. if (cDow < dow) {
  1214. daysToAdd = dow - cDow;
  1215. } else if (cDow > dow) {
  1216. daysToAdd = dow + (7 - cDow);
  1217. }
  1218. boolean dayShifted = false;
  1219. if (daysToAdd > 0) {
  1220. dayShifted = true;
  1221. }
  1222. day += daysToAdd;
  1223. int weekOfMonth = day / 7;
  1224. if (day % 7 > 0) {
  1225. weekOfMonth++;
  1226. }
  1227. daysToAdd = (nthdayOfWeek - weekOfMonth) * 7;
  1228. day += daysToAdd;
  1229. if (daysToAdd < 0 || day > getLastDayOfMonth(mon, cl.get(Calendar.YEAR))) {
  1230. cl.set(Calendar.SECOND, 0);
  1231. cl.set(Calendar.MINUTE, 0);
  1232. cl.set(Calendar.HOUR_OF_DAY, 0);
  1233. cl.set(Calendar.DAY_OF_MONTH, 1);
  1234. cl.set(Calendar.MONTH, mon);
  1235. // no '- 1' here because we are promoting the month
  1236. continue;
  1237. } else if (daysToAdd > 0 || dayShifted) {
  1238. cl.set(Calendar.SECOND, 0);
  1239. cl.set(Calendar.MINUTE, 0);
  1240. cl.set(Calendar.HOUR_OF_DAY, 0);
  1241. cl.set(Calendar.DAY_OF_MONTH, day);
  1242. cl.set(Calendar.MONTH, mon - 1);
  1243. // '- 1' here because we are NOT promoting the month
  1244. continue;
  1245. }
  1246. } else {
  1247. int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
  1248. int dow = ((Integer) daysOfWeek.first()).intValue(); // desired
  1249. // d-o-w
  1250. st = daysOfWeek.tailSet(Integer.valueOf(cDow));
  1251. if (st != null && st.size() > 0) {
  1252. dow = ((Integer) st.first()).intValue();
  1253. }
  1254. int daysToAdd = 0;
  1255. if (cDow < dow) {
  1256. daysToAdd = dow - cDow;
  1257. }
  1258. if (cDow > dow) {
  1259. daysToAdd = dow + (7 - cDow);
  1260. }
  1261. int lDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
  1262. if (day + daysToAdd > lDay) { // will we pass the end of
  1263. // the month?
  1264. cl.set(Calendar.SECOND, 0);
  1265. cl.set(Calendar.MINUTE, 0);
  1266. cl.set(Calendar.HOUR_OF_DAY, 0);
  1267. cl.set(Calendar.DAY_OF_MONTH, 1);
  1268. cl.set(Calendar.MONTH, mon);
  1269. // no '- 1' here because we are promoting the month
  1270. continue;
  1271. } else if (daysToAdd > 0) { // are we swithing days?
  1272. cl.set(Calendar.SECOND, 0);
  1273. cl.set(Calendar.MINUTE, 0);
  1274. cl.set(Calendar.HOUR_OF_DAY, 0);
  1275. cl.set(Calendar.DAY_OF_MONTH, day + daysToAdd);
  1276. cl.set(Calendar.MONTH, mon - 1);
  1277. // '- 1' because calendar is 0-based for this field,
  1278. // and we are 1-based
  1279. continue;
  1280. }
  1281. }
  1282. } else { // dayOfWSpec && !dayOfMSpec
  1283. throw new UnsupportedOperationException(
  1284. "Support for specifying both a day-of-week AND a day-of-month parameter is not implemented.");
  1285. // TODO:
  1286. }
  1287. cl.set(Calendar.DAY_OF_MONTH, day);
  1288. mon = cl.get(Calendar.MONTH) + 1;
  1289. // '+ 1' because calendar is 0-based for this field, and we are
  1290. // 1-based
  1291. int year = cl.get(Calendar.YEAR);
  1292. t = -1;
  1293. // test for expressions that never generate a valid fire date,
  1294. // but keep looping...
  1295. if (year > YEAR_TO_GIVEUP_SCHEDULING_AT) {
  1296. return null;
  1297. }
  1298. // get month...................................................
  1299. st = months.tailSet(Integer.valueOf(mon));
  1300. if (st != null && st.size() != 0) {
  1301. t = mon;
  1302. mon = ((Integer) st.first()).intValue();
  1303. } else {
  1304. mon = ((Integer) months.first()).intValue();
  1305. year++;
  1306. }
  1307. if (mon != t) {
  1308. cl.set(Calendar.SECOND, 0);
  1309. cl.set(Calendar.MINUTE, 0);
  1310. cl.set(Calendar.HOUR_OF_DAY, 0);
  1311. cl.set(Calendar.DAY_OF_MONTH, 1);
  1312. cl.set(Calendar.MONTH, mon - 1);
  1313. // '- 1' because calendar is 0-based for this field, and we are
  1314. // 1-based
  1315. cl.set(Calendar.YEAR, year);
  1316. continue;
  1317. }
  1318. cl.set(Calendar.MONTH, mon - 1);
  1319. // '- 1' because calendar is 0-based for this field, and we are
  1320. // 1-based
  1321. year = cl.get(Calendar.YEAR);
  1322. t = -1;
  1323. // get year...................................................
  1324. st = years.tailSet(Integer.valueOf(year));
  1325. if (st != null && st.size() != 0) {
  1326. t = year;
  1327. year = ((Integer) st.first()).intValue();
  1328. } else {
  1329. return null; // ran out of years...
  1330. }
  1331. if (year != t) {
  1332. cl.set(Calendar.SECOND, 0);
  1333. cl.set(Calendar.MINUTE, 0);
  1334. cl.set(Calendar.HOUR_OF_DAY, 0);
  1335. cl.set(Calendar.DAY_OF_MONTH, 1);
  1336. cl.set(Calendar.MONTH, 0);
  1337. // '- 1' because calendar is 0-based for this field, and we are
  1338. // 1-based
  1339. cl.set(Calendar.YEAR, year);
  1340. continue;
  1341. }
  1342. cl.set(Calendar.YEAR, year);
  1343. gotOne = true;
  1344. }
  1345. return cl.getTime();
  1346. }
  1347. /**
  1348. * Advance the calendar to the particular hour paying particular attention
  1349. * to daylight saving problems.
  1350. *
  1351. * @param cal
  1352. * @param hour
  1353. */
  1354. protected void setCalendarHour(Calendar cal, int hour) {
  1355. cal.set(java.util.Calendar.HOUR_OF_DAY, hour);
  1356. if (cal.get(java.util.Calendar.HOUR_OF_DAY) != hour && hour != 24) {
  1357. cal.set(java.util.Calendar.HOUR_OF_DAY, hour + 1);
  1358. }
  1359. }
  1360. /**
  1361. * NOT YET IMPLEMENTED: Returns the time before the given time
  1362. * that the <code>CronExpression</code> matches.
  1363. */
  1364. protected Date getTimeBefore(Date endTime) {
  1365. // TODO: implement QUARTZ-423
  1366. return null;
  1367. }
  1368. /**
  1369. * NOT YET IMPLEMENTED: Returns the final time that the
  1370. * <code>CronExpression</code> will match.
  1371. */
  1372. public Date getFinalFireTime() {
  1373. // TODO: implement QUARTZ-423
  1374. return null;
  1375. }
  1376. protected boolean isLeapYear(int year) {
  1377. return ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0));
  1378. }
  1379. protected int getLastDayOfMonth(int monthNum, int year) {
  1380. switch (monthNum) {
  1381. case 1:
  1382. return 31;
  1383. case 2:
  1384. return (isLeapYear(year)) ? 29 : 28;
  1385. case 3:
  1386. return 31;
  1387. case 4:
  1388. return 30;
  1389. case 5:
  1390. return 31;
  1391. case 6:
  1392. return 30;
  1393. case 7:
  1394. return 31;
  1395. case 8:
  1396. return 31;
  1397. case 9:
  1398. return 30;
  1399. case 10:
  1400. return 31;
  1401. case 11:
  1402. return 30;
  1403. case 12:
  1404. return 31;
  1405. default:
  1406. throw new IllegalArgumentException("Illegal month number: " + monthNum);
  1407. }
  1408. }
  1409. private void readObject(java.io.ObjectInputStream stream) throws java.io.IOException, ClassNotFoundException {
  1410. stream.defaultReadObject();
  1411. try {
  1412. buildExpression(cronExpression);
  1413. } catch (Exception ignore) {
  1414. logger.error(ignore.getMessage());
  1415. }
  1416. }
  1417. public Object clone() {
  1418. CronExpression copy = null;
  1419. try {
  1420. copy = new CronExpression(getCronExpression());
  1421. if (getTimeZone() != null) {
  1422. copy.setTimeZone((TimeZone) getTimeZone().clone());
  1423. }
  1424. } catch (ParseException ex) { // never happens since the source is
  1425. // valid...
  1426. throw new IncompatibleClassChangeError("Not Cloneable.");
  1427. }
  1428. return copy;
  1429. }
  1430. public String getSeconds() {
  1431. return getExpressionSetSummary(seconds);
  1432. }
  1433. public String getMinutes() {
  1434. return getExpressionSetSummary(minutes);
  1435. }
  1436. public String getHours() {
  1437. return getExpressionSetSummary(hours);
  1438. }
  1439. public String getDaysOfMonth() {
  1440. return getExpressionSetSummary(daysOfMonth);
  1441. }
  1442. public String getMonths() {
  1443. return getExpressionSetSummary(months);
  1444. }
  1445. public String getDaysOfWeek() {
  1446. return getExpressionSetSummary(daysOfWeek);
  1447. }
  1448. public String getYears() {
  1449. return getExpressionSetSummary(years);
  1450. }
  1451. public boolean isLastdayOfWeek() {
  1452. return lastdayOfWeek;
  1453. }
  1454. public int getNthdayOfWeek() {
  1455. return nthdayOfWeek;
  1456. }
  1457. public boolean isLastdayOfMonth() {
  1458. return lastdayOfMonth;
  1459. }
  1460. public boolean isNearestWeekday() {
  1461. return nearestWeekday;
  1462. }
  1463. public boolean isExpressionParsed() {
  1464. return expressionParsed;
  1465. }
  1466. }
  1467. class ValueSet {
  1468. public int value;
  1469. public int pos;
  1470. }