Tue, 10 Feb 2015 18:12:00 +0100
Import initial revisions of existing project AndroidCaldavSyncAdapater,
forked from upstream repository at 27e8a0f8495c92e0780d450bdf0c7cec77a03a55.
1 /**
2 * Copyright (c) 2012, Ben Fortuna
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * o Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 *
12 * o Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 *
16 * o Neither the name of Ben Fortuna nor the names of any other contributors
17 * may be used to endorse or promote products derived from this software
18 * without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 */
32 package net.fortuna.ical4j.model;
34 import java.io.IOException;
35 import java.io.Serializable;
36 import java.net.URISyntaxException;
37 import java.text.ParseException;
38 import java.util.Iterator;
40 import net.fortuna.ical4j.model.parameter.Value;
41 import net.fortuna.ical4j.model.property.DateProperty;
42 import net.fortuna.ical4j.model.property.DtStart;
43 import net.fortuna.ical4j.model.property.Duration;
44 import net.fortuna.ical4j.model.property.ExDate;
45 import net.fortuna.ical4j.model.property.ExRule;
46 import net.fortuna.ical4j.model.property.RDate;
47 import net.fortuna.ical4j.model.property.RRule;
48 import net.fortuna.ical4j.util.Strings;
50 import org.apache.commons.lang.builder.EqualsBuilder;
51 import org.apache.commons.lang.builder.HashCodeBuilder;
53 /**
54 * $Id$ [Apr 5, 2004]
55 *
56 * Defines an iCalendar component. Subclasses of this class provide additional validation and typed values for specific
57 * iCalendar components.
58 * @author Ben Fortuna
59 */
60 public abstract class Component implements Serializable {
62 private static final long serialVersionUID = 4943193483665822201L;
64 /**
65 * Component start token.
66 */
67 public static final String BEGIN = "BEGIN";
69 /**
70 * Component end token.
71 */
72 public static final String END = "END";
74 /**
75 * Component token.
76 */
77 public static final String VEVENT = "VEVENT";
79 /**
80 * Component token.
81 */
82 public static final String VTODO = "VTODO";
84 /**
85 * Component token.
86 */
87 public static final String VJOURNAL = "VJOURNAL";
89 /**
90 * Component token.
91 */
92 public static final String VFREEBUSY = "VFREEBUSY";
94 /**
95 * Component token.
96 */
97 public static final String VTIMEZONE = "VTIMEZONE";
99 /**
100 * Component token.
101 */
102 public static final String VALARM = "VALARM";
104 /**
105 * Component token.
106 */
107 public static final String VAVAILABILITY = "VAVAILABILITY";
109 /**
110 * Component token.
111 */
112 public static final String VVENUE = "VVENUE";
114 /**
115 * Component token.
116 */
117 public static final String AVAILABLE = "AVAILABLE";
119 /**
120 * Prefix for non-standard components.
121 */
122 public static final String EXPERIMENTAL_PREFIX = "X-";
124 private String name;
126 private PropertyList properties;
128 /**
129 * Constructs a new component containing no properties.
130 * @param s a component name
131 */
132 protected Component(final String s) {
133 this(s, new PropertyList());
134 }
136 /**
137 * Constructor made protected to enforce the use of <code>ComponentFactory</code> for component instantiation.
138 * @param s component name
139 * @param p a list of properties
140 */
141 protected Component(final String s, final PropertyList p) {
142 this.name = s;
143 this.properties = p;
144 }
146 /**
147 * {@inheritDoc}
148 */
149 public String toString() {
150 final StringBuffer buffer = new StringBuffer();
151 buffer.append(BEGIN);
152 buffer.append(':');
153 buffer.append(getName());
154 buffer.append(Strings.LINE_SEPARATOR);
155 buffer.append(getProperties());
156 buffer.append(END);
157 buffer.append(':');
158 buffer.append(getName());
159 buffer.append(Strings.LINE_SEPARATOR);
161 return buffer.toString();
162 }
164 /**
165 * @return Returns the name.
166 */
167 public final String getName() {
168 return name;
169 }
171 /**
172 * @return Returns the properties.
173 */
174 public final PropertyList getProperties() {
175 return properties;
176 }
178 /**
179 * Convenience method for retrieving a list of named properties.
180 * @param name name of properties to retrieve
181 * @return a property list containing only properties with the specified name
182 */
183 public final PropertyList getProperties(final String name) {
184 return getProperties().getProperties(name);
185 }
187 /**
188 * Convenience method for retrieving a named property.
189 * @param name name of the property to retrieve
190 * @return the first matching property in the property list with the specified name
191 */
192 public final Property getProperty(final String name) {
193 return getProperties().getProperty(name);
194 }
196 /**
197 * Perform validation on a component and its properties.
198 * @throws ValidationException where the component is not in a valid state
199 */
200 public final void validate() throws ValidationException {
201 validate(true);
202 }
204 /**
205 * Perform validation on a component.
206 * @param recurse indicates whether to validate the component's properties
207 * @throws ValidationException where the component is not in a valid state
208 */
209 public abstract void validate(final boolean recurse)
210 throws ValidationException;
212 /**
213 * Invoke validation on the component properties in its current state.
214 * @throws ValidationException where any of the component properties is not in a valid state
215 */
216 protected final void validateProperties() throws ValidationException {
217 for (final Iterator i = getProperties().iterator(); i.hasNext();) {
218 final Property property = (Property) i.next();
219 property.validate();
220 }
221 }
223 /**
224 * {@inheritDoc}
225 */
226 public boolean equals(final Object arg0) {
227 if (arg0 instanceof Component) {
228 final Component c = (Component) arg0;
229 return new EqualsBuilder().append(getName(), c.getName())
230 .append(getProperties(), c.getProperties()).isEquals();
231 }
232 return super.equals(arg0);
233 }
235 /**
236 * {@inheritDoc}
237 */
238 public int hashCode() {
239 return new HashCodeBuilder().append(getName()).append(getProperties())
240 .toHashCode();
241 }
243 /**
244 * Create a (deep) copy of this component.
245 * @return the component copy
246 * @throws IOException where an error occurs reading the component data
247 * @throws ParseException where parsing component data fails
248 * @throws URISyntaxException where component data contains an invalid URI
249 */
250 public Component copy() throws ParseException, IOException,
251 URISyntaxException {
253 // Deep copy properties..
254 final PropertyList newprops = new PropertyList(getProperties());
256 return ComponentFactory.getInstance().createComponent(getName(),
257 newprops);
258 }
260 /**
261 * Calculates the recurrence set for this component using the specified period.
262 * The recurrence set is derived from a combination of the component start date,
263 * recurrence rules and dates, and exception rules and dates. Note that component
264 * transparency and anniversary-style dates do not affect the resulting
265 * intersection.
266 * <p>If an explicit DURATION is not specified, the effective duration of each
267 * returned period is derived from the DTSTART and DTEND or DUE properties.
268 * If the component has no DURATION, DTEND or DUE, the effective duration is set
269 * to PT0S</p>
270 * @param period a range to calculate recurrences for
271 * @return a list of periods
272 */
273 public final PeriodList calculateRecurrenceSet(final Period period) {
275 // validate();
277 final PeriodList recurrenceSet = new PeriodList();
279 final DtStart start = (DtStart) getProperty(Property.DTSTART);
280 DateProperty end = (DateProperty) getProperty(Property.DTEND);
281 if (end == null) {
282 end = (DateProperty) getProperty(Property.DUE);
283 }
284 Duration duration = (Duration) getProperty(Property.DURATION);
286 // if no start date specified return empty list..
287 if (start == null) {
288 return recurrenceSet;
289 }
291 final Value startValue = (Value) start.getParameter(Parameter.VALUE);
293 // initialise timezone..
294 // if (startValue == null || Value.DATE_TIME.equals(startValue)) {
295 if (start.isUtc()) {
296 recurrenceSet.setUtc(true);
297 }
298 else if (start.getDate() instanceof DateTime) {
299 recurrenceSet.setTimeZone(((DateTime) start.getDate()).getTimeZone());
300 }
302 // if an explicit event duration is not specified, derive a value for recurring
303 // periods from the end date..
304 Dur rDuration;
305 // if no end or duration specified, end date equals start date..
306 if (end == null && duration == null) {
307 rDuration = new Dur(start.getDate(), start.getDate());
308 }
309 else if (duration == null) {
310 rDuration = new Dur(start.getDate(), end.getDate());
311 }
312 else {
313 rDuration = duration.getDuration();
314 }
316 // add recurrence dates..
317 for (final Iterator i = getProperties(Property.RDATE).iterator(); i.hasNext();) {
318 final RDate rdate = (RDate) i.next();
319 final Value rdateValue = (Value) rdate.getParameter(Parameter.VALUE);
320 if (Value.PERIOD.equals(rdateValue)) {
321 for (final Iterator j = rdate.getPeriods().iterator(); j.hasNext();) {
322 final Period rdatePeriod = (Period) j.next();
323 if (period.intersects(rdatePeriod)) {
324 recurrenceSet.add(rdatePeriod);
325 }
326 }
327 }
328 else if (Value.DATE_TIME.equals(rdateValue)) {
329 for (final Iterator j = rdate.getDates().iterator(); j.hasNext();) {
330 final DateTime rdateTime = (DateTime) j.next();
331 if (period.includes(rdateTime)) {
332 recurrenceSet.add(new Period(rdateTime, rDuration));
333 }
334 }
335 }
336 else {
337 for (final Iterator j = rdate.getDates().iterator(); j.hasNext();) {
338 final Date rdateDate = (Date) j.next();
339 if (period.includes(rdateDate)) {
340 recurrenceSet.add(new Period(new DateTime(rdateDate), rDuration));
341 }
342 }
343 }
344 }
346 // allow for recurrence rules that start prior to the specified period
347 // but still intersect with it..
348 final DateTime startMinusDuration = new DateTime(period.getStart());
349 startMinusDuration.setTime(rDuration.negate().getTime(
350 period.getStart()).getTime());
352 // add recurrence rules..
353 for (final Iterator i = getProperties(Property.RRULE).iterator(); i.hasNext();) {
354 final RRule rrule = (RRule) i.next();
355 final DateList rruleDates = rrule.getRecur().getDates(start.getDate(),
356 new Period(startMinusDuration, period.getEnd()), startValue);
357 for (final Iterator j = rruleDates.iterator(); j.hasNext();) {
358 final Date rruleDate = (Date) j.next();
359 recurrenceSet.add(new Period(new DateTime(rruleDate), rDuration));
360 }
361 }
363 // add initial instance if intersection with the specified period..
364 Period startPeriod = null;
365 if (end != null) {
366 startPeriod = new Period(new DateTime(start.getDate()),
367 new DateTime(end.getDate()));
368 }
369 else {
370 /*
371 * PeS: Anniversary type has no DTEND nor DUR, define DUR
372 * locally, otherwise we get NPE
373 */
374 if (duration == null) {
375 duration = new Duration(rDuration);
376 }
378 startPeriod = new Period(new DateTime(start.getDate()),
379 duration.getDuration());
380 }
381 if (period.intersects(startPeriod)) {
382 recurrenceSet.add(startPeriod);
383 }
385 // subtract exception dates..
386 for (final Iterator i = getProperties(Property.EXDATE).iterator(); i.hasNext();) {
387 final ExDate exdate = (ExDate) i.next();
388 for (final Iterator j = recurrenceSet.iterator(); j.hasNext();) {
389 final Period recurrence = (Period) j.next();
390 // for DATE-TIME instances check for DATE-based exclusions also..
391 if (exdate.getDates().contains(recurrence.getStart())
392 || exdate.getDates().contains(new Date(recurrence.getStart()))) {
393 j.remove();
394 }
395 }
396 }
398 // subtract exception rules..
399 for (final Iterator i = getProperties(Property.EXRULE).iterator(); i.hasNext();) {
400 final ExRule exrule = (ExRule) i.next();
401 final DateList exruleDates = exrule.getRecur().getDates(start.getDate(),
402 period, startValue);
403 for (final Iterator j = recurrenceSet.iterator(); j.hasNext();) {
404 final Period recurrence = (Period) j.next();
405 // for DATE-TIME instances check for DATE-based exclusions also..
406 if (exruleDates.contains(recurrence.getStart())
407 || exruleDates.contains(new Date(recurrence.getStart()))) {
408 j.remove();
409 }
410 }
411 }
413 return recurrenceSet;
414 }
415 }