| /******************************************************************************* |
| * 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)); |
| } |
| } |
| } |
| } |
| } |