package org.jblooming.agenda;
import org.jblooming.ontology.IdentifiableSupport;
import org.jblooming.persistence.PersistenceHome;
import org.jblooming.tracer.Tracer;
import org.jblooming.waf.view.PageState;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import net.sf.json.JSONObject;
public abstract class ScheduleSupport extends IdentifiableSupport implements Schedule, Comparable {
private Date start;
private Date end;
private int startTime; // Milliseconds elapsed from midnight
private long duration; // Duration in milliseconds
private int freq = 1; // how many days the event is repeated
private int repeat = 1; // number of times the event is repeated
private boolean onlyWorkingDays = false; // whether the event is repeated only on working days
public ScheduleSupport() {
}
/**
* @return the start date. Note this method returns null if the date is null
* see getValidityStartDate() on each subclass
*/
public Date getStartDate() {
return getStart();
}
/**
* @return the end date. Note this method returns null if the date is null
* see getValidityEndDate() on each subclass
*/
public Date getEndDate() {
return getEnd();
}
/**
* Set the start date and recompute the fields
*
* @param start
*/
public void setStartDate(Date start) {
setStart(start);
recalculateFields();
}
/**
* @return millis elapsed from hour midnight (00:00:00.000)
*/
public int getStartTimeInMillis() {
return startTime;
}
/**
* set the start time in millis from hour 00:00:00.000 and recompute fields
*
* @param startTime
*/
public void setStartTimeInMillis(int startTime) {
this.startTime = startTime;
if (start != null) {
CompanyCalendar cc = new CompanyCalendar();
cc.setTime(start);
cc.setMillisFromMidnight(this.startTime);
this.start = cc.getTime();
}
recalculateFields();
}
/**
* @return the event duration
*/
public long getDurationInMillis() {
return duration;
}
/**
* set the event duration in millis and recompute fields
*
* @param duration
*/
public void setDurationInMillis(long duration) {
this.duration = duration;
recalculateFields();
}
/**
* @return true if there is an overlapping betwheen periods
*/
// public boolean overlap(Period p) {
// return !(this.getStartDate().after(p.getEndDate()) || this.getEndDate().before(p.getStartDate()));
// }
public boolean overlap(Period p) {
long pf = getPreviousFireTimeBefore(p.getStartDate().getTime());
pf = pf + getDurationInMillis();
if (pf > p.getStartDate().getTime()) {
return true;
} else {
pf = getNextFireTimeAfter(p.getStartDate().getTime());
return pf < p.getEndDate().getTime();
}
}
/**
* @return the start date. If null return CompanyCalendar.MIN_DATE
* If you need to get null use getStartDate()
*/
public Date getValidityStartDate() {
if (this.getStartDate() == null)
return CompanyCalendar.MIN_DATE;
return this.getStartDate();
}
/**
* @return the start date. If null return CompanyCalendar.MAX_DATE
* If you need to get null use getEndDate()
*/
public Date getValidityEndDate() {
if (this.getEndDate() == null)
return CompanyCalendar.MAX_DATE;
return this.getEndDate();
}
/**
* @return the start. If null return CompanyCalendar.MIN_DATE
* If you need to get null use getEndDate()
*/
public long getValidityStartTime() {
if (this.getStartDate() == null)
return CompanyCalendar.MIN_DATE.getTime();
return this.getStartDate().getTime();
}
public long getValidityEndTime() {
if (this.getEndDate() == null)
return CompanyCalendar.MAX_DATE.getTime();
return this.getEndDate().getTime();
}
/**
* Used to get the smaller period that wraps the whole recurrent set.
* In case of not recurrent a new period with same ends is returned.
*/
public Period getPeriod() {
return new Period(this.getStartDate(), this.getEndDate());
}
public Date getNextFireDate() {
long nextFireTime = getNextFireTime();
if (nextFireTime > 0)
return new Date(nextFireTime);
else
return null;
}
public long getNextFireTime() {
Date afterTime = new Date();
Date pot = getNextDateAfter(afterTime);
if (pot == null || (end != null && pot.after(end)))
return 0;
else
return pot.getTime();
}
public Date getNextDateAfter(Date afterTime) {
long nextFireTime = getNextFireTimeAfter(afterTime.getTime());
if (nextFireTime > 0 && nextFireTime < Long.MAX_VALUE)
return new Date(nextFireTime);
else
return null;
}
public Date getPreviousDateBefore(Date beforeTime) {
long previousFireTime = getPreviousFireTimeBefore(beforeTime.getTime());
if (previousFireTime > 0)
return new Date(previousFireTime);
else
return null;
}
public int getFrequency() {
return getFreq();
}
public void setFrequency(int freq) {
this.freq = freq;
recalculateFields();
}
public int getRepetitions() {
return repeat;
}
public void setRepetitions(int repeat) {
this.repeat = repeat;
recalculateFields();
}
public void setOnWorkingDaysOnly(boolean f) {
setOnlyWorkingDays(f);
recalculateFields();
}
public boolean getOnWorkingDaysOnly() {
return isOnlyWorkingDays();
}
protected abstract void recalculateFields();
/**
* @param p
* @param trim
* @return re
*/
public List<Period> getPeriods(Period p, boolean trim) {
List<Period> ret = new ArrayList<Period>();
long requiredStart = p.getValidityStartTime();
long requiredEnd = p.getValidityEndTime();
if (!(this.getValidityEndTime() < requiredStart || this.getValidityStartTime() > requiredEnd)) {
// find the previous
long ps = this.getPreviousFireTimeBefore(requiredStart);
ps += this.getDurationInMillis();
if (ps > requiredStart) {
if (trim)
ret.add(new Period(requiredStart, Math.min(ps, requiredEnd)));
else
ret.add(new Period(ps - this.getDurationInMillis(), ps));
}
//long nextFire = this.getNextFireTimeAfter(requiredStart - 1);// 8/6/2010 - commented out in order to avoid hits the same appointment when this.start==p.start on recurrent events
long nextFire=requiredStart;
if (requiredStart!=getStart().getTime())
nextFire = this.getNextFireTimeAfter(requiredStart);
int watchDog = 0; // not more than 1000 occurrences for event
while (nextFire <= requiredEnd && watchDog < 1000) {
long eventStart = nextFire;
long eventDuration = this.getDurationInMillis();
if (trim && (nextFire + eventDuration > requiredEnd)) {
ret.add(new Period(eventStart, requiredEnd));
} else {
ret.add(new Period(eventStart, eventStart + eventDuration));
}
nextFire = this.getNextFireTimeAfter(nextFire + eventDuration + 1);
watchDog++;
}
if (watchDog >= 1000)
Tracer.platformLogger.warn("watchDog barking on ScheduleSupport.getPeriods period id = " + getId());
}
return ret;
}
/**
* @param date
* @return true if the date is include in a fire period
*/
public boolean contains(Date date) {
boolean ret = false;
// if (this.getPreviousFireTimeBefore(date.getTime()) + getDurationInMillis() > date.getTime()) // BUG this does not work in the extremes
if (this.getPreviousFireTimeBefore(date.getTime() + 1) + getDurationInMillis() >= date.getTime()) // FIXED on 24/09/2008 by robicch
ret = true;
return ret;
}
/*
* This method return the validity period.
* The period is trimmed to the starting time and the ending time.
* There is no test for working days validity (eg. for example if the validity period starts on sat at 8.00
* and you choos working days only, the validity period start on sat at 8.00 NOT on mon 8.00)
*/
public Period getValidityPeriod() {
return new Period(getValidityStartDate(), getValidityEndDate());
}
protected Date getStart() {
return start;
}
protected void setStart(Date start) {
this.start = start;
}
protected Date getEnd() {
return end;
}
protected void setEnd(Date end) {
this.end = end;
}
protected int getStartTime() {
return startTime;
}
protected void setStartTime(int startTime) {
this.startTime = startTime;
}
protected long getDuration() {
return duration;
}
protected void setDuration(long duration) {
this.duration = duration;
}
protected int getFreq() {
return freq;
}
protected void setFreq(int freq) {
this.freq = freq;
}
protected int getRepeat() {
return repeat;
}
protected void setRepeat(int repeat) {
this.repeat = repeat;
}
protected boolean isOnlyWorkingDays() {
return onlyWorkingDays;
}
protected void setOnlyWorkingDays(boolean onlyWorkingDays) {
this.onlyWorkingDays = onlyWorkingDays;
}
public int compareTo(Object o) {
int retVal = 0;
ScheduleSupport scheduleSupport = ((ScheduleSupport) o);
if (getValidityStartTime() < scheduleSupport.getValidityStartTime())
retVal = -1;
else if (getValidityStartTime() > scheduleSupport.getValidityStartTime())
retVal = 1;
else if (this.getId() != null && !PersistenceHome.NEW_EMPTY_ID.equals(getId())) {
retVal = (this.getId() + "").compareTo(scheduleSupport.getId() + "");
} else {
retVal = this.getEndDate().compareTo(scheduleSupport.getEndDate()); // commented on 11/5/2007
//retVal = (this==scheduleSupport ? 0 : 1); // two non persistent period with identical timind are equals only when are == (at least this is required in agendaweek)
}
return retVal;
}
/* added by schelazzi: this method count the number of repetision of an event ina specific period. this because google ical returns ot the number but the date until an event is valid. */
public static int calculateRepetition(int[] days, Date start, Date until) {
int repetition = 0;
Calendar cd = Calendar.getInstance();
cd.setTime(start);
int numberOfWeekStart = cd.get(CompanyCalendar.WEEK_OF_YEAR);
int numberOfDayStart = cd.get(CompanyCalendar.DAY_OF_WEEK);
cd.setTime(until);
int numberOfWeekUntil = cd.get(CompanyCalendar.WEEK_OF_YEAR);
int numberOfDayUntil = cd.get(CompanyCalendar.DAY_OF_WEEK);
repetition = ((numberOfWeekUntil - numberOfWeekStart) - 1) * days.length;
for (int d : days) {
if (d >= numberOfDayStart)
repetition++;
if (d <= numberOfDayUntil)
repetition++;
}
return repetition;
}
public JSONObject jsonify() {
JSONObject ret = new JSONObject();
ret.element("id", getId());
ret.element("startMillis", getStartDate().getTime());
ret.element("endMillis", getEndDate().getTime());
ret.element("duration", getDuration());
ret.element("freq", getFrequency());
ret.element("repeat", getRepetitions());
ret.element("onlyWorkingDays", isOnlyWorkingDays());
return ret;
}
}