001: /**********************************************************************************
002: * $URL: https://source.sakaiproject.org/svn/calendar/tags/sakai_2-4-1/calendar-impl/impl/src/java/org/sakaiproject/calendar/impl/RecurrenceRuleBase.java $
003: * $Id: RecurrenceRuleBase.java 8050 2006-04-20 17:39:55Z ggolden@umich.edu $
004: ***********************************************************************************
005: *
006: * Copyright (c) 2003, 2004, 2005, 2006 The Sakai Foundation.
007: *
008: * Licensed under the Educational Community License, Version 1.0 (the "License");
009: * you may not use this file except in compliance with the License.
010: * You may obtain a copy of the License at
011: *
012: * http://www.opensource.org/licenses/ecl1.php
013: *
014: * Unless required by applicable law or agreed to in writing, software
015: * distributed under the License is distributed on an "AS IS" BASIS,
016: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017: * See the License for the specific language governing permissions and
018: * limitations under the License.
019: *
020: **********************************************************************************/package org.sakaiproject.calendar.impl;
021:
022: import java.util.GregorianCalendar;
023: import java.util.List;
024: import java.util.TimeZone;
025: import java.util.Vector;
026:
027: import org.sakaiproject.calendar.api.RecurrenceRule;
028: import org.sakaiproject.time.api.Time;
029: import org.sakaiproject.time.api.TimeBreakdown;
030: import org.sakaiproject.time.api.TimeRange;
031: import org.sakaiproject.time.cover.TimeService;
032: import org.w3c.dom.Element;
033:
034: /**
035: * This is a common base for the daily, weekly, monthly, and yearly recurrence rules.
036: */
037: public abstract class RecurrenceRuleBase implements RecurrenceRule {
038: /** Every this many number of units: 1 would be daily/monthly/annually. */
039: private int interval = 1;
040:
041: /** For this many occurrences - if 0, does not limit. */
042: private int count = 0;
043:
044: /** No time ranges past this time are generated - if null, does not limit. */
045: private Time until = null;
046:
047: /**
048: * Construct.
049: */
050: public RecurrenceRuleBase() {
051: } // RecurrenceRuleBase
052:
053: /**
054: * Construct with no limits.
055: * @param interval Every this many number of days: 1 would be daily.
056: */
057: public RecurrenceRuleBase(int interval) {
058: this .interval = interval;
059: } // RecurrenceRuleBase
060:
061: /**
062: * Construct with count limit.
063: * @param interval Every this many number of days: 1 would be daily.
064: * @param count For this many occurrences - if 0, does not limit.
065: */
066: public RecurrenceRuleBase(int interval, int count) {
067: this .interval = interval;
068: this .count = count;
069: } // RecurrenceRuleBase
070:
071: /**
072: * Construct with time limit.
073: * @param interval Every this many number of days: 1 would be daily.
074: * @param until No time ranges past this time are generated - if null, does not limit.
075: */
076: public RecurrenceRuleBase(int interval, Time until) {
077: this .interval = interval;
078: this .until = (Time) until.clone();
079: } // RecurrenceRuleBase
080:
081: /**
082: * Return a List of all RecurrenceInstance objects generated by this rule within the given time range, based on the
083: * prototype first range, in time order.
084: * @param prototype The prototype first TimeRange.
085: * @param range A time range to limit the generated ranges.
086: * @param timeZone The time zone to use for displaying times.
087: * %%% Note: this is currently not implemented, and always uses the "local" zone.
088: * @return a List of RecurrenceInstance generated by this rule in this range.
089: */
090: public List generateInstances(TimeRange prototype, TimeRange range,
091: TimeZone timeZone) {
092: // %%% Note: base the breakdonw on the "timeZone" parameter to support multiple timeZone displays -ggolden
093: TimeBreakdown startBreakdown = prototype.firstTime()
094: .breakdownLocal();
095: List rv = new Vector();
096:
097: // %%% Note: use "timeZone" parameter, when the breakdown above is also based on that zone -ggolden
098: // GregorianCalendar startCalendarDate = new GregorianCalendar(Time.getLocalTimeZone());
099:
100: GregorianCalendar startCalendarDate = TimeService.getCalendar(
101: TimeService.getLocalTimeZone(), 0, 0, 0, 0, 0, 0, 0);
102:
103: startCalendarDate.set(startBreakdown.getYear(), startBreakdown
104: .getMonth() - 1, startBreakdown.getDay(),
105: startBreakdown.getHour(), startBreakdown.getMin(),
106: startBreakdown.getSec());
107:
108: GregorianCalendar nextCalendarDate = (GregorianCalendar) startCalendarDate
109: .clone();
110:
111: int currentCount = 1;
112:
113: do {
114: Time nextTime = TimeService.newTime(nextCalendarDate);
115:
116: // is this past count?
117: if ((getCount() > 0) && (currentCount > getCount()))
118: break;
119:
120: // is this past until?
121: if ((getUntil() != null) && isAfter(nextTime, getUntil()))
122: break;
123:
124: TimeRange nextTimeRange = TimeService.newTimeRange(nextTime
125: .getTime(), prototype.duration());
126:
127: //
128: // Is this out of the range?
129: //
130: if (isOverlap(range, nextTimeRange)) {
131: TimeRange eventTimeRange = null;
132:
133: // Single time cases require special handling.
134: if (prototype.isSingleTime()) {
135: eventTimeRange = TimeService
136: .newTimeRange(nextTimeRange.firstTime());
137: } else {
138: eventTimeRange = TimeService.newTimeRange(
139: nextTimeRange.firstTime(), nextTimeRange
140: .lastTime(), true, false);
141: }
142:
143: // use this one
144: rv.add(new RecurrenceInstance(eventTimeRange,
145: currentCount));
146: }
147:
148: // if next starts after the range, stop generating
149: else if (isAfter(nextTime, range.lastTime()))
150: break;
151:
152: // advance interval years.
153: nextCalendarDate = (GregorianCalendar) startCalendarDate
154: .clone();
155: nextCalendarDate.add(getRecurrenceType(), getInterval()
156: * currentCount);
157: currentCount++;
158: } while (true);
159:
160: return rv;
161: }
162:
163: protected abstract int getRecurrenceType();
164:
165: /**
166: * Checks for overlap.
167: * @param range1
168: * @param range2
169: */
170: protected final boolean isOverlap(TimeRange range1, TimeRange range2) {
171: return range1.overlaps(range2);
172: }
173:
174: /**
175: * Returns true if time1 is after time2
176: * @param time1
177: * @param time2
178: */
179: protected final boolean isAfter(Time time1, Time time2) {
180: return (time1.getTime() > time2.getTime());
181: }
182:
183: /**
184: * Gets the number of times (years) that this event should repeat.
185: */
186: public final int getCount() {
187: return count;
188: }
189:
190: /**
191: * Gets the end time for recurring events.
192: */
193: public final Time getUntil() {
194: return until;
195: }
196:
197: /**
198: * Gets the interval (in years) for this event.
199: */
200: public final int getInterval() {
201: return interval;
202: }
203:
204: /**
205: * Sets the number of times that this event will repeat. This object is immutable, but we need
206: * the "set" method for unit testing.
207: * @param i
208: */
209: protected final void setCount(int i) {
210: this .count = i;
211: setUntil(null);
212: }
213:
214: /**
215: * Sets the interval (in months) which this event will repeat. This object is immutable, but we need
216: * the "set" method for unit testing.
217: * @param i
218: */
219: protected final void setInterval(int i) {
220: this .interval = i;
221: setCount(0);
222: }
223:
224: /**
225: * Sets the end time for this recurring event. This object is immutable, but we need
226: * the "set" method for unit testing.
227: * @param time
228: */
229: protected final void setUntil(Time time) {
230: this .until = time;
231: }
232:
233: /**
234: * Take values from this xml element
235: * @param el The xml element.
236: */
237: public void set(Element el) {
238: // read the interval
239: try {
240: setInterval(Integer.parseInt(el.getAttribute("interval")));
241: } catch (Exception any) {
242: }
243:
244: // read the count
245: try {
246: setCount(Integer.parseInt(el.getAttribute("count")));
247: } catch (Exception any) {
248: }
249:
250: // read the until
251: try {
252: setUntil(TimeService.newTimeGmt(el.getAttribute("until")));
253: } catch (Exception any) {
254: }
255:
256: }
257:
258: /**
259: * Remove from the ranges list any RecurrenceInstance excluded by this rule.
260: * @param ranges The list (RecurrenceInstance) of ranges.
261: */
262: public final void excludeInstances(List ranges) {
263: }
264:
265: /**
266: * Set base class attributes in the Element rule during XML serialization.
267: * @param rule
268: */
269: protected void setBaseClassXML(Element rule) {
270: // set the interval
271: rule.setAttribute("interval", Integer.toString(getInterval()));
272:
273: // set either count or until (or neither), not both
274: if (getCount() > 0) {
275: rule.setAttribute("count", Integer.toString(getCount()));
276: } else if (getUntil() != null) {
277: rule.setAttribute("until", getUntil().toString());
278: }
279: }
280: }
|