package org.oddjob.schedules;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
import org.apache.log4j.Logger;
import org.oddjob.schedules.schedules.ParentChildSchedule;
/**
* A base class for a Schedule which has a from and a to
* date.
*
* @author Rob Gordon
*/
abstract public class ConstrainedSchedule extends AbstractSchedule {
private static final long serialVersionUID = 20050226;
private static Logger logger = Logger.getLogger(ConstrainedSchedule.class);
/**
* Provide a Calendar for the start of the constraint.
*
* @param referenceDate The date/time now.
* @param timeZone The time zone.
*
* @return A calendar for the from time.
*/
abstract protected Calendar fromCalendar(Date referenceDate, TimeZone timeZone);
/**
* Provide a Calendar for the end of the constraint.
*
* @param referenceDate The date/time now.
* @param timeZone The time zone.
*
* @return A calendar for the end time.
*/
abstract protected Calendar toCalendar(Date referenceDate, TimeZone timeZone);
/**
* Sub classes must provide a unit which is what must be
* added to move the schedule on. I.e. the equivalent of a day, week,
* month etc.
*
* @return
*/
protected abstract CalendarUnit intervalBetween();
/**
* Calculate the next interval, without children.
*
* @param context
* @return
*/
protected final Interval nextInterval(ScheduleContext context) {
Calendar fromCal = fromCalendar(context.getDate(), context.getTimeZone());
Calendar toCal = toCalendar(context.getDate(), context.getTimeZone());
Calendar nowCal = Calendar.getInstance(context.getTimeZone());
nowCal.setTime(context.getDate());
if (toCal.before(fromCal)) {
if (nowCal.before(toCal)) {
fromCal = shiftFromCalendar(fromCal, -1);
}
}
else {
if (nowCal.compareTo(toCal) >= 0) {
fromCal = shiftFromCalendar(fromCal, 1);
}
}
if (nowCal.compareTo(toCal) >= 0) {
toCal = shiftToCalendar(toCal, 1);
}
return new SimpleInterval(fromCal.getTime(), toCal.getTime());
}
/**
* Calculate the last interval.
*
* @param context
* @return
*/
protected final Interval lastInterval(ScheduleContext context) {
Calendar fromCal = fromCalendar(context.getDate(), context.getTimeZone());
Calendar toCal = toCalendar(context.getDate(), context.getTimeZone());
Calendar nowCal = Calendar.getInstance(context.getTimeZone());
nowCal.setTime(context.getDate());
if (toCal.before(fromCal)) {
if (nowCal.before(toCal)) {
fromCal = shiftFromCalendar(fromCal, -2);
}
else {
fromCal = shiftFromCalendar(fromCal, -1);
}
}
else {
if (nowCal.before(toCal)) {
fromCal = shiftFromCalendar(fromCal, -1);
}
}
if (nowCal.before(toCal)) {
toCal = shiftToCalendar(toCal, -1);
}
return new SimpleInterval(fromCal.getTime(), toCal.getTime());
}
/**
* Shift the from Calendar by an interval. The subclass fromCalendar
* is used to re-adjust the shifted calendar. This is needed
* in at least these situations:
* <ul>
* <li>A yearly schedule for the month of February that returned
* a toDate in the year before a leap year of the 28th would shift
* to the 28th as the last day of February in the leap year without
* re-adjustment.</li>
* <li>A last day of the month schedule that returned 28th in
* February would shift to the 28th of March without re-adjustment.
* </li>
* <ul>
*
* @param calendar
* @param interval.
* @return
*/
protected Calendar shiftFromCalendar(Calendar calendar, int intervals) {
calendar = shiftCalendar(calendar, intervals);
return fromCalendar(calendar.getTime(), calendar.getTimeZone());
}
/**
* Shift the to Calendar by an interval. The subclass toCalendar
* is used to re-adjust the shifted calendar for the reasons given
* in {@link #shiftFromCalendar(Calendar, int)}
*
* @param calendar
* @param intervals
* @return
*/
protected Calendar shiftToCalendar(Calendar calendar, int intervals) {
calendar = shiftCalendar(calendar, intervals);
calendar.add(Calendar.MILLISECOND, -1);
return toCalendar(calendar.getTime(), calendar.getTimeZone());
}
private Calendar shiftCalendar(Calendar calendar, int intervals) {
CalendarUnit unit = intervalBetween();
calendar.add(unit.getField(), intervals * unit.getValue());
return calendar;
}
/*
* (non-Javadoc)
* @see org.treesched.Schedule#nextDue(java.util.Date)
*/
public ScheduleResult nextDue(ScheduleContext context) {
Date now = context.getDate();
if (now == null) {
return null;
}
ParentChildSchedule parentChild =
new ParentChildSchedule(new Schedule() {
public ScheduleResult nextDue(ScheduleContext context) {
return new SimpleScheduleResult(nextInterval(context));
}
}, getRefinement());
ScheduleResult nextResult = parentChild.nextDue(context);
if (nextResult == null) {
return null;
}
// If we are before the next interval we need to check we
// aren't still in the last (because a child interval
// could extend beyond the limit of it's parent).
if (now.before(nextResult.getFromDate())) {
parentChild =
new ParentChildSchedule(new Schedule() {
public ScheduleResult nextDue(ScheduleContext context) {
return new SimpleScheduleResult(lastInterval(context));
}
}, getRefinement());
ScheduleResult previous = parentChild.nextDue(context);
if (previous != null && now.before(previous.getToDate())) {
nextResult = previous;
}
}
logger.debug(ConstrainedSchedule.this + ": in date is " + now +
", next interval is " + nextResult);
return nextResult;
}
/**
* Force sub classes to override toString.
*
* @return A description of the schedule.
*/
public abstract String toString();
}