Timothy A. Bish 2011-03-14 19:44:59 +00:00
parent f0429be6ee
commit 3d61acf2b2
3 changed files with 344 additions and 117 deletions

View File

@ -24,6 +24,7 @@ import java.util.StringTokenizer;
import javax.jms.MessageFormatException;
public class CronParser {
private static final int NUMBER_TOKENS = 5;
private static final int MINUTES = 0;
private static final int HOURS = 1;
@ -32,59 +33,204 @@ public class CronParser {
private static final int DAY_OF_WEEK = 4;
public static long getNextScheduledTime(final String cronEntry, long currentTime) throws MessageFormatException {
long result = 0;
if (cronEntry != null && cronEntry.length() > 0) {
List<String> list = tokenize(cronEntry);
List<CronEntry> entries = buildCronEntries(list);
Calendar working = Calendar.getInstance();
working.setTimeInMillis(currentTime);
CronEntry minutes = entries.get(MINUTES);
CronEntry hours = entries.get(HOURS);
CronEntry dayOfMonth = entries.get(DAY_OF_MONTH);
CronEntry month = entries.get(MONTH);
CronEntry dayOfWeek = entries.get(DAY_OF_WEEK);
int currentMinutes = working.get(Calendar.MINUTE);
if (!isCurrent(minutes, currentMinutes)) {
int nextMinutes = getNext(minutes, currentMinutes);
working.add(Calendar.MINUTE, nextMinutes);
result = working.getTimeInMillis();
}
int currentHours = working.get(Calendar.HOUR_OF_DAY);
if (!isCurrent(hours, currentHours)) {
int nextHour = getNext(hours, currentHours);
working.add(Calendar.HOUR_OF_DAY, nextHour);
result = working.getTimeInMillis();
}
int currentDayOfWeek = working.get(Calendar.DAY_OF_WEEK) - 1;
long result = 0;
if (cronEntry == null || cronEntry.length() == 0) {
return result;
}
// Handle the once per minute case "* * * * *"
// starting the next event at the top of the minute.
if (cronEntry.startsWith("* * * * *")) {
result = currentTime + 60 * 1000;
result = result / 1000 * 1000;
return result;
}
List<String> list = tokenize(cronEntry);
List<CronEntry> entries = buildCronEntries(list);
Calendar working = Calendar.getInstance();
working.setTimeInMillis(currentTime);
working.set(Calendar.SECOND, 0);
CronEntry minutes = entries.get(MINUTES);
CronEntry hours = entries.get(HOURS);
CronEntry dayOfMonth = entries.get(DAY_OF_MONTH);
CronEntry month = entries.get(MONTH);
CronEntry dayOfWeek = entries.get(DAY_OF_WEEK);
// Start at the top of the next minute, cron is only guaranteed to be
// run on the minute.
int timeToNextMinute = 60 - working.get(Calendar.SECOND);
working.add(Calendar.SECOND, timeToNextMinute);
// If its already to late in the day this will roll us over to tomorrow
// so we'll need to check again when done updating month and day.
int currentMinutes = working.get(Calendar.MINUTE);
if (!isCurrent(minutes, currentMinutes)) {
int nextMinutes = getNext(minutes, currentMinutes);
working.add(Calendar.MINUTE, nextMinutes);
}
int currentHours = working.get(Calendar.HOUR_OF_DAY);
if (!isCurrent(hours, currentHours)) {
int nextHour = getNext(hours, currentHours);
working.add(Calendar.HOUR_OF_DAY, nextHour);
}
// We can roll into the next month here which might violate the cron setting
// rules so we check once then recheck again after applying the month settings.
doUpdateCurrentDay(working, dayOfMonth, dayOfWeek);
// Start by checking if we are in the right month, if not then calculations
// need to start from the beginning of the month to ensure that we don't end
// up on the wrong day. (Can happen when DAY_OF_WEEK is set and current time
// is ahead of the day of the week to execute on).
doUpdateCurrentMonth(working, month);
// Now Check day of week and day of month together since they can be specified
// together in one entry, if both "day of month" and "day of week" are restricted
// (not "*"), then either the "day of month" field (3) or the "day of week" field
// (5) must match the current day or the Calenday must be advanced.
doUpdateCurrentDay(working, dayOfMonth, dayOfWeek);
// Now we can chose the correct hour and minute of the day in question.
currentHours = working.get(Calendar.HOUR_OF_DAY);
if (!isCurrent(hours, currentHours)) {
int nextHour = getNext(hours, currentHours);
working.add(Calendar.HOUR_OF_DAY, nextHour);
}
currentMinutes = working.get(Calendar.MINUTE);
if (!isCurrent(minutes, currentMinutes)) {
int nextMinutes = getNext(minutes, currentMinutes);
working.add(Calendar.MINUTE, nextMinutes);
}
result = working.getTimeInMillis();
if (result <= currentTime) {
throw new ArithmeticException("Unable to compute next scheduled exection time.");
}
return result;
}
protected static long doUpdateCurrentMonth(Calendar working, CronEntry month) throws MessageFormatException {
int currentMonth = working.get(Calendar.MONTH) + 1;
if (!isCurrent(month, currentMonth)) {
int nextMonth = getNext(month, currentMonth);
working.add(Calendar.MONTH, nextMonth);
// Reset to start of month.
resetToStartOfDay(working, 1);
return working.getTimeInMillis();
}
return 0L;
}
protected static long doUpdateCurrentDay(Calendar working, CronEntry dayOfMonth, CronEntry dayOfWeek) throws MessageFormatException {
int currentDayOfWeek = working.get(Calendar.DAY_OF_WEEK) - 1;
int currentDayOfMonth = working.get(Calendar.DAY_OF_MONTH);
// Simplest case, both are unrestricted or both match today otherwise
// result must be the closer of the two if both are set, or the next
// match to the one that is.
if (!isCurrent(dayOfWeek, currentDayOfWeek) ||
!isCurrent(dayOfMonth, currentDayOfMonth) ) {
int nextWeekDay = Integer.MAX_VALUE;
int nextCalendarDay = Integer.MAX_VALUE;
if (!isCurrent(dayOfWeek, currentDayOfWeek)) {
int nextDay = getNext(dayOfWeek, currentDayOfWeek);
working.add(Calendar.DAY_OF_WEEK, nextDay);
result = working.getTimeInMillis();
nextWeekDay = getNext(dayOfWeek, currentDayOfWeek);
}
int currentMonth = working.get(Calendar.MONTH) + 1;
if (!isCurrent(month, currentMonth)) {
int nextMonth = getNext(month, currentMonth);
working.add(Calendar.MONTH, nextMonth);
result = working.getTimeInMillis();
}
int currentDayOfMonth = working.get(Calendar.DAY_OF_MONTH);
if (!isCurrent(dayOfMonth, currentDayOfMonth)) {
int nextDay = getNext(dayOfMonth, currentDayOfMonth);
working.add(Calendar.DAY_OF_MONTH, nextDay);
result = working.getTimeInMillis();
nextCalendarDay = getNext(dayOfMonth, currentDayOfMonth);
}
if (result == 0) {
// this can occur for "* * * * *"
result = currentTime + 60 * 1000;
result = result / 1000 * 1000;
if( nextWeekDay < nextCalendarDay ) {
working.add(Calendar.DAY_OF_WEEK, nextWeekDay);
} else {
working.add(Calendar.DAY_OF_MONTH, nextCalendarDay);
}
// Since the day changed, we restart the clock at the start of the day
// so that the next time will either be at 12am + value of hours and
// minutes pattern.
resetToStartOfDay(working, working.get(Calendar.DAY_OF_MONTH));
return working.getTimeInMillis();
}
return 0L;
}
public static void validate(final String cronEntry) throws MessageFormatException {
List<String> list = tokenize(cronEntry);
List<CronEntry> entries = buildCronEntries(list);
for (CronEntry e : entries) {
validate(e);
}
}
static void validate(final CronEntry entry) throws MessageFormatException {
List<Integer> list = entry.currentWhen;
if (list.isEmpty() || list.get(0).intValue() < entry.start || list.get(list.size() - 1).intValue() > entry.end) {
throw new MessageFormatException("Invalid token: " + entry);
}
}
static int getNext(final CronEntry entry, final int current) throws MessageFormatException {
int result = 0;
if (entry.currentWhen == null) {
entry.currentWhen = calculateValues(entry);
}
List<Integer> list = entry.currentWhen;
int next = -1;
for (Integer i : list) {
if (i.intValue() > current) {
next = i.intValue();
break;
}
}
if (next != -1) {
result = next - current;
} else {
int first = list.get(0).intValue();
result = entry.end + first - entry.start - current;
// Account for difference of one vs zero based indices.
if (entry.name.equals("DayOfWeek") || entry.name.equals("Month")) {
result++;
}
}
return result;
}
static boolean isCurrent(final CronEntry entry, final int current) throws MessageFormatException {
boolean result = entry.currentWhen.contains(new Integer(current));
return result;
}
protected static void resetToStartOfDay(Calendar target, int day) {
target.set(Calendar.DAY_OF_MONTH, day);
target.set(Calendar.HOUR_OF_DAY, 0);
target.set(Calendar.MINUTE, 0);
target.set(Calendar.SECOND, 0);
}
static List<String> tokenize(String cron) throws IllegalArgumentException {
StringTokenizer tokenize = new StringTokenizer(cron);
List<String> result = new ArrayList<String>();
@ -98,51 +244,7 @@ public class CronParser {
return result;
}
public static void validate(final String cronEntry) throws MessageFormatException {
List<String> list = tokenize(cronEntry);
List<CronEntry> entries = buildCronEntries(list);
for (CronEntry e : entries) {
validate(e);
}
}
static void validate(CronEntry entry) throws MessageFormatException {
List<Integer> list = calculateValues(entry);
if (list.isEmpty() || list.get(0).intValue() < entry.start || list.get(list.size() - 1).intValue() > entry.end) {
throw new MessageFormatException("Invalid token: " + entry);
}
}
static int getNext(final CronEntry entry, final int current) throws MessageFormatException {
int result = 0;
List<Integer> list = calculateValues(entry);
Collections.sort(list);
int next = -1;
for (Integer i : list) {
if (i.intValue() > current) {
next = i.intValue();
break;
}
}
if (next != -1) {
result = next - current;
} else {
int first = list.get(0).intValue();
result = entry.end + first - entry.start - current;
}
return result;
}
static boolean isCurrent(final CronEntry entry, final int current) throws MessageFormatException {
List<Integer> list = calculateValues(entry);
boolean result = list.contains(new Integer(current));
return result;
}
protected static List<Integer> calculateValues(CronEntry entry) {
protected static List<Integer> calculateValues(final CronEntry entry) {
List<Integer> result = new ArrayList<Integer>();
if (isAll(entry.token)) {
for (int i = entry.start; i <= entry.end; i++) {
@ -177,6 +279,7 @@ public class CronParser {
int value = Integer.parseInt(entry.token);
result.add(value);
}
Collections.sort(result);
return result;
}
@ -211,31 +314,44 @@ public class CronParser {
}
static List<CronEntry> buildCronEntries(List<String> tokens) {
List<CronEntry> result = new ArrayList<CronEntry>();
CronEntry minutes = new CronEntry("Minutes", tokens.get(MINUTES), 0, 60);
minutes.currentWhen = calculateValues(minutes);
result.add(minutes);
CronEntry hours = new CronEntry("Hours", tokens.get(HOURS), 0, 24);
hours.currentWhen = calculateValues(hours);
result.add(hours);
CronEntry dayOfMonth = new CronEntry("DayOfMonth", tokens.get(DAY_OF_MONTH), 1, 31);
dayOfMonth.currentWhen = calculateValues(dayOfMonth);
result.add(dayOfMonth);
CronEntry month = new CronEntry("Month", tokens.get(MONTH), 1, 12);
month.currentWhen = calculateValues(month);
result.add(month);
CronEntry dayOfWeek = new CronEntry("DayOfWeek", tokens.get(DAY_OF_WEEK), 0, 6);
dayOfWeek.currentWhen = calculateValues(dayOfWeek);
result.add(dayOfWeek);
return result;
}
static class CronEntry {
final String name;
final String token;
final int start;
final int end;
List<Integer> currentWhen;
CronEntry(String name, String token, int start, int end) {
this.name = name;
this.token = token;
this.start = start;
this.end = end;
}
@Override
public String toString() {
return this.name + ":" + token;

View File

@ -24,28 +24,31 @@ import java.util.Calendar;
import java.util.List;
import javax.jms.MessageFormatException;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class CronParserTest {
private static final Logger LOG = LoggerFactory.getLogger(CronParserTest.class);
@Test
public void testgetNextTimeDayOfWeek() throws MessageFormatException {
// using an absolute date so that result will be absolute - Monday 15 Nov 2010
Calendar current = Calendar.getInstance();
current.set(2010, Calendar.NOVEMBER, 15, 9, 15, 30);
System.out.println("start:" + current.getTime());
LOG.debug("start:" + current.getTime());
String test = "* * * * 5";
long next = CronParser.getNextScheduledTime(test, current.getTimeInMillis());
Calendar result = Calendar.getInstance();
result.setTimeInMillis(next);
System.out.println("next:" + result.getTime());
LOG.debug("next:" + result.getTime());
assertEquals(30,result.get(Calendar.SECOND));
assertEquals(15,result.get(Calendar.MINUTE));
assertEquals(9,result.get(Calendar.HOUR));
assertEquals(0,result.get(Calendar.SECOND));
assertEquals(0,result.get(Calendar.MINUTE));
assertEquals(0,result.get(Calendar.HOUR));
// expecting Friday 19th
assertEquals(19,result.get(Calendar.DAY_OF_MONTH));
assertEquals(Calendar.NOVEMBER,result.get(Calendar.MONTH));
@ -53,22 +56,21 @@ public class CronParserTest {
}
@Test
public void testgetNextTimeCase1() throws MessageFormatException {
public void testgetNextTimeDayOfWeekVariant() throws MessageFormatException {
// using an absolute date so that result will be absolute - Monday 15 Nov 2010
// using an absolute date so that result will be absolute - Monday 7 March 2011
Calendar current = Calendar.getInstance();
current.set(2011, Calendar.MARCH, 7, 9, 15, 30);
System.out.println("start:" + current.getTime());
LOG.debug("start:" + current.getTime());
String test = "50 20 * * 5";
long next = CronParser.getNextScheduledTime(test, current.getTimeInMillis());
Calendar result = Calendar.getInstance();
result.setTimeInMillis(next);
System.out.println("next:" + result.getTime());
LOG.debug("next:" + result.getTime());
assertEquals(30,result.get(Calendar.SECOND));
assertEquals(0,result.get(Calendar.SECOND));
assertEquals(50,result.get(Calendar.MINUTE));
assertEquals(20,result.get(Calendar.HOUR_OF_DAY));
// expecting Friday 11th
@ -76,28 +78,111 @@ public class CronParserTest {
assertEquals(Calendar.FRIDAY,result.get(Calendar.DAY_OF_WEEK));
assertEquals(Calendar.MARCH,result.get(Calendar.MONTH));
assertEquals(2011,result.get(Calendar.YEAR));
// Match to the day of week, but to late to run, should just a week forward.
current = Calendar.getInstance();
current.set(2011, Calendar.MARCH, 11, 22, 0, 30);
LOG.debug("update:" + current.getTime());
next = CronParser.getNextScheduledTime(test, current.getTimeInMillis());
result = Calendar.getInstance();
result.setTimeInMillis(next);
LOG.debug("next:" + result.getTime());
//assertEquals(0,result.get(Calendar.SECOND));
assertEquals(50,result.get(Calendar.MINUTE));
assertEquals(20,result.get(Calendar.HOUR_OF_DAY));
// expecting Friday 18th
assertEquals(18,result.get(Calendar.DAY_OF_MONTH));
assertEquals(Calendar.FRIDAY,result.get(Calendar.DAY_OF_WEEK));
assertEquals(Calendar.MARCH,result.get(Calendar.MONTH));
assertEquals(2011,result.get(Calendar.YEAR));
}
@Test
public void testgetNextTimeMonthVariant() throws MessageFormatException {
// using an absolute date so that result will be absolute - Monday 7 March 2011
Calendar current = Calendar.getInstance();
current.set(2011, Calendar.MARCH, 7, 9, 15, 30);
LOG.debug("start:" + current.getTime());
String test = "0 20 * 4,5 0";
long next = CronParser.getNextScheduledTime(test, current.getTimeInMillis());
Calendar result = Calendar.getInstance();
result.setTimeInMillis(next);
LOG.debug("next:" + result.getTime());
assertEquals(0,result.get(Calendar.SECOND));
assertEquals(0,result.get(Calendar.MINUTE));
assertEquals(20,result.get(Calendar.HOUR_OF_DAY));
// expecting Sunday 3rd of April
assertEquals(Calendar.APRIL,result.get(Calendar.MONTH));
assertEquals(3,result.get(Calendar.DAY_OF_MONTH));
assertEquals(Calendar.SUNDAY,result.get(Calendar.DAY_OF_WEEK));
assertEquals(2011,result.get(Calendar.YEAR));
current = Calendar.getInstance();
current.set(2011, Calendar.APRIL, 30, 22, 0, 30);
LOG.debug("update:" + current.getTime());
next = CronParser.getNextScheduledTime(test, current.getTimeInMillis());
result = Calendar.getInstance();
result.setTimeInMillis(next);
LOG.debug("next:" + result.getTime());
assertEquals(0,result.get(Calendar.SECOND));
assertEquals(0,result.get(Calendar.MINUTE));
assertEquals(20,result.get(Calendar.HOUR_OF_DAY));
// expecting Sunday 1st of May
assertEquals(1,result.get(Calendar.DAY_OF_MONTH));
assertEquals(Calendar.SUNDAY,result.get(Calendar.DAY_OF_WEEK));
assertEquals(Calendar.MAY,result.get(Calendar.MONTH));
assertEquals(2011,result.get(Calendar.YEAR));
// Move past last time and see if reschedule to next year works.
current = Calendar.getInstance();
current.set(2011, Calendar.MAY, 30, 22, 0, 30);
LOG.debug("update:" + current.getTime());
next = CronParser.getNextScheduledTime(test, current.getTimeInMillis());
result = Calendar.getInstance();
result.setTimeInMillis(next);
LOG.debug("next:" + result.getTime());
assertEquals(0,result.get(Calendar.SECOND));
assertEquals(0,result.get(Calendar.MINUTE));
assertEquals(20,result.get(Calendar.HOUR_OF_DAY));
// expecting Sunday 1st of April - 2012
assertEquals(1,result.get(Calendar.DAY_OF_MONTH));
assertEquals(Calendar.SUNDAY,result.get(Calendar.DAY_OF_WEEK));
assertEquals(Calendar.APRIL,result.get(Calendar.MONTH));
assertEquals(2012,result.get(Calendar.YEAR));
}
@Test
public void testgetNextTimeMonth() throws MessageFormatException {
// using an absolute date so that result will be absolute - Monday 15 Nov 2010
Calendar current = Calendar.getInstance();
current.set(2010, Calendar.NOVEMBER, 15, 9, 15, 30);
System.out.println("start:" + current.getTime());
LOG.debug("start:" + current.getTime());
String test = "* * * 12 *";
long next = CronParser.getNextScheduledTime(test, current.getTimeInMillis());
Calendar result = Calendar.getInstance();
result.setTimeInMillis(next);
System.out.println("next:" + result.getTime());
LOG.debug("next:" + result.getTime());
assertEquals(30,result.get(Calendar.SECOND));
assertEquals(15,result.get(Calendar.MINUTE));
assertEquals(9,result.get(Calendar.HOUR));
assertEquals(15,result.get(Calendar.DAY_OF_MONTH));
assertEquals(0,result.get(Calendar.SECOND));
assertEquals(0,result.get(Calendar.MINUTE));
assertEquals(0,result.get(Calendar.HOUR_OF_DAY));
assertEquals(1,result.get(Calendar.DAY_OF_MONTH));
assertEquals(Calendar.DECEMBER,result.get(Calendar.MONTH));
assertEquals(2010,result.get(Calendar.YEAR));
}
@ -105,22 +190,21 @@ public class CronParserTest {
@Test
public void testgetNextTimeDays() throws MessageFormatException {
// using an absolute date so that result will be absolute - Monday 15 Nov 2010
Calendar current = Calendar.getInstance();
current.set(2010, Calendar.NOVEMBER, 15, 9, 15, 30);
System.out.println("start:" + current.getTime());
LOG.debug("start:" + current.getTime());
String test = "* * 16 * *";
long next = CronParser.getNextScheduledTime(test, current.getTimeInMillis());
Calendar result = Calendar.getInstance();
result.setTimeInMillis(next);
System.out.println("next:" + result.getTime());
LOG.debug("next:" + result.getTime());
assertEquals(30,result.get(Calendar.SECOND));
assertEquals(15,result.get(Calendar.MINUTE));
assertEquals(9,result.get(Calendar.HOUR));
assertEquals(0,result.get(Calendar.SECOND));
assertEquals(0,result.get(Calendar.MINUTE));
assertEquals(0,result.get(Calendar.HOUR));
assertEquals(16,result.get(Calendar.DAY_OF_MONTH));
assertEquals(Calendar.NOVEMBER,result.get(Calendar.MONTH));
assertEquals(2010,result.get(Calendar.YEAR));
@ -132,11 +216,11 @@ public class CronParserTest {
long current = 20*60*1000;
Calendar calender = Calendar.getInstance();
calender.setTimeInMillis(current);
System.out.println("start:" + calender.getTime());
LOG.debug("start:" + calender.getTime());
long next = CronParser.getNextScheduledTime(test, current);
calender.setTimeInMillis(next);
System.out.println("next:" + calender.getTime());
LOG.debug("next:" + calender.getTime());
long result = next - current;
assertEquals(60*10*1000,result);
}
@ -152,7 +236,7 @@ public class CronParserTest {
calender.setTimeInMillis(next);
long result = next - current;
long expected = 60*1000*60*8;
long expected = 60*1000*60*8 + 60 * 1000;
assertEquals(expected,result);
}
@ -262,7 +346,6 @@ public class CronParserTest {
assertEquals(list.get(2), "2");
assertEquals(list.get(3), "3");
assertEquals(list.get(4), "4");
}
public void testGetNextScheduledTime() {

View File

@ -19,6 +19,7 @@ package org.apache.activemq.broker.scheduler;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.util.Calendar;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@ -52,6 +53,33 @@ public class JobSchedulerTest {
assertEquals(0,latch.getCount());
}
@Test
public void testAddCronAndByteSequence() throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
scheduler.addListener(new JobListener() {
public void scheduledJob(String id, ByteSequence job) {
latch.countDown();
}
});
Calendar current = Calendar.getInstance();
int minutes = current.get(Calendar.MINUTE) + 1;
int hour = current.get(Calendar.HOUR_OF_DAY);
int day = current.get(Calendar.DAY_OF_WEEK) - 1;
String cronTab = String.format("%d %d * * %d", minutes, hour, day);
String str = new String("test1");
scheduler.schedule("id:1", new ByteSequence(str.getBytes()), cronTab, 0, 0, 0);
assertTrue(latch.await(60, TimeUnit.SECONDS));
assertEquals(0, latch.getCount());
}
@Test
public void testAddLongLongIntStringByteSequence() throws Exception {
final int COUNT = 10;