com.hfg.util.scheduler.CronSchedule Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of com_hfg Show documentation
Show all versions of com_hfg Show documentation
com.hfg xml, html, svg, and bioinformatics utility library
package com.hfg.util.scheduler;
import com.hfg.util.StringUtil;
import com.hfg.util.collection.CollectionUtil;
import com.hfg.util.collection.OrderedSet;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
//------------------------------------------------------------------------------
/**
Scheduler that uses the same specification syntax as the Unix cron utility.
See Wikipedia.
The text-based specification has five fields:
* * * * * command to execute
| | | | |
| | | | |
| | | | +------ day of week (0 - 6) (0 to 6 are Sunday to Saturday, or use names; 7 is Sunday, the same as 0)
| | | +----------- month (1 - 12)
| | +---------------- day of month (1 - 31)
| +--------------------- hour (0 - 23)
+-------------------------- min (0 - 59)
@author J. Alex Taylor, hairyfatguy.com
*/
//------------------------------------------------------------------------------
// com.hfg Library
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
// J. Alex Taylor, President, Founder, CEO, COO, CFO, OOPS hairyfatguy.com
// [email protected]
//------------------------------------------------------------------------------
// TODO: More to do to fully support cron syntax.
public class CronSchedule implements Schedule
{
private String mMinuteSpec;
private String mHourSpec;
private String mDayOfMonthSpec;
private String mMonthSpec;
private String mDayOfWeekSpec;
private OrderedSet mMinuteValues;
private OrderedSet mHourValues;
private OrderedSet mDayOfMonthValues;
private OrderedSet mMonthValues;
private OrderedSet mDayOfWeekValues;
//###########################################################################
// CONSTRUCTORS
//###########################################################################
//---------------------------------------------------------------------------
public CronSchedule(String inCronString)
{
parse(inCronString);
}
//###########################################################################
// PUBLIC METHODS
//###########################################################################
//---------------------------------------------------------------------------
public Date next()
{
return nextAfter(new Date());
}
//---------------------------------------------------------------------------
public Date nextAfter(Date inReferenceDate)
{
Calendar calendar = new GregorianCalendar();
calendar.setTime(inReferenceDate);
// Cron doesn't specify times with a precision below minutes
calendar.set(Calendar.MILLISECOND, 0);
calendar.set(Calendar.SECOND, 0);
calendar.add(Calendar.MINUTE, 1);
// Every minute value is valid for execution unless values have been specified
if (CollectionUtil.hasValues(mMinuteValues))
{
while (! mMinuteValues.contains(calendar.get(Calendar.MINUTE)))
{
calendar.add(Calendar.MINUTE, 1);
}
}
// Every hour value is valid for execution unless values have been specified
if (CollectionUtil.hasValues(mHourValues))
{
while (! mHourValues.contains(calendar.get(Calendar.HOUR_OF_DAY)))
{
calendar.add(Calendar.HOUR_OF_DAY, 1);
if (null == mMinuteValues)
{
// Set the minute value to zero
zeroCalendarField(calendar, Calendar.MINUTE);
}
}
}
if (CollectionUtil.hasValues(mDayOfMonthValues)
|| CollectionUtil.hasValues(mDayOfWeekValues))
{
boolean modified = false;
while ((mDayOfMonthValues != null
&& ! mDayOfMonthValues.contains(calendar.get(Calendar.DAY_OF_MONTH)))
|| (mDayOfWeekValues != null
&& ! mDayOfWeekValues.contains(calendar.get(Calendar.DAY_OF_WEEK))))
{
calendar.add(Calendar.DAY_OF_MONTH, 1);
modified = true;
}
if (modified)
{
if (null == mHourValues)
{
// Set the hour value to zero
zeroCalendarField(calendar, Calendar.HOUR_OF_DAY);
}
if (null == mMinuteValues)
{
// Set the minute value to zero
zeroCalendarField(calendar, Calendar.MINUTE);
}
}
}
if (CollectionUtil.hasValues(mMonthValues))
{
boolean modified = false;
while ((mMonthValues != null
&& ! mMonthValues.contains(calendar.get(Calendar.MONTH) + 1))
|| (mDayOfMonthValues != null
&& ! mDayOfMonthValues.contains(calendar.get(Calendar.DAY_OF_MONTH)))
|| (mDayOfWeekValues != null
&& ! mDayOfWeekValues.contains(calendar.get(Calendar.DAY_OF_WEEK))))
{
modified = true;
// Are we already at the end of the month?
if (calendar.get(Calendar.DAY_OF_MONTH) == calendar.getActualMaximum(Calendar.DAY_OF_MONTH))
{
calendar.set(Calendar.DAY_OF_MONTH, 1);
calendar.add(Calendar.MONTH, 1);
while (! mMonthValues.contains(calendar.get(Calendar.MONTH) + 1))
{
calendar.add(Calendar.MONTH, 1);
}
}
else
{
calendar.add(Calendar.DAY_OF_MONTH, 1);
}
}
if (modified)
{
if (null == mHourValues)
{
// Set the hour value to zero
zeroCalendarField(calendar, Calendar.HOUR_OF_DAY);
}
if (null == mMinuteValues)
{
// Set the minute value to zero
zeroCalendarField(calendar, Calendar.MINUTE);
}
}
}
return calendar.getTime();
}
//---------------------------------------------------------------------------
public CronSchedule setMinuteSpec(String inMinuteSpec)
{
mMinuteValues = null;
if (StringUtil.isSet(inMinuteSpec))
{
String spec = inMinuteSpec.trim();
if (! spec.equals("*"))
{
List values = new ArrayList(60);
String[] pieces = spec.split(",");
for (String piece : pieces)
{
int startValue;
int endValue;
int dashIdx = piece.indexOf("-");
if (dashIdx > 0)
{
startValue = Integer.parseInt(piece.substring(0, dashIdx));
endValue = Integer.parseInt(piece.substring(dashIdx + 1));
}
else
{
startValue = endValue = Integer.parseInt(piece);
}
for (int i = startValue; i <= endValue; i++)
{
if (i < 0
|| i > 59)
{
throw new RuntimeException("Invalid minute value: " + StringUtil.singleQuote(i) + "! Valid value range: 0-59.");
}
values.add(i);
}
}
// Make sure the values are sorted low to high
Collections.sort(values);
mMinuteValues = new OrderedSet(values);
}
}
return this;
}
//---------------------------------------------------------------------------
public CronSchedule setHourSpec(String inHourSpec)
{
mHourValues = null;
if (StringUtil.isSet(inHourSpec))
{
String spec = inHourSpec.trim();
if (! spec.equals("*"))
{
List values = new ArrayList(24);
String[] pieces = spec.split(",");
for (String piece : pieces)
{
int startValue;
int endValue;
int dashIdx = piece.indexOf("-");
if (dashIdx > 0)
{
startValue = Integer.parseInt(piece.substring(0, dashIdx));
endValue = Integer.parseInt(piece.substring(dashIdx + 1));
}
else
{
startValue = endValue = Integer.parseInt(piece);
}
for (int i = startValue; i <= endValue; i++)
{
if (i < 0
|| i > 23)
{
throw new RuntimeException("Invalid hour value: " + StringUtil.singleQuote(i) + "! Valid value range: 0-23.");
}
values.add(i);
}
}
// Make sure the values are sorted low to high
Collections.sort(values);
mHourValues = new OrderedSet(values);
}
}
return this;
}
//---------------------------------------------------------------------------
public CronSchedule setDayOfMonthSpec(String inDayOfMonthSpec)
{
mDayOfMonthValues = null;
if (StringUtil.isSet(inDayOfMonthSpec))
{
String spec = inDayOfMonthSpec.trim();
if (! spec.equals("*"))
{
List values = new ArrayList(32);
String[] pieces = spec.split(",");
for (String piece : pieces)
{
int startValue;
int endValue;
int dashIdx = piece.indexOf("-");
if (dashIdx > 0)
{
startValue = Integer.parseInt(piece.substring(0, dashIdx));
endValue = Integer.parseInt(piece.substring(dashIdx + 1));
}
else
{
startValue = endValue = Integer.parseInt(piece);
}
for (int i = startValue; i <= endValue; i++)
{
if (i < 1
|| i > 31)
{
throw new RuntimeException("Invalid day of month value: " + StringUtil.singleQuote(i) + "! Valid value range: 1-31.");
}
values.add(i);
}
}
// Make sure the values are sorted low to high
Collections.sort(values);
mDayOfMonthValues = new OrderedSet(values);
}
}
return this;
}
//---------------------------------------------------------------------------
public CronSchedule setMonthSpec(String inMonthSpec)
{
mMonthValues = null;
if (StringUtil.isSet(inMonthSpec))
{
String spec = inMonthSpec.trim();
if (! spec.equals("*"))
{
List values = new ArrayList(13);
String[] pieces = spec.split(",");
for (String piece : pieces)
{
int startValue;
int endValue;
int dashIdx = piece.indexOf("-");
if (dashIdx > 0)
{
startValue = Integer.parseInt(piece.substring(0, dashIdx));
endValue = Integer.parseInt(piece.substring(dashIdx + 1));
}
else
{
startValue = endValue = Integer.parseInt(piece);
}
for (int i = startValue; i <= endValue; i++)
{
if (i < 1
|| i > 12)
{
throw new RuntimeException("Invalid day of month value: " + StringUtil.singleQuote(i) + "! Valid value range: 1-12.");
}
values.add(i);
}
}
// Make sure the values are sorted low to high
Collections.sort(values);
mMonthValues = new OrderedSet(values);
}
}
return this;
}
//---------------------------------------------------------------------------
public CronSchedule setDayOfWeekSpec(String inDayOfWeekSpec)
{
mDayOfWeekValues = null;
if (StringUtil.isSet(inDayOfWeekSpec))
{
String spec = inDayOfWeekSpec.trim();
if (! spec.equals("*"))
{
List values = new ArrayList(13);
String[] pieces = spec.split(",");
for (String piece : pieces)
{
int startValue;
int endValue;
int dashIdx = piece.indexOf("-");
if (dashIdx > 0)
{
startValue = Integer.parseInt(piece.substring(0, dashIdx));
endValue = Integer.parseInt(piece.substring(dashIdx + 1));
}
else
{
startValue = endValue = Integer.parseInt(piece);
}
for (int i = startValue; i <= endValue; i++)
{
if (i < 0
|| i > 6)
{
throw new RuntimeException("Invalid day of month value: " + StringUtil.singleQuote(i) + "! Valid value range: 0-6.");
}
values.add(i + 1);
}
}
// Make sure the values are sorted low to high
Collections.sort(values);
mDayOfWeekValues = new OrderedSet(values);
}
}
return this;
}
//###########################################################################
// PRIVATE METHODS
//###########################################################################
//---------------------------------------------------------------------------
private void parse(String inCronString)
{
String[] pieces = inCronString.trim().split("\\s+");
if (pieces.length != 5)
{
throw new RuntimeException("The cron string " + StringUtil.singleQuote(inCronString) + " did not have the expected 5 cron fields!");
}
mMinuteSpec = pieces[0];
mHourSpec = pieces[1];
mDayOfMonthSpec = pieces[2];
mMonthSpec = pieces[3];
mDayOfWeekSpec = pieces[4];
setMinuteSpec(pieces[0]);
setHourSpec(pieces[1]);
setDayOfMonthSpec(pieces[2]);
setMonthSpec(pieces[3]);
setDayOfWeekSpec(pieces[4]);
}
//---------------------------------------------------------------------------
private void zeroCalendarField(Calendar inCalendar, int inField)
{
inCalendar.add(inField, - inCalendar.get(inField));
}
}