blob: f1e1acc1ae0bbfd427af7748eb1b2741ee0e356d [file] [log] [blame]
/*******************************************************************************
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*******************************************************************************/
package org.ofbiz.workeffort.workeffort;
import java.util.List;
import java.util.Set;
import java.util.Stack;
import javolution.util.FastList;
import javolution.util.FastSet;
import net.fortuna.ical4j.model.*;
import net.fortuna.ical4j.model.property.*;
import org.ofbiz.service.calendar.TemporalExpression;
import org.ofbiz.service.calendar.TemporalExpressionVisitor;
import org.ofbiz.service.calendar.TemporalExpressions;
import org.ofbiz.service.calendar.TemporalExpressions.*;
import com.ibm.icu.util.Calendar;
/** Temporal Expression to iCalendar recurrence converter. The conversion results
* (or conversion success) are unpredictable since the OFBiz Temporal Expressions
* are more sophisticated than iCalendar recurrences. This class attempts to
* make a best attempt at conversion and throws <code>IllegalStateException</code>
* when conversion is not possible.
*/
public class ICalRecurConverter implements TemporalExpressionVisitor {
protected static final WeekDay dayOfWeekArray[] = {WeekDay.SU, WeekDay.MO, WeekDay.TU, WeekDay.WE, WeekDay.TH, WeekDay.FR, WeekDay.SA};
@SuppressWarnings("unchecked")
public static void convert(TemporalExpression expr, PropertyList eventProps) {
ICalRecurConverter converter = new ICalRecurConverter();
expr.accept(converter);
DtStart dateStart = (DtStart) eventProps.getProperty(Property.DTSTART);
if (converter.dateStart != null) {
if (dateStart != null) {
eventProps.remove(dateStart);
}
dateStart = converter.dateStart;
eventProps.add(dateStart);
}
if (dateStart != null && converter.exRuleList.size() > 0) {
// iCalendar quirk - if exclusions exist, then the start date must be excluded also
ExDate exdate = new ExDate();
exdate.getDates().add(dateStart.getDate());
converter.exDateList.add(exdate);
}
eventProps.addAll(converter.incDateList);
eventProps.addAll(converter.incRuleList);
eventProps.addAll(converter.exDateList);
eventProps.addAll(converter.exRuleList);
}
protected DtStart dateStart = null;
protected List<DateListProperty> incDateList = FastList.newInstance();
protected List<DateListProperty> exDateList = FastList.newInstance();
protected List<RRule> incRuleList = FastList.newInstance();
protected List<ExRule> exRuleList = FastList.newInstance();
protected VisitorState state = new VisitorState();
protected Stack<VisitorState> stateStack = new Stack<VisitorState>();
protected ICalRecurConverter() {}
@SuppressWarnings("unchecked")
protected Recur consolidateRecurs(List<Recur> recurList) {
// Try to consolidate a list of Recur instances into one instance
Set<Integer> monthList = FastSet.newInstance();
Set<Integer> monthDayList = FastSet.newInstance();
Set<WeekDay> weekDayList = FastSet.newInstance();
Set<Integer> hourList = FastSet.newInstance();
Set<Integer> minuteList = FastSet.newInstance();
String freq = null;
int freqCount = 0;
for (Recur recur : recurList) {
monthList.addAll(recur.getMonthList());
monthDayList.addAll(recur.getMonthDayList());
weekDayList.addAll(recur.getDayList());
hourList.addAll(recur.getHourList());
minuteList.addAll(recur.getMinuteList());
if (recur.getInterval() != 0) {
freq = recur.getFrequency();
freqCount = recur.getInterval();
}
}
if (freq == null && monthList.size() > 0) {
freq = Recur.MONTHLY;
} else if (freq == null && (monthDayList.size() > 0 || weekDayList.size() > 0)) {
freq = Recur.DAILY;
} else if (freq == null && hourList.size() > 0) {
freq = Recur.HOURLY;
} else if (freq == null && minuteList.size() > 0) {
freq = Recur.MINUTELY;
}
if (freq == null) {
throw new IllegalStateException("Unable to convert intersection");
}
Recur newRecur = new Recur(freq, 0);
if (freqCount != 0) {
newRecur.setInterval(freqCount);
}
newRecur.getMonthList().addAll(monthList);
newRecur.getMonthDayList().addAll(monthDayList);
newRecur.getDayList().addAll(weekDayList);
newRecur.getHourList().addAll(hourList);
newRecur.getMinuteList().addAll(minuteList);
return newRecur;
}
// ----- TemporalExpressionVisitor Implementation ----- //
@Override
public void visit(Difference expr) {
VisitorState newState = new VisitorState();
newState.isIntersection = this.state.isIntersection;
this.stateStack.push(this.state);
this.state = newState;
expr.getIncluded().accept(this);
newState.isExcluded = true;
expr.getExcluded().accept(this);
this.state = this.stateStack.pop();
if (this.state.isIntersection) {
this.state.inclRecurList.addAll(newState.inclRecurList);
this.state.exRecurList.addAll(newState.exRecurList);
}
}
@SuppressWarnings("unchecked")
@Override
public void visit(HourRange expr) {
NumberList hourList = new NumberList();
hourList.addAll(expr.getHourRangeAsSet());
Recur recur = new Recur(Recur.HOURLY, 0);
recur.getHourList().addAll(hourList);
this.state.addRecur(recur);
}
@Override
public void visit(Intersection expr) {
this.stateStack.push(this.state);
VisitorState newState = new VisitorState();
newState.isExcluded = this.state.isExcluded;
newState.isIntersection = true;
this.state = newState;
for (TemporalExpression childExpr : expr.getExpressionSet()) {
childExpr.accept(this);
}
this.state = this.stateStack.pop();
if (newState.inclRecurList.size() > 0) {
this.incRuleList.add(new RRule(this.consolidateRecurs(newState.inclRecurList)));
}
if (newState.exRecurList.size() > 0) {
this.exRuleList.add(new ExRule(this.consolidateRecurs(newState.exRecurList)));
}
}
@SuppressWarnings("unchecked")
@Override
public void visit(MinuteRange expr) {
NumberList minuteList = new NumberList();
minuteList.addAll(expr.getMinuteRangeAsSet());
Recur recur = new Recur(Recur.MINUTELY, 0);
recur.getMinuteList().addAll(minuteList);
this.state.addRecur(recur);
}
@Override
public void visit(Null expr) {}
@Override
public void visit(Substitution expr) {
// iCalendar format does not support substitutions. Do nothing for now.
}
@Override
public void visit(TemporalExpressions.DateRange expr) {
if (this.state.isExcluded) {
throw new IllegalStateException("iCalendar does not support excluded date ranges");
}
org.ofbiz.base.util.DateRange range = expr.getDateRange();
PeriodList periodList = new PeriodList();
periodList.add(new Period(new DateTime(range.start()), new DateTime(range.end())));
this.incDateList.add(new RDate(periodList));
}
@Override
public void visit(TemporalExpressions.DayInMonth expr) {
Recur recur = new Recur(Recur.MONTHLY, 0);
recur.getDayList().add(new WeekDay(dayOfWeekArray[expr.getDayOfWeek() - 1], expr.getOccurrence()));
this.state.addRecur(recur);
}
@SuppressWarnings("unchecked")
@Override
public void visit(TemporalExpressions.DayOfMonthRange expr) {
int startDay = expr.getStartDay();
int endDay = expr.getEndDay();
NumberList dayList = new NumberList();
dayList.add(startDay);
while (startDay != endDay) {
startDay++;
dayList.add(startDay);
}
Recur recur = new Recur(Recur.DAILY, 0);
recur.getMonthDayList().addAll(dayList);
this.state.addRecur(recur);
}
@SuppressWarnings("unchecked")
@Override
public void visit(TemporalExpressions.DayOfWeekRange expr) {
int startDay = expr.getStartDay();
int endDay = expr.getEndDay();
WeekDayList dayList = new WeekDayList();
dayList.add(dayOfWeekArray[startDay - 1]);
while (startDay != endDay) {
startDay++;
if (startDay > Calendar.SATURDAY) {
startDay = Calendar.SUNDAY;
}
dayList.add(dayOfWeekArray[startDay - 1]);
}
Recur recur = new Recur(Recur.DAILY, 0);
recur.getDayList().addAll(dayList);
this.state.addRecur(recur);
}
@Override
public void visit(TemporalExpressions.Frequency expr) {
if (this.dateStart == null) {
this.dateStart = new DtStart(new net.fortuna.ical4j.model.Date(expr.getStartDate()));
}
int freqCount = expr.getFreqCount();
int freqType = expr.getFreqType();
switch (freqType) {
case Calendar.SECOND:
this.state.addRecur((new Recur(Recur.SECONDLY, freqCount)));
break;
case Calendar.MINUTE:
this.state.addRecur((new Recur(Recur.MINUTELY, freqCount)));
break;
case Calendar.HOUR:
this.state.addRecur((new Recur(Recur.HOURLY, freqCount)));
break;
case Calendar.DAY_OF_MONTH:
this.state.addRecur((new Recur(Recur.DAILY, freqCount)));
break;
case Calendar.MONTH:
this.state.addRecur((new Recur(Recur.MONTHLY, freqCount)));
break;
case Calendar.YEAR:
this.state.addRecur((new Recur(Recur.YEARLY, freqCount)));
break;
}
}
@SuppressWarnings("unchecked")
@Override
public void visit(TemporalExpressions.MonthRange expr) {
int startMonth = expr.getStartMonth();
int endMonth = expr.getEndMonth();
Calendar cal = Calendar.getInstance();
int maxMonth = cal.getActualMaximum(Calendar.MONTH);
NumberList monthList = new NumberList();
monthList.add(startMonth + 1);
while (startMonth != endMonth) {
startMonth++;
if (startMonth > maxMonth) {
startMonth = Calendar.JANUARY;
}
monthList.add(startMonth + 1);
}
Recur recur = new Recur(Recur.MONTHLY, 0);
recur.getMonthList().addAll(monthList);
this.state.addRecur(recur);
}
@Override
public void visit(Union expr) {
for (TemporalExpression childExpr : expr.getExpressionSet()) {
childExpr.accept(this);
}
}
protected class VisitorState {
public boolean isExcluded = false;
public boolean isIntersection = false;
public List<Recur> inclRecurList = FastList.newInstance();
public List<Recur> exRecurList = FastList.newInstance();
public void addRecur(Recur recur) {
if (this.isIntersection) {
if (this.isExcluded) {
this.exRecurList.add(recur);
} else {
this.inclRecurList.add(recur);
}
} else {
if (this.isExcluded) {
exRuleList.add(new ExRule(recur));
} else {
incRuleList.add(new RRule(recur));
}
}
}
}
}