PageRenderTime 46ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/resin/src/main/java/com/caucho/config/timer/CronTrigger.java

http://github.com/CleverCloud/Quercus
Java | 947 lines | 683 code | 172 blank | 92 comment | 268 complexity | 0f352c3ebead3f7a4ab082db45e2cb91 MD5 | raw file
Possible License(s): GPL-2.0
  1. /*
  2. * Copyright (c) 1998-2010 Caucho Technology -- all rights reserved
  3. *
  4. * This file is part of Resin(R) Open Source
  5. *
  6. * Each copy or derived work must preserve the copyright notice and this
  7. * notice unmodified.
  8. *
  9. * Resin Open Source is free software; you can redistribute it and/or modify
  10. * it under the terms of the GNU General Public License as published by
  11. * the Free Software Foundation; either version 2 of the License, or
  12. * (at your option) any later version.
  13. *
  14. * Resin Open Source is distributed in the hope that it will be useful,
  15. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
  17. * of NON-INFRINGEMENT. See the GNU General Public License for more
  18. * details.
  19. *
  20. * You should have received a copy of the GNU General Public License
  21. * along with Resin Open Source; if not, write to the
  22. *
  23. * Free Software Foundation, Inc.
  24. * 59 Temple Place, Suite 330
  25. * Boston, MA 02111-1307 USA
  26. *
  27. * @author Reza Rahman
  28. */
  29. package com.caucho.config.timer;
  30. import java.util.LinkedList;
  31. import java.util.List;
  32. import java.util.TimeZone;
  33. import java.util.concurrent.atomic.AtomicReference;
  34. import com.caucho.config.ConfigException;
  35. import com.caucho.config.types.Trigger;
  36. import com.caucho.util.L10N;
  37. import com.caucho.util.QDate;
  38. /**
  39. * Implements a cron-style trigger. This trigger is primarily intended for the
  40. * EJB calendar style timer service functionality.
  41. */
  42. // TODO Is this class getting too large? Maybe separate into a
  43. // parser/lexer/interpreter sub-component? Also, is parsing better done/more
  44. // readable/maintainable via creating compiler style grammar tokens instead of
  45. // direct String processing?
  46. public class CronTrigger implements Trigger {
  47. private static final L10N L = new L10N(CronTrigger.class);
  48. // Order of search is important in the token maps.
  49. private static final String [][] MONTH_TOKEN_MAP = { { "january", "1" },
  50. { "february", "2" }, { "march", "3" }, { "april", "4" }, { "may", "5" },
  51. { "june", "6" }, { "july", "7" }, { "august", "8" },
  52. { "september", "9" }, { "october", "10" }, { "november", "11" },
  53. { "december", "12" }, { "jan", "1" }, { "feb", "2" }, { "mar", "3" },
  54. { "apr", "4" }, { "jun", "6" }, { "jul", "7" }, { "aug", "8" },
  55. { "sep", "9" }, { "oct", "10" }, { "nov", "11" }, { "dec", "12" } };
  56. private static final String [][] DAY_OF_WEEK_TOKEN_MAP = { { "sunday", "0" },
  57. { "monday", "1" }, { "tuesday", "2" }, { "wednesday", "3" },
  58. { "thursday", "4" }, { "friday", "5" }, { "saturday", "6" },
  59. { "sun", "0" }, { "mon", "1" }, { "tue", "2" }, { "wed", "3" },
  60. { "thu", "4" }, { "fri", "5" }, { "sat", "6" } };
  61. private static final String [][] RELATIVE_DAY_OF_WEEK_TOKEN_MAP = {
  62. { "last", "-0" }, { "1st", "1" }, { "2nd", "2" }, { "3rd", "3" },
  63. { "4th", "4" }, { "5th", "5" } };
  64. private AtomicReference<QDate> _localCalendar = new AtomicReference<QDate>();
  65. private boolean [] _seconds;
  66. private boolean [] _minutes;
  67. private boolean [] _hours;
  68. private boolean _isDaysFilterRelative;
  69. private boolean [] _days;
  70. private String _daysFilter;
  71. private boolean [] _months;
  72. private boolean [] _daysOfWeek;
  73. private YearsFilter _yearsFilter;
  74. private TimeZone _timezone = TimeZone.getTimeZone("GMT");
  75. private long _start = -1;
  76. private long _end = -1;
  77. /**
  78. * Creates new cron trigger.
  79. *
  80. * @param cronExpression
  81. * The cron expression to create the trigger from.
  82. * @param start
  83. * The date the trigger should begin firing, in milliseconds. -1
  84. * indicates that no start date should be enforced.
  85. * @param end
  86. * The date the trigger should end firing, in milliseconds. -1
  87. * indicates that no end date should be enforced.
  88. * @param string
  89. */
  90. public CronTrigger(final CronExpression cronExpression, final long start,
  91. final long end, TimeZone timezone)
  92. {
  93. if (cronExpression.getSecond() != null) {
  94. _seconds = parseRange(cronExpression.getSecond(), 0, 59, false);
  95. }
  96. if (cronExpression.getMinute() != null) {
  97. _minutes = parseRange(cronExpression.getMinute(), 0, 59, false);
  98. }
  99. if (cronExpression.getHour() != null) {
  100. _hours = parseRange(cronExpression.getHour(), 0, 23, false);
  101. }
  102. if (cronExpression.getDayOfWeek() != null) {
  103. _daysOfWeek = parseRange(
  104. tokenizeDayOfWeek(cronExpression.getDayOfWeek()), 0, 7, false);
  105. }
  106. if (_daysOfWeek[7]) { // Both 0 and 7 are Sunday, as in UNIX cron.
  107. _daysOfWeek[0] = _daysOfWeek[7];
  108. }
  109. if (cronExpression.getDayOfMonth() != null) {
  110. _daysFilter = tokenizeDayOfMonth(cronExpression.getDayOfMonth());
  111. _days = parseRange(_daysFilter, 1, 31, true);
  112. }
  113. if (cronExpression.getMonth() != null) {
  114. _months = parseRange(tokenizeMonth(cronExpression.getMonth()), 1, 12,
  115. false);
  116. }
  117. if (cronExpression.getYear() != null) {
  118. _yearsFilter = parseYear(cronExpression.getYear());
  119. }
  120. if (timezone != null) {
  121. _timezone = timezone;
  122. }
  123. _start = start;
  124. _end = end;
  125. }
  126. private String tokenizeDayOfWeek(String dayOfWeek)
  127. {
  128. dayOfWeek = tokenize(dayOfWeek, DAY_OF_WEEK_TOKEN_MAP);
  129. return dayOfWeek;
  130. }
  131. private String tokenizeDayOfMonth(String dayOfMonth)
  132. {
  133. dayOfMonth = tokenize(dayOfMonth, RELATIVE_DAY_OF_WEEK_TOKEN_MAP);
  134. dayOfMonth = tokenize(dayOfMonth, DAY_OF_WEEK_TOKEN_MAP);
  135. return dayOfMonth;
  136. }
  137. private String tokenizeMonth(String month)
  138. {
  139. month = tokenize(month, MONTH_TOKEN_MAP);
  140. return month;
  141. }
  142. private String tokenize(String value, String [][] tokenMap)
  143. {
  144. // TODO The String processing is more resource intensive than necessary. See
  145. // if StringBuilder can work with regex?
  146. for (int i = 0; i < tokenMap.length; i++) {
  147. value = value.replaceAll("(?i)" + tokenMap[i][0], tokenMap[i][1]);
  148. }
  149. return value;
  150. }
  151. /**
  152. * parses a range, following cron rules.
  153. */
  154. // TODO This does not handle extra spaces between tokens, should it?
  155. private boolean [] parseRange(String range, int rangeMin, int rangeMax,
  156. boolean parseDayOfMonth) throws ConfigException
  157. {
  158. boolean [] values = new boolean[rangeMax + 1];
  159. int i = 0;
  160. while (i < range.length()) {
  161. char character = range.charAt(i);
  162. int min = 0;
  163. int max = 0;
  164. int increment = 1;
  165. if (character == '*') {
  166. min = rangeMin;
  167. max = rangeMax;
  168. i++;
  169. } else if ('0' <= character && character <= '9') {
  170. for (; i < range.length() && '0' <= (character = range.charAt(i))
  171. && character <= '9'; i++) {
  172. min = 10 * min + character - '0';
  173. }
  174. if (i < range.length() && character == '-') {
  175. for (i++; i < range.length() && '0' <= (character = range.charAt(i))
  176. && character <= '9'; i++) {
  177. max = 10 * max + character - '0';
  178. }
  179. } else if (parseDayOfMonth && (i < range.length())
  180. && (character == ' ')) {
  181. // This is just for further parsing validation, the filter value
  182. // cannot be processed right now.
  183. _isDaysFilterRelative = true; // This is the Nth weekday case.
  184. int dayOfWeek = 0;
  185. for (i++; i < range.length() && '0' <= (character = range.charAt(i))
  186. && character <= '9'; i++) {
  187. dayOfWeek = 10 * dayOfWeek + character - '0';
  188. }
  189. if ((dayOfWeek < 0) || (dayOfWeek > 6)) {
  190. throw new ConfigException(L.l(
  191. "'{0}' is an illegal cron range (day of week out of range)",
  192. range));
  193. }
  194. if ((min < 1) || (min > 5)) {
  195. throw new ConfigException(L.l(
  196. "'{0}' is an illegal cron range (invalid day of week)", range));
  197. }
  198. if (i < range.length()) {
  199. if ((i < (range.length() - 1)) && (character == ',')) {
  200. i++;
  201. } else {
  202. throw new ConfigException(L.l(
  203. "'{0}' is an illegal cron range (invalid syntax)", range));
  204. }
  205. }
  206. continue;
  207. } else {
  208. max = min;
  209. }
  210. } else {
  211. if (parseDayOfMonth && (character == '-')) { // This is a -N days of
  212. // month case.
  213. _isDaysFilterRelative = true;
  214. // This is just for further parsing validation, the filter value
  215. // cannot be processed right now.
  216. int dayOfMonth = 0;
  217. for (i++; i < range.length() && '0' <= (character = range.charAt(i))
  218. && character <= '9'; i++) {
  219. // Don't need to do anything, evaluation will be done later, just
  220. // need to validate parsing for now.
  221. dayOfMonth = 10 * dayOfMonth + character - '0';
  222. }
  223. if ((dayOfMonth < 0) || (dayOfMonth > 30)) {
  224. throw new ConfigException(L.l(
  225. "'{0}' is an illegal cron range (day of month out of range)",
  226. range));
  227. }
  228. if ((i < range.length()) && ((character = range.charAt(i)) == '/')) {
  229. increment = 0;
  230. for (i++; i < range.length()
  231. && '0' <= (character = range.charAt(i)) && character <= '9'; i++) {
  232. increment = 10 * increment + character - '0';
  233. }
  234. if ((increment < rangeMin) && (increment > rangeMax)) {
  235. throw new ConfigException(
  236. L
  237. .l(
  238. "'{0}' is an illegal cron range (increment value out of range)",
  239. range));
  240. }
  241. }
  242. // The case of the last (-0) weekday
  243. if ((i < range.length()) && ((character = range.charAt(i)) == ' ')) {
  244. // Just need to do validation parsing, evaluation will be done
  245. // later.
  246. int dayOfWeek = 0;
  247. for (i++; i < range.length()
  248. && '0' <= (character = range.charAt(i)) && character <= '9'; i++) {
  249. dayOfWeek = 10 * dayOfWeek + character - '0';
  250. }
  251. if ((dayOfWeek < 0) || (dayOfWeek > 6)) {
  252. throw new ConfigException(L.l(
  253. "'{0}' is an illegal cron range (day of week out of range)",
  254. range));
  255. }
  256. if (min != 0) {
  257. throw new ConfigException(L.l(
  258. "'{0}' is an illegal cron range (invalid syntax)", range));
  259. }
  260. }
  261. if (i < range.length()) {
  262. if ((i < (range.length() - 1)) && (character == ',')) {
  263. i++;
  264. } else {
  265. throw new ConfigException(L.l(
  266. "'{0}' is an illegal cron range (invalid syntax)", range));
  267. }
  268. }
  269. continue;
  270. } else {
  271. throw new ConfigException(L.l(
  272. "'{0}' is an illegal cron range (invalid syntax)", range));
  273. }
  274. }
  275. if (min < rangeMin) {
  276. throw new ConfigException(L.l(
  277. "'{0}' is an illegal cron range (min value is too small)", range));
  278. } else if (max > rangeMax) {
  279. throw new ConfigException(L.l(
  280. "'{0}' is an illegal cron range (max value is too large)", range));
  281. }
  282. if ((i < range.length()) && ((character = range.charAt(i)) == '/')) {
  283. increment = 0;
  284. for (i++; i < range.length() && '0' <= (character = range.charAt(i))
  285. && character <= '9'; i++) {
  286. increment = 10 * increment + character - '0';
  287. }
  288. if (min == max) { // This is in the form of N/M, where N is the interval
  289. // start.
  290. max = rangeMax;
  291. }
  292. if ((increment < rangeMin) && (increment > rangeMax)) {
  293. throw new ConfigException(L.l(
  294. "'{0}' is an illegal cron range (increment value out of range)",
  295. range));
  296. }
  297. }
  298. if (i < range.length()) {
  299. if ((i < (range.length() - 1)) && (character == ',')) {
  300. i++;
  301. } else {
  302. throw new ConfigException(L.l(
  303. "'{0}' is an illegal cron range (invalid syntax)", range));
  304. }
  305. }
  306. for (; min <= max; min += increment) {
  307. values[min] = true;
  308. }
  309. }
  310. return values;
  311. }
  312. private YearsFilter parseYear(String year)
  313. {
  314. YearsFilter yearsFilter = new YearsFilter();
  315. int i = 0;
  316. while (i < year.length()) {
  317. YearsFilterValue filterValue = new YearsFilterValue();
  318. char character = year.charAt(i);
  319. if (character == '*') {
  320. filterValue.setAnyYear(true);
  321. i++;
  322. } else if ('0' <= character && character <= '9') {
  323. int startYear = 0;
  324. for (; i < year.length() && '0' <= (character = year.charAt(i))
  325. && character <= '9'; i++) {
  326. startYear = 10 * startYear + character - '0';
  327. }
  328. filterValue.setStartYear(startYear);
  329. if (i < year.length() && character == '-') {
  330. int endYear = 0;
  331. for (i++; i < year.length() && '0' <= (character = year.charAt(i))
  332. && character <= '9'; i++) {
  333. endYear = 10 * endYear + character - '0';
  334. }
  335. filterValue.setEndYear(endYear);
  336. } else {
  337. filterValue.setEndYear(startYear);
  338. }
  339. } else {
  340. throw new ConfigException(L.l("'{0}' is an illegal cron range", year));
  341. }
  342. if ((i < year.length()) && ((character = year.charAt(i)) == '/')) {
  343. filterValue.setAnyYear(false);
  344. int increment = 0;
  345. for (i++; i < year.length() && '0' <= (character = year.charAt(i))
  346. && character <= '9'; i++) {
  347. increment = 10 * increment + character - '0';
  348. }
  349. if (increment == 0) {
  350. throw new ConfigException(L.l("'{0}' is an illegal cron range", year));
  351. } else {
  352. filterValue.setIncrement(increment);
  353. }
  354. if (filterValue.getStartYear() == filterValue.getEndYear()) {
  355. // This is in the form of N/M, where N is the interval start.
  356. filterValue.setEndYear(Integer.MAX_VALUE);
  357. }
  358. }
  359. yearsFilter.addFilterValue(filterValue);
  360. if (i < year.length()) {
  361. if ((i < (year.length() - 1)) && (character == ',')) {
  362. i++;
  363. } else {
  364. throw new ConfigException(L.l("'{0}' is an illegal cron range", year));
  365. }
  366. }
  367. }
  368. return yearsFilter;
  369. }
  370. /**
  371. * Gets the next time this trigger should be fired.
  372. *
  373. * @param now
  374. * The current time.
  375. * @return The next time this trigger should be fired.
  376. */
  377. @Override
  378. public long nextTime(long now)
  379. {
  380. // Jump to start time.
  381. if ((_start != -1) && (now < _start)) {
  382. now = _start;
  383. }
  384. QDate calendar = allocateCalendar();
  385. // Round up to seconds.
  386. long time = now + 1000 - now % 1000;
  387. calendar.setGMTTime(time);
  388. QDate nextTime = getNextTime(calendar);
  389. if (nextTime != null) {
  390. time = nextTime.getGMTTime();
  391. time -= _timezone.getRawOffset(); // Adjust for time zone specification.
  392. } else {
  393. time = Long.MAX_VALUE; // This trigger is inactive.
  394. }
  395. // Check for end date
  396. if ((_end != -1) && (time > _end)) {
  397. time = Long.MAX_VALUE; // This trigger is inactive.
  398. }
  399. freeCalendar(calendar);
  400. if (now < time)
  401. return time;
  402. else
  403. return nextTime(now + 3600000L); // Daylight savings time.
  404. }
  405. private QDate getNextTime(QDate currentTime)
  406. {
  407. int year = _yearsFilter.getNextMatch(currentTime.getYear());
  408. if (year == -1) {
  409. return null;
  410. } else {
  411. if (year > currentTime.getYear()) {
  412. currentTime.setSecond(0);
  413. currentTime.setMinute(0);
  414. currentTime.setHour(0);
  415. currentTime.setDayOfMonth(1);
  416. currentTime.setMonth(0); // The QDate implementation uses 0 indexed
  417. // months, but cron does not.
  418. currentTime.setYear(year);
  419. }
  420. QDate nextTime = getNextTimeInYear(currentTime);
  421. int count = 0;
  422. // Don't look more than approximately five years ahead.
  423. while ((count < 5) && (nextTime == null)) {
  424. count++;
  425. year++;
  426. year = _yearsFilter.getNextMatch(year);
  427. if (year == -1) {
  428. return null;
  429. } else {
  430. currentTime.setSecond(0);
  431. currentTime.setMinute(0);
  432. currentTime.setHour(0);
  433. currentTime.setDayOfMonth(1);
  434. currentTime.setMonth(0); // The QDate implementation uses 0 indexed
  435. // months, but cron does not.
  436. currentTime.setYear(year);
  437. nextTime = getNextTimeInYear(currentTime);
  438. }
  439. }
  440. return nextTime;
  441. }
  442. }
  443. private QDate getNextTimeInYear(QDate currentTime)
  444. {
  445. int month = getNextMatch(_months, (currentTime.getMonth() + 1));
  446. if (month == -1) {
  447. return null;
  448. } else {
  449. if (month > (currentTime.getMonth() + 1)) {
  450. currentTime.setSecond(0);
  451. currentTime.setMinute(0);
  452. currentTime.setHour(0);
  453. currentTime.setDayOfMonth(1);
  454. currentTime.setMonth(month - 1);
  455. }
  456. QDate nextTime = getNextTimeInMonth(currentTime);
  457. while ((month < _months.length) && (nextTime == null)) {
  458. month++;
  459. month = getNextMatch(_months, month);
  460. if (month == -1) {
  461. return null;
  462. } else {
  463. currentTime.setSecond(0);
  464. currentTime.setMinute(0);
  465. currentTime.setHour(0);
  466. currentTime.setDayOfMonth(1);
  467. currentTime.setMonth(month - 1);
  468. nextTime = getNextTimeInMonth(currentTime);
  469. }
  470. }
  471. return nextTime;
  472. }
  473. }
  474. private QDate getNextTimeInMonth(QDate currentTime)
  475. {
  476. // If the days filter is relative to particular months, the days map should
  477. // be re-calculated.
  478. if (_isDaysFilterRelative) {
  479. calculateDays(currentTime);
  480. }
  481. // Note, QDate uses a 1 indexed weekday, while cron uses a 0 indexed
  482. // weekday.
  483. int day = getNextDayMatch(currentTime.getDayOfMonth(), (currentTime
  484. .getDayOfWeek() - 1), currentTime.getDayOfMonth(), currentTime
  485. .getDaysInMonth());
  486. if (day == -1) {
  487. return null;
  488. } else {
  489. if (day > currentTime.getDayOfMonth()) {
  490. currentTime.setSecond(0);
  491. currentTime.setMinute(0);
  492. currentTime.setHour(0);
  493. currentTime.setDayOfMonth(day);
  494. }
  495. QDate nextTime = getNextTimeInDay(currentTime);
  496. if (nextTime == null) {
  497. day++;
  498. // Note, QDate uses a 1 indexed weekday, while cron uses a 0 indexed
  499. // weekday.
  500. day = getNextDayMatch(currentTime.getDayOfMonth(), (currentTime
  501. .getDayOfWeek() - 1), day, currentTime.getDaysInMonth());
  502. if (day == -1) {
  503. return null;
  504. } else {
  505. currentTime.setSecond(0);
  506. currentTime.setMinute(0);
  507. currentTime.setHour(0);
  508. currentTime.setDayOfMonth(day);
  509. return getNextTimeInDay(currentTime);
  510. }
  511. }
  512. return nextTime;
  513. }
  514. }
  515. private void calculateDays(QDate currentTime)
  516. {
  517. _days = new boolean[currentTime.getDaysInMonth() + 1];
  518. int i = 0;
  519. while (i < _daysFilter.length()) {
  520. char character = _daysFilter.charAt(i);
  521. int min = 0;
  522. int max = min;
  523. int increment = 1;
  524. if (character == '*') {
  525. min = 1;
  526. max = currentTime.getDaysInMonth();
  527. i++;
  528. } else if ('0' <= character && character <= '9') {
  529. for (; i < _daysFilter.length()
  530. && '0' <= (character = _daysFilter.charAt(i)) && character <= '9'; i++) {
  531. min = 10 * min + character - '0';
  532. }
  533. if (i < _daysFilter.length() && character == '-') {
  534. for (i++; i < _daysFilter.length()
  535. && '0' <= (character = _daysFilter.charAt(i)) && character <= '9'; i++) {
  536. max = 10 * max + character - '0';
  537. }
  538. } else if ((i < _daysFilter.length()) && (character == ' ')) {
  539. // This is the Nth weekday case.
  540. int dayOfWeek = 0;
  541. for (i++; i < _daysFilter.length()
  542. && '0' <= (character = _daysFilter.charAt(i)) && character <= '9'; i++) {
  543. dayOfWeek = 10 * dayOfWeek + character - '0';
  544. }
  545. int n = min;
  546. min = 1;
  547. int monthStartDayofWeek = ((currentTime.getDayOfWeek() - 1)
  548. - ((currentTime.getDayOfMonth() - min) % 7) + 7) % 7;
  549. min = min + ((dayOfWeek - monthStartDayofWeek + 7) % 7);
  550. min = min + ((n - 1) * 7);
  551. max = min;
  552. } else {
  553. max = min;
  554. }
  555. } else if (character == '-') { // This is a -N days from end of month
  556. // case.
  557. for (i++; i < _daysFilter.length()
  558. && '0' <= (character = _daysFilter.charAt(i)) && character <= '9'; i++) {
  559. min = 10 * min + character - '0';
  560. }
  561. min = currentTime.getDaysInMonth() - min;
  562. // The case of the last (-0) weekday case.
  563. if ((i < _daysFilter.length())
  564. && ((character = _daysFilter.charAt(i)) == ' ')) {
  565. int dayOfWeek = 0;
  566. for (i++; i < _daysFilter.length()
  567. && '0' <= (character = _daysFilter.charAt(i)) && character <= '9'; i++) {
  568. dayOfWeek = 10 * dayOfWeek + character - '0';
  569. }
  570. min = 1;
  571. int monthStartDayofWeek = ((currentTime.getDayOfWeek() - 1)
  572. - ((currentTime.getDayOfMonth() - min) % 7) + 7) % 7;
  573. min = min + ((dayOfWeek - monthStartDayofWeek + 7) % 7);
  574. // This is an integer division.
  575. min = min + (((currentTime.getDaysInMonth() - min) / 7) * 7);
  576. }
  577. max = min;
  578. }
  579. if ((i < _daysFilter.length())
  580. && ((character = _daysFilter.charAt(i)) == '/')) {
  581. for (i++; i < _daysFilter.length()
  582. && '0' <= (character = _daysFilter.charAt(i)) && character <= '9'; i++) {
  583. increment = 10 * increment + character - '0';
  584. }
  585. if (min == max) { // This is in the form of N/M, where N is the interval
  586. // start and the end is the max value.
  587. max = currentTime.getDaysInMonth();
  588. }
  589. }
  590. for (int day = min; ((day > 0) && (day <= max) && (day <= currentTime
  591. .getDaysInMonth())); day += increment) {
  592. _days[day] = true;
  593. }
  594. if (character == ',') {
  595. i++;
  596. }
  597. }
  598. }
  599. private int getNextDayMatch(int initialDayOfMonth, int initialDayOfWeek,
  600. int day, int daysInMonth)
  601. {
  602. while (day <= daysInMonth) {
  603. day = getNextMatch(_days, day, (daysInMonth + 1));
  604. if (day == -1) {
  605. return -1;
  606. }
  607. int dayOfWeek = ((initialDayOfWeek + ((day - initialDayOfMonth) % 7)) % 7);
  608. int nextDayOfWeek = getNextMatch(_daysOfWeek, dayOfWeek);
  609. if (nextDayOfWeek == dayOfWeek) {
  610. return day;
  611. } else if (nextDayOfWeek == -1) {
  612. day += (7 - dayOfWeek);
  613. } else {
  614. day += (nextDayOfWeek - dayOfWeek);
  615. }
  616. }
  617. return -1;
  618. }
  619. private QDate getNextTimeInDay(QDate currentTime)
  620. {
  621. int hour = getNextMatch(_hours, currentTime.getHour());
  622. if (hour == -1) {
  623. return null;
  624. } else {
  625. if (hour > currentTime.getHour()) {
  626. currentTime.setSecond(0);
  627. currentTime.setMinute(0);
  628. currentTime.setHour(hour);
  629. }
  630. QDate nextTime = getNextTimeInHour(currentTime);
  631. if (nextTime == null) {
  632. hour++;
  633. hour = getNextMatch(_hours, hour);
  634. if (hour == -1) {
  635. return null;
  636. } else {
  637. currentTime.setSecond(0);
  638. currentTime.setMinute(0);
  639. currentTime.setHour(hour);
  640. return getNextTimeInHour(currentTime);
  641. }
  642. }
  643. return nextTime;
  644. }
  645. }
  646. private QDate getNextTimeInHour(QDate currentTime)
  647. {
  648. int minute = getNextMatch(_minutes, currentTime.getMinute());
  649. if (minute == -1) {
  650. return null;
  651. } else {
  652. if (minute > currentTime.getMinute()) {
  653. currentTime.setSecond(0);
  654. currentTime.setMinute(minute);
  655. }
  656. QDate nextTime = getNextTimeInMinute(currentTime);
  657. if (nextTime == null) {
  658. minute++;
  659. minute = getNextMatch(_minutes, minute);
  660. if (minute == -1) {
  661. return null;
  662. } else {
  663. currentTime.setSecond(0);
  664. currentTime.setMinute(minute);
  665. return getNextTimeInMinute(currentTime);
  666. }
  667. }
  668. return nextTime;
  669. }
  670. }
  671. private QDate getNextTimeInMinute(QDate currentTime)
  672. {
  673. int second = getNextMatch(_seconds, currentTime.getSecond());
  674. if (second == -1) {
  675. return null;
  676. } else {
  677. currentTime.setSecond(second);
  678. return currentTime;
  679. }
  680. }
  681. private int getNextMatch(boolean [] range, int start)
  682. {
  683. return getNextMatch(range, start, range.length);
  684. }
  685. private int getNextMatch(boolean [] range, int start, int end)
  686. {
  687. for (int match = start; match < end; match++) {
  688. if (range[match]) {
  689. return match;
  690. }
  691. }
  692. return -1;
  693. }
  694. private QDate allocateCalendar()
  695. {
  696. QDate calendar = _localCalendar.getAndSet(null);
  697. if (calendar == null) {
  698. calendar = QDate.createLocal();
  699. }
  700. return calendar;
  701. }
  702. private void freeCalendar(QDate cal)
  703. {
  704. _localCalendar.set(cal);
  705. }
  706. private class YearsFilter {
  707. private List<YearsFilterValue> _filterValues = new LinkedList<YearsFilterValue>();
  708. private boolean _anyYear = false;
  709. private int _endYear = 0;
  710. private void addFilterValue(YearsFilterValue filterValue)
  711. {
  712. if (filterValue.isAnyYear()) {
  713. _anyYear = true;
  714. } else if (filterValue.getEndYear() > _endYear) {
  715. _endYear = filterValue.getEndYear();
  716. }
  717. _filterValues.add(filterValue);
  718. }
  719. private int getNextMatch(int year)
  720. {
  721. if (_anyYear) {
  722. return year;
  723. }
  724. while (year <= _endYear) {
  725. for (YearsFilterValue filterValue : _filterValues) {
  726. if ((year >= filterValue.getStartYear())
  727. && (year <= filterValue.getEndYear())
  728. && ((year % filterValue.getIncrement()) == 0)) {
  729. return year;
  730. }
  731. }
  732. year++;
  733. }
  734. return -1;
  735. }
  736. }
  737. private class YearsFilterValue {
  738. private boolean _anyYear = false;
  739. private int _startYear = 0;
  740. private int _endYear = 0;
  741. private int _increment = 1;
  742. public boolean isAnyYear()
  743. {
  744. return _anyYear;
  745. }
  746. public void setAnyYear(boolean anyYear)
  747. {
  748. _anyYear = anyYear;
  749. }
  750. public int getStartYear()
  751. {
  752. return _startYear;
  753. }
  754. public void setStartYear(int startYear)
  755. {
  756. _startYear = startYear;
  757. }
  758. public int getEndYear()
  759. {
  760. return _endYear;
  761. }
  762. public void setEndYear(int endYear)
  763. {
  764. _endYear = endYear;
  765. }
  766. public void setIncrement(int increment)
  767. {
  768. _increment = increment;
  769. }
  770. public int getIncrement()
  771. {
  772. return _increment;
  773. }
  774. }
  775. }