/* ************************************************************************
#
# DivConq
#
# http://divconq.com/
#
# Copyright:
# Copyright 2014 eTimeline, LLC. All rights reserved.
#
# License:
# See the license.txt file in the project's top-level directory for details.
#
# Authors:
# * Andy White
#
************************************************************************ */
package divconq.scheduler.common;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.List;
import org.joda.time.DateTime;
import divconq.scheduler.limit.CheckInfo;
import divconq.scheduler.limit.MonthdayKind;
import divconq.util.StringUtil;
import divconq.util.TimeUtil;
import divconq.xml.XElement;
public class MonthHelper extends CommonHelper {
protected List<MonthEntry> months = new ArrayList<MonthEntry>();
// first time this code was run since the module/server was started
// requires special check for any missed runs
protected boolean firstCall = true;
@Override
public void init(CommonSchedule schedule, XElement config) {
if (config != null) {
for (XElement el : config.selectAll("Months")) {
MonthEntry entry = new MonthEntry();
entry.init(el);
this.months.add(entry);
}
}
if (this.months.size() == 0) {
MonthEntry entry = new MonthEntry();
entry.init(null);
this.months.add(entry);
}
}
// return null to indicate this can never run again
@Override
public DateTime next() {
this.schedule.setHint("_ScheduleTimeHint", "");
if (this.firstCall) {
this.firstCall = false;
if (this.limits.isEnded())
return null;
// if the valid range for schedule has not started yet, then
// schedule way out
if (!this.limits.isStarted()) {
this.firstCall = false;
this.last = new DateTime(this.limits.getFirstDate());
}
// if there was no past runs, do not try to find them
// or if there are not any required
//else if (this.last == null) {
// this.last = new DateTime(); // start with today (now)
//}
// see if we need to run because of a missed past schedule
else {
if (this.last == null)
this.last = new DateTime(1970, 1, 1, 0, 0, 0, 0); // start with deep past
for (MonthEntry we : this.months) {
if (we.checkStartupNeedsRun()) {
//System.out.println("run asap!!!!");
this.last = this.limits.nextAllowedRun();
return this.last;
}
}
if (this.last == null)
this.last = new DateTime(); // start with today (now)
}
}
// we should never get here with a really out of date "last", today is fine
CheckInfo ci = new CheckInfo();
ci.setWhen(this.last);
while (true) {
for (MonthEntry we : this.months) {
MonthdayEntry me = we.getApplicable(ci);
if (me != null) {
ScheduleEntry se = me.dailySchedule.next(ci.getWhen());
while (se != null) {
// add the scheduled entry to the time
// (first time with a given date, this is the time past midnight)
DateTime tlast = TimeUtil.withTime(ci.getWhen(), se.getTime());
// we cannot schedule again, the schedule is expired
if (this.limits.isEnded(tlast)) {
this.last = null;
return null;
}
// can we run at the suggested time?
if (this.limits.checkForRun(tlast)) {
this.last = tlast;
return this.last;
}
// if not, should we run asap after the suggested time
if (se.isRunIfMissed()) {
this.last = this.limits.nextAllowedRunAfter(tlast);
return this.last;
}
// go on and check the next time
se = me.dailySchedule.next(tlast);
}
break;
}
}
ci.incrementDay();
}
}
class MonthEntry implements IDateChecker {
protected List<MonthdayEntry> monthly = new ArrayList<MonthdayEntry>();
// (0 = jan, 11 = dec)
protected BitSet monthOfYear = new BitSet(12);
public void init(XElement config) {
if (config != null) {
if ("True".equals(config.getAttribute("All")))
this.monthOfYear.set(0, 11);
if ("True".equals(config.getAttribute("January")))
this.monthOfYear.set(0);
if ("True".equals(config.getAttribute("February")))
this.monthOfYear.set(1);
if ("True".equals(config.getAttribute("March")))
this.monthOfYear.set(2);
if ("True".equals(config.getAttribute("April")))
this.monthOfYear.set(3);
if ("True".equals(config.getAttribute("May")))
this.monthOfYear.set(4);
if ("True".equals(config.getAttribute("June")))
this.monthOfYear.set(5);
if ("True".equals(config.getAttribute("July")))
this.monthOfYear.set(6);
if ("True".equals(config.getAttribute("August")))
this.monthOfYear.set(7);
if ("True".equals(config.getAttribute("September")))
this.monthOfYear.set(8);
if ("True".equals(config.getAttribute("October")))
this.monthOfYear.set(9);
if ("True".equals(config.getAttribute("November")))
this.monthOfYear.set(10);
if ("True".equals(config.getAttribute("December")))
this.monthOfYear.set(11);
String[] slots = { "Monthday", "First", "Second", "Third", "Fourth", "Last" };
for (String slot : slots)
for (XElement el : config.selectAll(slot)) {
MonthdayEntry ww = new MonthdayEntry();
ww.init(el);
this.monthly.add(ww);
}
}
// if none set, then default to all
if (monthOfYear.cardinality() == 0)
this.monthOfYear.set(0, 11);
if (this.monthly.size() == 0) {
MonthdayEntry ww = new MonthdayEntry();
ww.init(null);
this.monthly.add(ww);
}
}
// the meaning of this method is off a little from expected
// normally this should only check monthOfYear and ignore the monthdays
// but for the checkStartupNeedsRun this needs to check the monthdays
// lets see if any problem come up
@Override
public boolean checkDate(CheckInfo ci) {
if (this.monthOfYear.get(ci.getMonthOfYear() - 1))
for (MonthdayEntry w : this.monthly)
if (w.checkDate(ci))
return true;
return false;
}
public MonthdayEntry getApplicable(CheckInfo ci) {
if (this.monthOfYear.get(ci.getMonthOfYear() - 1))
for (MonthdayEntry w : this.monthly)
if (w.checkDate(ci))
return w;
return null;
}
public boolean checkStartupNeedsRun() {
for (MonthdayEntry w : this.monthly)
if (MonthHelper.this.checkStartupNeedsRun(w.dailySchedule, this))
return true;
return false;
}
}
class MonthdayEntry implements IDateChecker {
ScheduleList dailySchedule = new ScheduleList();
// (0 = monday, 6 = sunday)
BitSet dayOfWeek = new BitSet(7);
protected MonthdayKind kind = MonthdayKind.Set;
// if type SET then look here for the listed days
protected BitSet dayOfMonth = new BitSet(31);
// does the set include "Last"
protected boolean isLastDayOfMonth = false;
public void init(XElement config) {
if (config != null) {
String kind = config.getName();
if ("First".equals(kind))
this.kind = MonthdayKind.First;
else if ("Second".equals(kind))
this.kind = MonthdayKind.Second;
else if ("Third".equals(kind))
this.kind = MonthdayKind.Third;
else if ("Fourth".equals(kind))
this.kind = MonthdayKind.Fourth;
else if ("Last".equals(kind))
this.kind = MonthdayKind.Last;
if (this.kind != MonthdayKind.Set) {
if ("True".equals(config.getAttribute("All")))
this.dayOfWeek.set(0, 6);
if ("True".equals(config.getAttribute("Monday")))
this.dayOfWeek.set(0);
if ("True".equals(config.getAttribute("Tuesday")))
this.dayOfWeek.set(1);
if ("True".equals(config.getAttribute("Wednesday")))
this.dayOfWeek.set(2);
if ("True".equals(config.getAttribute("Thursday")))
this.dayOfWeek.set(3);
if ("True".equals(config.getAttribute("Friday")))
this.dayOfWeek.set(4);
if ("True".equals(config.getAttribute("Saturday")))
this.dayOfWeek.set(5);
if ("True".equals(config.getAttribute("Sunday")))
this.dayOfWeek.set(6);
// if none set then default to all
if (this.dayOfWeek.cardinality() == 0)
this.dayOfWeek.set(0, 6);
}
else {
String list = config.getAttribute("List");
if (StringUtil.isEmpty(list) || "All".equals(list)) {
this.isLastDayOfMonth = true;
this.dayOfMonth.set(0, 30);
}
else {
String[] parts = list.split(",");
for (String p : parts) {
if (p.equals("Last")) {
this.isLastDayOfMonth = true;
break;
}
int idx = (int)StringUtil.parseInt(p, 0) - 1;
if (idx > -1)
this.dayOfMonth.set(idx);
}
}
}
}
if ((this.kind == MonthdayKind.Set) && (this.dayOfMonth.cardinality() == 0)) {
this.isLastDayOfMonth = true;
this.dayOfMonth.set(0, 30);
}
this.dailySchedule.init(config);
}
@Override
public boolean checkDate(CheckInfo si) {
if (this.kind == MonthdayKind.Set) {
if (si.isLastDay() && this.isLastDayOfMonth)
return true;
else if (this.dayOfMonth.get(si.getDayOfMonth() - 1))
return true;
}
else if (this.dayOfWeek.get(si.getDayOfWeek() - 1)) {
if ((this.kind == MonthdayKind.First) && (si.getMonthPlacement() == 1))
return true;
else if ((this.kind == MonthdayKind.Second) && (si.getMonthPlacement() == 2))
return true;
else if ((this.kind == MonthdayKind.Third) && (si.getMonthPlacement() == 3))
return true;
else if ((this.kind == MonthdayKind.Fourth) && (si.getMonthPlacement() == 4))
return true;
else if ((this.kind == MonthdayKind.Last) && (si.isLastPlacement()))
return true;
}
return false;
}
}
}