| /******************************************************************************* |
| * 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.service.calendar; |
| |
| import java.util.ArrayList; |
| import com.ibm.icu.util.Calendar; |
| import java.util.Collections; |
| import java.util.Date; |
| import java.util.Iterator; |
| import java.util.List; |
| |
| import org.ofbiz.base.util.Debug; |
| import org.ofbiz.base.util.StringUtil; |
| import org.ofbiz.base.util.UtilValidate; |
| import org.ofbiz.service.calendar.TemporalExpression; |
| import org.ofbiz.entity.Delegator; |
| import org.ofbiz.entity.GenericEntityException; |
| import org.ofbiz.entity.GenericValue; |
| |
| /** |
| * Recurrence Info Object |
| */ |
| public class RecurrenceInfo { |
| |
| public static final String module = RecurrenceInfo.class.getName(); |
| |
| protected GenericValue info; |
| protected Date startDate; |
| protected List<RecurrenceRule> rRulesList; |
| protected List<RecurrenceRule> eRulesList; |
| protected List<Date> rDateList; |
| protected List<Date> eDateList; |
| |
| /** Creates new RecurrenceInfo */ |
| public RecurrenceInfo(GenericValue info) throws RecurrenceInfoException { |
| this.info = info; |
| if (!info.getEntityName().equals("RecurrenceInfo")) |
| throw new RecurrenceInfoException("Invalid RecurrenceInfo Value object."); |
| init(); |
| } |
| |
| /** Initializes the rules for this RecurrenceInfo object. */ |
| public void init() throws RecurrenceInfoException { |
| |
| if (info.get("startDateTime") == null) |
| throw new RecurrenceInfoException("Recurrence startDateTime cannot be null."); |
| |
| // Get start date |
| long startTime = info.getTimestamp("startDateTime").getTime(); |
| |
| if (startTime > 0) { |
| int nanos = info.getTimestamp("startDateTime").getNanos(); |
| |
| startTime += (nanos / 1000000); |
| } else { |
| throw new RecurrenceInfoException("Recurrence startDateTime must have a value."); |
| } |
| startDate = new Date(startTime); |
| |
| // Get the recurrence rules objects |
| try { |
| rRulesList = new ArrayList<RecurrenceRule>(); |
| for (GenericValue value: info.getRelated("RecurrenceRule", null, null, false)) { |
| rRulesList.add(new RecurrenceRule(value)); |
| } |
| } catch (GenericEntityException gee) { |
| rRulesList = null; |
| } catch (RecurrenceRuleException rre) { |
| throw new RecurrenceInfoException("Illegal rule format.", rre); |
| } |
| |
| // Get the exception rules objects |
| try { |
| eRulesList = new ArrayList<RecurrenceRule>(); |
| for (GenericValue value: info.getRelated("ExceptionRecurrenceRule", null, null, false)) { |
| eRulesList.add(new RecurrenceRule(value)); |
| } |
| } catch (GenericEntityException gee) { |
| eRulesList = null; |
| } catch (RecurrenceRuleException rre) { |
| throw new RecurrenceInfoException("Illegal rule format", rre); |
| } |
| |
| // Get the recurrence date list |
| rDateList = RecurrenceUtil.parseDateList(StringUtil.split(info.getString("recurrenceDateTimes"), ",")); |
| // Get the exception date list |
| eDateList = RecurrenceUtil.parseDateList(StringUtil.split(info.getString("exceptionDateTimes"), ",")); |
| |
| // Sort the lists. |
| Collections.sort(rDateList); |
| Collections.sort(eDateList); |
| } |
| |
| /** Returns the primary key for this value object */ |
| public String getID() { |
| return info.getString("recurrenceInfoId"); |
| } |
| |
| /** Returns the startDate Date object. */ |
| public Date getStartDate() { |
| return this.startDate; |
| } |
| |
| /** Returns the long value of the startDate. */ |
| public long getStartTime() { |
| return this.startDate.getTime(); |
| } |
| |
| /** Returns a recurrence rule iterator */ |
| public Iterator<RecurrenceRule> getRecurrenceRuleIterator() { |
| return rRulesList.iterator(); |
| } |
| |
| /** Returns a sorted recurrence date iterator */ |
| public Iterator<Date> getRecurrenceDateIterator() { |
| return rDateList.iterator(); |
| } |
| |
| /** Returns a exception recurrence iterator */ |
| public Iterator<RecurrenceRule> getExceptionRuleIterator() { |
| return eRulesList.iterator(); |
| } |
| |
| /** Returns a sorted exception date iterator */ |
| public Iterator<Date> getExceptionDateIterator() { |
| return eDateList.iterator(); |
| } |
| |
| /** Returns the current count of this recurrence. */ |
| public long getCurrentCount() { |
| if (info.get("recurrenceCount") != null) |
| return info.getLong("recurrenceCount").longValue(); |
| return 0; |
| } |
| |
| /** Increments the current count of this recurrence and updates the record. */ |
| public void incrementCurrentCount() throws GenericEntityException { |
| incrementCurrentCount(true); |
| } |
| |
| /** Increments the current count of this recurrence. */ |
| public void incrementCurrentCount(boolean store) throws GenericEntityException { |
| if (store) { |
| info.set("recurrenceCount", getCurrentCount() + 1); |
| info.store(); |
| } |
| } |
| |
| /** Removes the recurrence from persistant store. */ |
| public void remove() throws RecurrenceInfoException { |
| List<RecurrenceRule> rulesList = new ArrayList<RecurrenceRule>(); |
| |
| rulesList.addAll(rRulesList); |
| rulesList.addAll(eRulesList); |
| |
| try { |
| for (RecurrenceRule rule: rulesList) |
| rule.remove(); |
| info.remove(); |
| } catch (RecurrenceRuleException rre) { |
| throw new RecurrenceInfoException(rre.getMessage(), rre); |
| } catch (GenericEntityException gee) { |
| throw new RecurrenceInfoException(gee.getMessage(), gee); |
| } |
| } |
| |
| /** Returns the first recurrence. */ |
| public long first() { |
| return startDate.getTime(); |
| // First recurrence is always the start time |
| } |
| |
| /** Returns the estimated last recurrence. */ |
| public long last() { |
| // TODO: find the last recurrence. |
| return 0; |
| } |
| |
| /** Returns the next recurrence from now. */ |
| public long next() { |
| return next(RecurrenceUtil.now()); |
| } |
| |
| /** Returns the next recurrence from the specified time. */ |
| public long next(long fromTime) { |
| // Check for the first recurrence (StartTime is always the first recurrence) |
| if (getCurrentCount() == 0 || fromTime == 0 || fromTime == startDate.getTime()) { |
| return first(); |
| } |
| |
| if (Debug.verboseOn()) { |
| Debug.logVerbose("Date List Size: " + (rDateList == null ? 0 : rDateList.size()), module); |
| Debug.logVerbose("Rule List Size: " + (rRulesList == null ? 0 : rRulesList.size()), module); |
| } |
| |
| // Check the rules and date list |
| if (rDateList == null && rRulesList == null) { |
| return 0; |
| } |
| |
| long nextRuleTime = fromTime; |
| boolean hasNext = true; |
| |
| // Get the next recurrence from the rule(s). |
| Iterator<RecurrenceRule> rulesIterator = getRecurrenceRuleIterator(); |
| while (rulesIterator.hasNext()) { |
| RecurrenceRule rule = rulesIterator.next(); |
| while (hasNext) { |
| // Gets the next recurrence time from the rule. |
| nextRuleTime = getNextTime(rule, nextRuleTime); |
| // Tests the next recurrence against the rules. |
| if (nextRuleTime == 0 || isValid(nextRuleTime)) { |
| hasNext = false; |
| } |
| } |
| } |
| return nextRuleTime; |
| } |
| |
| /** Checks the current recurrence validity at the moment. */ |
| public boolean isValidCurrent() { |
| return isValidCurrent(RecurrenceUtil.now()); |
| } |
| |
| /** Checks the current recurrence validity for checkTime. */ |
| public boolean isValidCurrent(long checkTime) { |
| if (checkTime == 0 || (rDateList == null && rRulesList == null)) { |
| return false; |
| } |
| |
| boolean found = false; |
| Iterator<RecurrenceRule> rulesIterator = getRecurrenceRuleIterator(); |
| while (rulesIterator.hasNext()) { |
| RecurrenceRule rule = rulesIterator.next(); |
| long currentTime = rule.validCurrent(getStartTime(), checkTime, getCurrentCount()); |
| currentTime = checkDateList(rDateList, currentTime, checkTime); |
| if ((currentTime > 0) && isValid(checkTime)) { |
| found = true; |
| } else { |
| return false; |
| } |
| } |
| |
| return found; |
| } |
| |
| private long getNextTime(RecurrenceRule rule, long fromTime) { |
| long nextTime = rule.next(getStartTime(), fromTime, getCurrentCount()); |
| if (Debug.verboseOn()) Debug.logVerbose("Next Time Before Date Check: " + nextTime, module); |
| return checkDateList(rDateList, nextTime, fromTime); |
| } |
| |
| private long checkDateList(List<Date> dateList, long time, long fromTime) { |
| long nextTime = time; |
| |
| if (UtilValidate.isNotEmpty(dateList)) { |
| for (Date thisDate: dateList) { |
| if (nextTime > 0 && thisDate.getTime() < nextTime && thisDate.getTime() > fromTime) |
| nextTime = thisDate.getTime(); |
| else if (nextTime == 0 && thisDate.getTime() > fromTime) |
| nextTime = thisDate.getTime(); |
| } |
| } |
| return nextTime; |
| } |
| |
| private boolean isValid(long time) { |
| Iterator<RecurrenceRule> exceptRulesIterator = getExceptionRuleIterator(); |
| |
| while (exceptRulesIterator.hasNext()) { |
| RecurrenceRule except = exceptRulesIterator.next(); |
| |
| if (except.isValid(getStartTime(), time) || eDateList.contains(new Date(time))) |
| return false; |
| } |
| return true; |
| } |
| |
| public String primaryKey() { |
| return info.getString("recurrenceInfoId"); |
| } |
| |
| public static RecurrenceInfo makeInfo(Delegator delegator, long startTime, int frequency, |
| int interval, int count) throws RecurrenceInfoException { |
| return makeInfo(delegator, startTime, frequency, interval, count, 0); |
| } |
| |
| public static RecurrenceInfo makeInfo(Delegator delegator, long startTime, int frequency, |
| int interval, long endTime) throws RecurrenceInfoException { |
| return makeInfo(delegator, startTime, frequency, interval, -1, endTime); |
| } |
| |
| public static RecurrenceInfo makeInfo(Delegator delegator, long startTime, int frequency, |
| int interval, int count, long endTime) throws RecurrenceInfoException { |
| try { |
| RecurrenceRule r = RecurrenceRule.makeRule(delegator, frequency, interval, count, endTime); |
| String ruleId = r.primaryKey(); |
| GenericValue value = delegator.makeValue("RecurrenceInfo"); |
| |
| value.set("recurrenceRuleId", ruleId); |
| value.set("startDateTime", new java.sql.Timestamp(startTime)); |
| delegator.createSetNextSeqId(value); |
| RecurrenceInfo newInfo = new RecurrenceInfo(value); |
| |
| return newInfo; |
| } catch (RecurrenceRuleException re) { |
| throw new RecurrenceInfoException(re.getMessage(), re); |
| } catch (GenericEntityException ee) { |
| throw new RecurrenceInfoException(ee.getMessage(), ee); |
| } catch (RecurrenceInfoException rie) { |
| throw rie; |
| } |
| } |
| |
| /** Convert a RecurrenceInfo object to a TemporalExpression object. |
| * @param info A RecurrenceInfo instance |
| * @return A TemporalExpression instance |
| */ |
| public static TemporalExpression toTemporalExpression(RecurrenceInfo info) { |
| if (info == null) { |
| throw new IllegalArgumentException("info argument cannot be null"); |
| } |
| return new RecurrenceWrapper(info); |
| } |
| |
| /** Wraps a RecurrenceInfo object with a TemporalExpression object. This |
| * class is intended to help with the transition from RecurrenceInfo/RecurrenceRule |
| * to TemporalExpression. |
| */ |
| @SuppressWarnings("serial") |
| protected static class RecurrenceWrapper extends TemporalExpression { |
| protected RecurrenceInfo info; |
| protected RecurrenceWrapper() {} |
| public RecurrenceWrapper(RecurrenceInfo info) { |
| this.info = info; |
| } |
| @Override |
| public Calendar first(Calendar cal) { |
| long result = this.info.first(); |
| if (result == 0) { |
| return null; |
| } |
| Calendar first = (Calendar) cal.clone(); |
| first.setTimeInMillis(result); |
| return first; |
| } |
| @Override |
| public boolean includesDate(Calendar cal) { |
| return this.info.isValidCurrent(cal.getTimeInMillis()); |
| } |
| @Override |
| public Calendar next(Calendar cal, ExpressionContext context) { |
| long result = this.info.next(cal.getTimeInMillis()); |
| if (result == 0) { |
| return null; |
| } |
| Calendar next = (Calendar) cal.clone(); |
| next.setTimeInMillis(result); |
| return next; |
| } |
| @Override |
| public void accept(TemporalExpressionVisitor visitor) {} |
| @Override |
| public boolean isSubstitutionCandidate(Calendar cal, TemporalExpression expressionToTest) { |
| return false; |
| } |
| } |
| } |