check for duplicate UID's on validation.
[ical4j.git] / source / net / fortuna / ical4j / model / Calendar.java
blob333575e9d8abeba783532ba729457405c8c418cf
1 /**
2 * Copyright (c) 2009, Ben Fortuna
3 * All rights reserved.
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
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;
40 import java.util.Set;
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;
59 /**
60 * $Id: Calendar.java,v 1.39 2009/09/16 11:09:41 fortuna Exp $ [Apr 5, 2004]
62 * Defines an iCalendar calendar.
64 * <pre>
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
76 * notation:
78 * icalbody = calprops component
80 * calprops = 2*(
82 * ; 'prodid' and 'version' are both REQUIRED,
83 * ; but MUST NOT occur more than once
85 * prodid /version /
87 * ; 'calscale' and 'method' are optional,
88 * ; but MUST NOT occur more than once
90 * calscale /
91 * method /
93 * x-prop
95 * )
97 * component = 1*(eventc / todoc / journalc / freebusyc /
98 * / timezonec / iana-comp / x-comp)
100 * iana-comp = &quot;BEGIN&quot; &quot;:&quot; iana-token CRLF
102 * 1*contentline
104 * &quot;END&quot; &quot;:&quot; iana-token CRLF
106 * x-comp = &quot;BEGIN&quot; &quot;:&quot; x-name CRLF
108 * 1*contentline
110 * &quot;END&quot; &quot;:&quot; x-name CRLF
111 * </pre>
113 * Example 1 - Creating a new calendar:
115 * <pre><code>
116 * Calendar calendar = new Calendar();
117 * calendar.getProperties().add(new ProdId(&quot;-//Ben Fortuna//iCal4j 1.0//EN&quot;));
118 * calendar.getProperties().add(Version.VERSION_2_0);
119 * calendar.getProperties().add(CalScale.GREGORIAN);
121 * // Add events, etc..
122 * </code></pre>
124 * @author Ben Fortuna
126 public class Calendar implements Serializable {
128 private static final long serialVersionUID = -1654118204678581940L;
131 * Begin token.
133 public static final String BEGIN = "BEGIN";
136 * Calendar token.
138 public static final String VCALENDAR = "VCALENDAR";
141 * End token.
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.
154 public Calendar() {
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);
167 * Constructor.
168 * @param p a list of properties
169 * @param c a list of components
171 public Calendar(final PropertyList p, final ComponentList c) {
172 this.properties = p;
173 this.components = 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,
184 URISyntaxException {
186 this(new PropertyList(c.getProperties()), new ComponentList(c
187 .getComponents()));
191 * {@inheritDoc}
193 public final String toString() {
194 final StringBuffer buffer = new StringBuffer();
195 buffer.append(BEGIN);
196 buffer.append(':');
197 buffer.append(VCALENDAR);
198 buffer.append(Strings.LINE_SEPARATOR);
199 buffer.append(getProperties());
200 buffer.append(getComponents());
201 buffer.append(END);
202 buffer.append(':');
203 buffer.append(VCALENDAR);
204 buffer.append(Strings.LINE_SEPARATOR);
206 return buffer.toString();
210 * @return Returns the components.
212 public final ComponentList getComponents() {
213 return components;
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() {
238 return properties;
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 {
264 validate(true);
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,
288 properties);
289 PropertyValidator.getInstance().assertOneOrLess(Property.METHOD,
290 properties);
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);
322 uidsSeen.add(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)) {
335 // validate method..
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());
490 // }
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);
500 if (recurse) {
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();
513 property.validate();
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);
562 * {@inheritDoc}
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);
574 * {@inheritDoc}
576 public final int hashCode() {
577 return new HashCodeBuilder().append(getProperties()).append(
578 getComponents()).toHashCode();