2 * Copyright (c) 2009, Ben Fortuna
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
9 * o Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
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.
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.
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.
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
.HashSet
;
39 import java
.util
.Iterator
;
42 import net
.fortuna
.ical4j
.model
.component
.CalendarComponent
;
43 import net
.fortuna
.ical4j
.model
.property
.CalScale
;
44 import net
.fortuna
.ical4j
.model
.property
.Method
;
45 import net
.fortuna
.ical4j
.model
.property
.ProdId
;
46 import net
.fortuna
.ical4j
.model
.property
.Uid
;
47 import net
.fortuna
.ical4j
.model
.property
.Version
;
48 import net
.fortuna
.ical4j
.model
.property
.XProperty
;
49 import net
.fortuna
.ical4j
.util
.CompatibilityHints
;
50 import net
.fortuna
.ical4j
.util
.ComponentValidator
;
51 import net
.fortuna
.ical4j
.util
.PropertyValidator
;
52 import net
.fortuna
.ical4j
.util
.Strings
;
53 import net
.fortuna
.ical4j
.util
.validation
.ValidationResultHandler
;
54 import net
.fortuna
.ical4j
.util
.validation
.ValidationRuleInfo
;
56 import org
.apache
.commons
.lang
.builder
.EqualsBuilder
;
57 import org
.apache
.commons
.lang
.builder
.HashCodeBuilder
;
60 * $Id: Calendar.java,v 1.39 2009/09/16 11:09:41 fortuna Exp $ [Apr 5, 2004]
62 * Defines an iCalendar calendar.
65 * 4.6 Calendar Components
67 * The body of the iCalendar object consists of a sequence of calendar
68 * properties and one or more calendar components. The calendar
69 * properties are attributes that apply to the calendar as a whole. The
70 * calendar components are collections of properties that express a
71 * particular calendar semantic. For example, the calendar component can
72 * specify an event, a to-do, a journal entry, time zone information, or
73 * free/busy time information, or an alarm.
75 * The body of the iCalendar object is defined by the following
78 * icalbody = calprops component
82 * ; 'prodid' and 'version' are both REQUIRED,
83 * ; but MUST NOT occur more than once
87 * ; 'calscale' and 'method' are optional,
88 * ; but MUST NOT occur more than once
97 * component = 1*(eventc / todoc / journalc / freebusyc /
98 * / timezonec / iana-comp / x-comp)
100 * iana-comp = "BEGIN" ":" iana-token CRLF
104 * "END" ":" iana-token CRLF
106 * x-comp = "BEGIN" ":" x-name CRLF
110 * "END" ":" x-name CRLF
113 * Example 1 - Creating a new calendar:
116 * Calendar calendar = new Calendar();
117 * calendar.getProperties().add(new ProdId("-//Ben Fortuna//iCal4j 1.0//EN"));
118 * calendar.getProperties().add(Version.VERSION_2_0);
119 * calendar.getProperties().add(CalScale.GREGORIAN);
121 * // Add events, etc..
124 * @author Ben Fortuna
126 public class Calendar
implements Serializable
{
128 private static final long serialVersionUID
= -1654118204678581940L;
133 public static final String BEGIN
= "BEGIN";
138 public static final String VCALENDAR
= "VCALENDAR";
143 public static final String END
= "END";
145 private static final ValidationRuleInfo VALID_UID
= new ValidationRuleInfo("3.8.4.7");
147 private PropertyList properties
;
149 private ComponentList components
;
152 * Default constructor.
155 this(new PropertyList(), new ComponentList());
159 * Constructs a new calendar with no properties and the specified components.
160 * @param components a list of components to add to the calendar
162 public Calendar(final ComponentList components
) {
163 this(new PropertyList(), components
);
168 * @param p a list of properties
169 * @param c a list of components
171 public Calendar(final PropertyList p
, final ComponentList c
) {
177 * Creates a deep copy of the specified calendar.
178 * @param c the calendar to copy
179 * @throws IOException where an error occurs reading calendar data
180 * @throws ParseException where calendar parsing fails
181 * @throws URISyntaxException where an invalid URI string is encountered
183 public Calendar(Calendar c
) throws ParseException
, IOException
,
186 this(new PropertyList(c
.getProperties()), new ComponentList(c
193 public final String
toString() {
194 final StringBuffer buffer
= new StringBuffer();
195 buffer
.append(BEGIN
);
197 buffer
.append(VCALENDAR
);
198 buffer
.append(Strings
.LINE_SEPARATOR
);
199 buffer
.append(getProperties());
200 buffer
.append(getComponents());
203 buffer
.append(VCALENDAR
);
204 buffer
.append(Strings
.LINE_SEPARATOR
);
206 return buffer
.toString();
210 * @return Returns the components.
212 public final ComponentList
getComponents() {
217 * Convenience method for retrieving a list of named components.
218 * @param name name of components to retrieve
219 * @return a component list containing only components with the specified name
221 public final ComponentList
getComponents(final String name
) {
222 return getComponents().getComponents(name
);
226 * Convenience method for retrieving a named component.
227 * @param name name of the component to retrieve
228 * @return the first matching component in the component list with the specified name
230 public final Component
getComponent(final String name
) {
231 return getComponents().getComponent(name
);
235 * @return Returns the properties.
237 public final PropertyList
getProperties() {
242 * Convenience method for retrieving a list of named properties.
243 * @param name name of properties to retrieve
244 * @return a property list containing only properties with the specified name
246 public final PropertyList
getProperties(final String name
) {
247 return getProperties().getProperties(name
);
251 * Convenience method for retrieving a named property.
252 * @param name name of the property to retrieve
253 * @return the first matching property in the property list with the specified name
255 public final Property
getProperty(final String name
) {
256 return getProperties().getProperty(name
);
260 * Perform validation on the calendar, its properties and its components in its current state.
261 * @throws ValidationException where the calendar is not in a valid state
263 public final void validate() throws ValidationException
{
268 * Perform validation on the calendar in its current state.
269 * @param recurse indicates whether to validate the calendar's properties and components
270 * @throws ValidationException where the calendar is not in a valid state
272 public void validate(boolean recurse
) throws ValidationException
{
273 // 'prodid' and 'version' are both REQUIRED,
274 // but MUST NOT occur more than once
275 PropertyValidator
.getInstance().assertOne(Property
.PRODID
, properties
);
276 PropertyValidator
.getInstance().assertOne(Property
.VERSION
, properties
);
278 if (!CompatibilityHints
.isHintEnabled(CompatibilityHints
.KEY_RELAXED_VALIDATION
)) {
279 // require VERSION:2.0 for RFC2445..
280 if (!Version
.VERSION_2_0
.equals(getProperty(Property
.VERSION
))) {
281 ValidationResultHandler
.onValidationResult("Unsupported Version: " + getProperty(Property
.VERSION
).getValue());
285 // 'calscale' and 'method' are optional,
286 // but MUST NOT occur more than once
287 PropertyValidator
.getInstance().assertOneOrLess(Property
.CALSCALE
,
289 PropertyValidator
.getInstance().assertOneOrLess(Property
.METHOD
,
292 // must contain at least one component
293 if (getComponents().isEmpty()) {
294 ValidationResultHandler
.onValidationResult(
295 "Calendar must contain at least one component");
298 // validate properties..
299 for (final Iterator i
= getProperties().iterator(); i
.hasNext();) {
300 final Property property
= (Property
) i
.next();
302 if (!(property
instanceof XProperty
)
303 && !property
.isCalendarProperty()) {
304 ValidationResultHandler
.onValidationResult("Invalid property: "
305 + property
.getName());
309 // UID's must be unique
310 Set uidsSeen
= new HashSet();
311 for (final Iterator i
= getComponents().iterator(); i
.hasNext();)
313 final Component component
= (Component
) i
.next();
314 for (final Iterator j
= component
.getProperties(Uid
.UID
).iterator(); j
.hasNext();)
316 final String uid
= ((Uid
) j
.next()).getValue();
317 if (uidsSeen
.contains(uid
))
319 ValidationResultHandler
.onValidationResult("UID [{0}] encountered multiple times",
320 new Object
[] { uid
}, VALID_UID
);
326 // validate components..
327 for (final Iterator i
= getComponents().iterator(); i
.hasNext();) {
328 final Component component
= (Component
) i
.next();
329 if (!(component
instanceof CalendarComponent
)) {
330 ValidationResultHandler
.onValidationResult("Not a valid calendar component: " + component
.getName());
334 // if (!CompatibilityHints.isHintEnabled(CompatibilityHints.KEY_RELAXED_VALIDATION)) {
336 final Method method
= (Method
) getProperty(Property
.METHOD
);
337 if (Method
.PUBLISH
.equals(method
)) {
338 if (getComponent(Component
.VEVENT
) != null) {
339 ComponentValidator
.assertNone(Component
.VFREEBUSY
, getComponents());
340 ComponentValidator
.assertNone(Component
.VJOURNAL
, getComponents());
342 if (!CompatibilityHints
.isHintEnabled(CompatibilityHints
.KEY_RELAXED_VALIDATION
)) {
343 ComponentValidator
.assertNone(Component
.VTODO
, getComponents());
346 else if (getComponent(Component
.VFREEBUSY
) != null) {
347 ComponentValidator
.assertNone(Component
.VTODO
, getComponents());
348 ComponentValidator
.assertNone(Component
.VJOURNAL
, getComponents());
349 ComponentValidator
.assertNone(Component
.VTIMEZONE
, getComponents());
350 ComponentValidator
.assertNone(Component
.VALARM
, getComponents());
352 else if (getComponent(Component
.VTODO
) != null) {
353 // ComponentValidator.assertNone(Component.VFREEBUSY, getComponents());
354 // ComponentValidator.assertNone(Component.VEVENT, getComponents());
355 ComponentValidator
.assertNone(Component
.VJOURNAL
, getComponents());
357 else if (getComponent(Component
.VJOURNAL
) != null) {
358 // ComponentValidator.assertNone(Component.VFREEBUSY, getComponents());
359 // ComponentValidator.assertNone(Component.VEVENT, getComponents());
360 // ComponentValidator.assertNone(Component.VTODO, getComponents());
363 else if (Method
.REQUEST
.equals(getProperty(Property
.METHOD
))) {
364 if (getComponent(Component
.VEVENT
) != null) {
365 ComponentValidator
.assertNone(Component
.VFREEBUSY
, getComponents());
366 ComponentValidator
.assertNone(Component
.VJOURNAL
, getComponents());
367 ComponentValidator
.assertNone(Component
.VTODO
, getComponents());
369 else if (getComponent(Component
.VFREEBUSY
) != null) {
370 ComponentValidator
.assertNone(Component
.VTODO
, getComponents());
371 ComponentValidator
.assertNone(Component
.VJOURNAL
, getComponents());
372 ComponentValidator
.assertNone(Component
.VTIMEZONE
, getComponents());
373 ComponentValidator
.assertNone(Component
.VALARM
, getComponents());
375 else if (getComponent(Component
.VTODO
) != null) {
376 // ComponentValidator.assertNone(Component.VFREEBUSY, getComponents());
377 // ComponentValidator.assertNone(Component.VEVENT, getComponents());
378 ComponentValidator
.assertNone(Component
.VJOURNAL
, getComponents());
381 else if (Method
.REPLY
.equals(getProperty(Property
.METHOD
))) {
382 if (getComponent(Component
.VEVENT
) != null) {
383 ComponentValidator
.assertOneOrLess(Component
.VTIMEZONE
, getComponents());
385 ComponentValidator
.assertNone(Component
.VALARM
, getComponents());
386 ComponentValidator
.assertNone(Component
.VFREEBUSY
, getComponents());
387 ComponentValidator
.assertNone(Component
.VJOURNAL
, getComponents());
388 ComponentValidator
.assertNone(Component
.VTODO
, getComponents());
390 else if (getComponent(Component
.VFREEBUSY
) != null) {
391 ComponentValidator
.assertNone(Component
.VTODO
, getComponents());
392 ComponentValidator
.assertNone(Component
.VJOURNAL
, getComponents());
393 ComponentValidator
.assertNone(Component
.VTIMEZONE
, getComponents());
394 ComponentValidator
.assertNone(Component
.VALARM
, getComponents());
396 else if (getComponent(Component
.VTODO
) != null) {
397 ComponentValidator
.assertOneOrLess(Component
.VTIMEZONE
, getComponents());
399 ComponentValidator
.assertNone(Component
.VALARM
, getComponents());
400 // ComponentValidator.assertNone(Component.VFREEBUSY, getComponents());
401 // ComponentValidator.assertNone(Component.VEVENT, getComponents());
402 ComponentValidator
.assertNone(Component
.VJOURNAL
, getComponents());
405 else if (Method
.ADD
.equals(getProperty(Property
.METHOD
))) {
406 if (getComponent(Component
.VEVENT
) != null) {
407 ComponentValidator
.assertNone(Component
.VFREEBUSY
, getComponents());
408 ComponentValidator
.assertNone(Component
.VJOURNAL
, getComponents());
409 ComponentValidator
.assertNone(Component
.VTODO
, getComponents());
411 else if (getComponent(Component
.VTODO
) != null) {
412 ComponentValidator
.assertNone(Component
.VFREEBUSY
, getComponents());
413 // ComponentValidator.assertNone(Component.VEVENT, getComponents());
414 ComponentValidator
.assertNone(Component
.VJOURNAL
, getComponents());
416 else if (getComponent(Component
.VJOURNAL
) != null) {
417 ComponentValidator
.assertOneOrLess(Component
.VTIMEZONE
, getComponents());
419 ComponentValidator
.assertNone(Component
.VFREEBUSY
, getComponents());
420 // ComponentValidator.assertNone(Component.VEVENT, getComponents());
421 // ComponentValidator.assertNone(Component.VTODO, getComponents());
424 else if (Method
.CANCEL
.equals(getProperty(Property
.METHOD
))) {
425 if (getComponent(Component
.VEVENT
) != null) {
426 ComponentValidator
.assertNone(Component
.VALARM
, getComponents());
427 ComponentValidator
.assertNone(Component
.VFREEBUSY
, getComponents());
428 ComponentValidator
.assertNone(Component
.VJOURNAL
, getComponents());
429 ComponentValidator
.assertNone(Component
.VTODO
, getComponents());
431 else if (getComponent(Component
.VTODO
) != null) {
432 ComponentValidator
.assertOneOrLess(Component
.VTIMEZONE
, getComponents());
434 ComponentValidator
.assertNone(Component
.VALARM
, getComponents());
435 ComponentValidator
.assertNone(Component
.VFREEBUSY
, getComponents());
436 // ComponentValidator.assertNone(Component.VEVENT, getComponents());
437 ComponentValidator
.assertNone(Component
.VJOURNAL
, getComponents());
439 else if (getComponent(Component
.VJOURNAL
) != null) {
440 ComponentValidator
.assertNone(Component
.VALARM
, getComponents());
441 ComponentValidator
.assertNone(Component
.VFREEBUSY
, getComponents());
442 // ComponentValidator.assertNone(Component.VEVENT, getComponents());
443 // ComponentValidator.assertNone(Component.VTODO, getComponents());
446 else if (Method
.REFRESH
.equals(getProperty(Property
.METHOD
))) {
447 if (getComponent(Component
.VEVENT
) != null) {
448 ComponentValidator
.assertNone(Component
.VALARM
, getComponents());
449 ComponentValidator
.assertNone(Component
.VFREEBUSY
, getComponents());
450 ComponentValidator
.assertNone(Component
.VJOURNAL
, getComponents());
451 ComponentValidator
.assertNone(Component
.VTODO
, getComponents());
453 else if (getComponent(Component
.VTODO
) != null) {
454 ComponentValidator
.assertNone(Component
.VALARM
, getComponents());
455 ComponentValidator
.assertNone(Component
.VFREEBUSY
, getComponents());
456 // ComponentValidator.assertNone(Component.VEVENT, getComponents());
457 ComponentValidator
.assertNone(Component
.VJOURNAL
, getComponents());
458 ComponentValidator
.assertNone(Component
.VTIMEZONE
, getComponents());
461 else if (Method
.COUNTER
.equals(getProperty(Property
.METHOD
))) {
462 if (getComponent(Component
.VEVENT
) != null) {
463 ComponentValidator
.assertNone(Component
.VFREEBUSY
, getComponents());
464 ComponentValidator
.assertNone(Component
.VJOURNAL
, getComponents());
465 ComponentValidator
.assertNone(Component
.VTODO
, getComponents());
467 else if (getComponent(Component
.VTODO
) != null) {
468 ComponentValidator
.assertOneOrLess(Component
.VTIMEZONE
, getComponents());
470 ComponentValidator
.assertNone(Component
.VFREEBUSY
, getComponents());
471 // ComponentValidator.assertNone(Component.VEVENT, getComponents());
472 ComponentValidator
.assertNone(Component
.VJOURNAL
, getComponents());
475 else if (Method
.DECLINE_COUNTER
.equals(getProperty(Property
.METHOD
))) {
476 if (getComponent(Component
.VEVENT
) != null) {
477 ComponentValidator
.assertNone(Component
.VFREEBUSY
, getComponents());
478 ComponentValidator
.assertNone(Component
.VJOURNAL
, getComponents());
479 ComponentValidator
.assertNone(Component
.VTODO
, getComponents());
480 ComponentValidator
.assertNone(Component
.VTIMEZONE
, getComponents());
481 ComponentValidator
.assertNone(Component
.VALARM
, getComponents());
483 else if (getComponent(Component
.VTODO
) != null) {
484 ComponentValidator
.assertNone(Component
.VALARM
, getComponents());
485 ComponentValidator
.assertNone(Component
.VFREEBUSY
, getComponents());
486 // ComponentValidator.assertNone(Component.VEVENT, getComponents());
487 ComponentValidator
.assertNone(Component
.VJOURNAL
, getComponents());
492 // perform ITIP validation on components..
493 if (method
!= null) {
494 for (final Iterator i
= getComponents().iterator(); i
.hasNext();) {
495 final CalendarComponent component
= (CalendarComponent
) i
.next();
496 component
.validate(method
);
501 validateProperties();
502 validateComponents();
507 * Invoke validation on the calendar properties in its current state.
508 * @throws ValidationException where any of the calendar properties is not in a valid state
510 private void validateProperties() throws ValidationException
{
511 for (final Iterator i
= getProperties().iterator(); i
.hasNext();) {
512 final Property property
= (Property
) i
.next();
518 * Invoke validation on the calendar components in its current state.
519 * @param resultHandler
520 * @throws ValidationException where any of the calendar components is not in a valid state
522 private void validateComponents() throws ValidationException
{
523 for (final Iterator i
= getComponents().iterator(); i
.hasNext();) {
524 final Component component
= (Component
) i
.next();
525 component
.validate(true);
530 * Returns the mandatory prodid property.
531 * @return the PRODID property, or null if property doesn't exist
533 public final ProdId
getProductId() {
534 return (ProdId
) getProperty(Property
.PRODID
);
538 * Returns the mandatory version property.
539 * @return the VERSION property, or null if property doesn't exist
541 public final Version
getVersion() {
542 return (Version
) getProperty(Property
.VERSION
);
546 * Returns the optional calscale property.
547 * @return the CALSCALE property, or null if property doesn't exist
549 public final CalScale
getCalendarScale() {
550 return (CalScale
) getProperty(Property
.CALSCALE
);
554 * Returns the optional method property.
555 * @return the METHOD property, or null if property doesn't exist
557 public final Method
getMethod() {
558 return (Method
) getProperty(Property
.METHOD
);
564 public final boolean equals(final Object arg0
) {
565 if (arg0
instanceof Calendar
) {
566 final Calendar calendar
= (Calendar
) arg0
;
567 return new EqualsBuilder().append(getProperties(), calendar
.getProperties())
568 .append(getComponents(), calendar
.getComponents()).isEquals();
570 return super.equals(arg0
);
576 public final int hashCode() {
577 return new HashCodeBuilder().append(getProperties()).append(
578 getComponents()).toHashCode();